diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 88e440be..1a8175f7 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -16,10 +16,10 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - sdk: [3.0.0, dev] + sdk: [3.5, dev] steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 with: sdk: ${{ matrix.sdk }} - name: Report version @@ -60,7 +60,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - sdk: [3.0.0, dev] + sdk: [3.5, dev] platform: [vm, chrome] exclude: # We only run Chrome tests on Linux. No need to run them @@ -70,8 +70,8 @@ jobs: - os: macos-latest platform: chrome steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: dart-lang/setup-dart@e630b99d28a3b71860378cafdc2a067c71107f94 with: sdk: ${{ matrix.sdk }} - name: Report version diff --git a/.github/workflows/health.yaml b/.github/workflows/health.yaml new file mode 100644 index 00000000..3ba41f30 --- /dev/null +++ b/.github/workflows/health.yaml @@ -0,0 +1,13 @@ +name: Health +on: + pull_request: + branches: [ master ] + types: [opened, synchronize, reopened, labeled, unlabeled] +jobs: + health: + uses: dart-lang/ecosystem/.github/workflows/health.yaml@main + with: + checks: "changelog,do-not-submit,breaking,coverage,leaking" + ignore_coverage: "example/**,interop/**" + permissions: + pull-requests: write diff --git a/.github/workflows/post_summaries.yaml b/.github/workflows/post_summaries.yaml new file mode 100644 index 00000000..9cf89491 --- /dev/null +++ b/.github/workflows/post_summaries.yaml @@ -0,0 +1,17 @@ +name: Comment on the pull request + +on: + # Trigger this workflow after the Health workflow completes. This workflow will have permissions to + # do things like create comments on the PR, even if the original workflow couldn't. + workflow_run: + workflows: + - Health + - Publish + types: + - completed + +jobs: + upload: + uses: dart-lang/ecosystem/.github/workflows/post_summaries.yaml@main + permissions: + pull-requests: write \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e1332dc5..273cc7c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## 4.0.2-wip + +* Internal optimization to client code. +* Small fixes, such as ports in testing and enabling `timeline_test.dart`. +* When the keep alive manager runs into a timeout, it will finish the transport instead of closing + the connection, as defined in the gRPC spec. +* Upgrade to `package:lints` version 5.0.0 and Dart SDK version 3.5.0. +* Upgrade `example/grpc-web` code. + +## 4.0.1 + +* Fix header and trailing not completing if the call is terminated. Fixes [#727](https://github.com/grpc/grpc-dart/issues/727) + +## 4.0.0 + +* Set compressed flag correctly for grpc-encoding = identity. Fixes [#669](https://github.com/grpc/grpc-dart/issues/669) (https://github.com/grpc/grpc-dart/pull/693) +* Remove generated status codes. +* Remove dependency on `package:archive`. +* Move `codec.dart`. +* Work around hang during Flutter hot restart by adding default case handler in _GrpcWebConversionSink.add. + ## 3.2.4 * Forward internal `GrpcError` on when throwing while sending a request. @@ -19,19 +40,19 @@ ## 3.2.1 -* `package:http` now supports more versions: `>=0.13.0 <2.0.0`. +* `package:http` now supports more versions: `>=0.13.0 <2.0.0`. * `package:protobuf` new supports more versions: `>=2.0.0 <4.0.0`. ## 3.2.0 -* `ChannelOptions` now exposes `connectTimeout`, which is used on the +* `ChannelOptions` now exposes `connectTimeout`, which is used on the socket connect. This is used to specify the maximum allowed time to wait for a connection to be established. If `connectTime` is longer than the system level timeout duration, a timeout may occur sooner than specified in `connectTimeout`. On timeout, a `SocketException` is thrown. * Require Dart 2.17 or greater. * Fix issue [#51](https://github.com/grpc/grpc-dart/issues/51), add support for custom error handling. -* Expose client IP address to server +* Expose client IP address to server * Add a `channelShutdownHandler` argument to `ClientChannel` and the subclasses. This callback can be used to react to channel shutdown or termination. * Export the `Code` protobuf enum from the `grpc.dart` library. diff --git a/README.md b/README.md index 667f28d9..f85542b2 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ The [Dart](https://www.dart.dev/) implementation of [gRPC](https://grpc.io/): A high performance, open source, general RPC framework that puts mobile and HTTP/2 first. -[![CI status](https://github.com/grpc/grpc-dart/workflows/Dart/badge.svg)](https://github.com/grpc/grpc-dart/actions?query=workflow%3A%22Dart%22+branch%3Amaster) +[![Dart](https://github.com/grpc/grpc-dart/actions/workflows/dart.yml/badge.svg)](https://github.com/grpc/grpc-dart/actions/workflows/dart.yml) [![pub package](https://img.shields.io/pub/v/grpc.svg)](https://pub.dev/packages/grpc) ## Learn more - [Quick Start](https://grpc.io/docs/languages/dart/quickstart) - get an app running in minutes -- [Examples](example) +- [Examples](https://github.com/grpc/grpc-dart/tree/master/example) - [API reference](https://grpc.io/docs/languages/dart/api) For complete documentation, see [Dart gRPC](https://grpc.io/docs/languages/dart). diff --git a/analysis_options.yaml b/analysis_options.yaml index 01c74eb9..5bcd150f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -9,13 +9,15 @@ analyzer: linter: rules: - - always_declare_return_types - - cancel_subscriptions - - close_sinks - - directives_ordering - - omit_local_variable_types - - prefer_final_locals - - prefer_single_quotes - - test_types_in_equals - - use_super_parameters - - prefer_relative_imports + #true + always_declare_return_types: true + cancel_subscriptions: true + close_sinks: true + directives_ordering: true + omit_local_variable_types: true + prefer_final_locals: true + prefer_single_quotes: true + test_types_in_equals: true + prefer_relative_imports: true + #false + unintended_html_in_doc_comment: false diff --git a/example/README.md b/example/README.md index 67cc9446..330358ab 100644 --- a/example/README.md +++ b/example/README.md @@ -1,7 +1,7 @@ Four code examples are available: 1. [helloworld](https://github.com/grpc/grpc-dart/tree/master/example/helloworld): - A demonstration of using the Dart gRPC library to perform unary RPs. + A demonstration of using the Dart gRPC library to perform unary RPCs. 1. [googleapis](https://github.com/grpc/grpc-dart/tree/master/example/googleapis): A demonstration of using the Dart gRPC library to communicate with Google APIs. diff --git a/example/grpc-web/lib/app.dart b/example/grpc-web/lib/app.dart index bdc920f6..fce83c51 100644 --- a/example/grpc-web/lib/app.dart +++ b/example/grpc-web/lib/app.dart @@ -1,3 +1,18 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import 'dart:async'; import 'dart:html'; diff --git a/example/grpc-web/pubspec.yaml b/example/grpc-web/pubspec.yaml index e215c131..10f80ceb 100644 --- a/example/grpc-web/pubspec.yaml +++ b/example/grpc-web/pubspec.yaml @@ -3,7 +3,7 @@ description: Dart gRPC-Web sample client publish_to: none environment: - sdk: '>=2.12.0 <3.0.0' + sdk: ^3.5.0 dependencies: grpc: @@ -11,6 +11,6 @@ dependencies: protobuf: ^3.0.0 dev_dependencies: - build_runner: ^2.0.0 - build_web_compilers: '>3.0.0 <5.0.0' - lints: ^2.0.0 + build_runner: ^2.4.13 + build_web_compilers: ^4.0.11 + lints: ^5.0.0 diff --git a/example/helloworld/bin/client.dart b/example/helloworld/bin/client.dart index 4d4635d9..4ec8e8e2 100644 --- a/example/helloworld/bin/client.dart +++ b/example/helloworld/bin/client.dart @@ -13,10 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// Dart implementation of the gRPC helloworld.Greeter client. import 'package:grpc/grpc.dart'; import 'package:helloworld/src/generated/helloworld.pbgrpc.dart'; +/// Dart implementation of the gRPC helloworld.Greeter client. Future main(List args) async { final channel = ClientChannel( 'localhost', diff --git a/example/helloworld/bin/server.dart b/example/helloworld/bin/server.dart index 0f2b2878..a2b491c7 100644 --- a/example/helloworld/bin/server.dart +++ b/example/helloworld/bin/server.dart @@ -13,10 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// Dart implementation of the gRPC helloworld.Greeter server. import 'package:grpc/grpc.dart'; import 'package:helloworld/src/generated/helloworld.pbgrpc.dart'; +/// Dart implementation of the gRPC helloworld.Greeter server. class GreeterService extends GreeterServiceBase { @override Future sayHello(ServiceCall call, HelloRequest request) async { diff --git a/example/helloworld/bin/unix_client.dart b/example/helloworld/bin/unix_client.dart index 5f30cc25..0c3cacc7 100644 --- a/example/helloworld/bin/unix_client.dart +++ b/example/helloworld/bin/unix_client.dart @@ -13,12 +13,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// Dart implementation of the gRPC helloworld.Greeter client. import 'dart:io'; import 'package:grpc/grpc.dart'; import 'package:helloworld/src/generated/helloworld.pbgrpc.dart'; +/// Dart implementation of the gRPC helloworld.Greeter client. Future main(List args) async { final udsAddress = InternetAddress('localhost', type: InternetAddressType.unix); diff --git a/example/helloworld/bin/unix_server.dart b/example/helloworld/bin/unix_server.dart index 82fbf022..a30eaecf 100644 --- a/example/helloworld/bin/unix_server.dart +++ b/example/helloworld/bin/unix_server.dart @@ -13,12 +13,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// Dart implementation of the gRPC helloworld.Greeter server. import 'dart:io'; import 'package:grpc/grpc.dart'; import 'package:helloworld/src/generated/helloworld.pbgrpc.dart'; +/// Dart implementation of the gRPC helloworld.Greeter server. class GreeterService extends GreeterServiceBase { @override Future sayHello(ServiceCall call, HelloRequest request) async { diff --git a/interop/lib/src/client.dart b/interop/lib/src/client.dart index 8cead2f1..a61fa817 100644 --- a/interop/lib/src/client.dart +++ b/interop/lib/src/client.dart @@ -208,7 +208,7 @@ class Tester { final receivedBytes = response.payload.body.length; if (receivedBytes != 314159) { throw 'Response payload mismatch. Expected 314159 bytes, ' - 'got ${receivedBytes}.'; + 'got $receivedBytes.'; } } @@ -869,7 +869,7 @@ class Tester { final receivedBytes = response.payload.body.length; if (receivedBytes != 314159) { throw 'Response payload mismatch. Expected 314159 bytes, ' - 'got ${receivedBytes}.'; + 'got $receivedBytes.'; } return response; } diff --git a/lib/grpc.dart b/lib/grpc.dart index 5f795236..1c9593fa 100644 --- a/lib/grpc.dart +++ b/lib/grpc.dart @@ -13,6 +13,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +// ignore: dangling_library_doc_comments +/// Status detail types and error codes +export 'package:grpc/src/generated/google/rpc/error_details.pb.dart'; + export 'src/auth/auth.dart' show BaseAuthenticator; export 'src/auth/auth_io.dart' show @@ -39,10 +43,6 @@ export 'src/client/options.dart' export 'src/client/proxy.dart' show Proxy; export 'src/client/transport/http2_credentials.dart' show BadCertificateHandler, allowBadCertificates, ChannelCredentials; - -/// Status detail types and error codes -export 'src/generated/google/rpc/code.pbenum.dart'; -export 'src/generated/google/rpc/error_details.pb.dart'; export 'src/server/call.dart' show ServiceCall; export 'src/server/interceptor.dart' show Interceptor; export 'src/server/server.dart' diff --git a/lib/service_api.dart b/lib/service_api.dart index 53d056d4..026b26b0 100644 --- a/lib/service_api.dart +++ b/lib/service_api.dart @@ -16,7 +16,7 @@ /// Exports the minimum api to define server and client stubs. /// /// Mainly intended to be imported by generated code. -library service_api; +library; export 'src/client/call.dart' show CallOptions, MetadataProvider; export 'src/client/channel.dart' show ClientChannel; diff --git a/lib/src/auth/auth_io.dart b/lib/src/auth/auth_io.dart index 681ee590..bb720004 100644 --- a/lib/src/auth/auth_io.dart +++ b/lib/src/auth/auth_io.dart @@ -69,7 +69,7 @@ class _CredentialsRefreshingAuthenticator extends HttpBasedAuthenticator { await super.authenticate(metadata, uri); if (_quotaProject != null) { // https://cloud.google.com/apis/docs/system-parameters#definitions - metadata['X-Goog-User-Project'] = _quotaProject!; + metadata['X-Goog-User-Project'] = _quotaProject; } } diff --git a/lib/src/client/call.dart b/lib/src/client/call.dart index 8d5918f8..65f0de3b 100644 --- a/lib/src/client/call.dart +++ b/lib/src/client/call.dart @@ -86,9 +86,9 @@ class CallOptions { CallOptions mergedWith(CallOptions? other) { if (other == null) return this; - final mergedMetadata = Map.from(metadata)..addAll(other.metadata); + final mergedMetadata = Map.of(metadata)..addAll(other.metadata); final mergedTimeout = other.timeout ?? timeout; - final mergedProviders = List.from(metadataProviders) + final mergedProviders = List.of(metadataProviders) ..addAll(other.metadataProviders); final mergedCompression = other.compression ?? compression; return CallOptions._( @@ -146,9 +146,9 @@ class WebCallOptions extends CallOptions { CallOptions mergedWith(CallOptions? other) { if (other == null) return this; - final mergedMetadata = Map.from(metadata)..addAll(other.metadata); + final mergedMetadata = Map.of(metadata)..addAll(other.metadata); final mergedTimeout = other.timeout ?? timeout; - final mergedProviders = List.from(metadataProviders) + final mergedProviders = List.of(metadataProviders) ..addAll(other.metadataProviders); if (other is! WebCallOptions) { @@ -241,7 +241,7 @@ class ClientCall implements Response { if (options.metadataProviders.isEmpty) { _sendRequest(connection, _sanitizeMetadata(options.metadata)); } else { - final metadata = Map.from(options.metadata); + final metadata = Map.of(options.metadata); Future.forEach( options.metadataProviders, (MetadataProvider provider) => provider(metadata, @@ -483,6 +483,12 @@ class ClientCall implements Response { if (_responseSubscription != null) { futures.add(_responseSubscription!.cancel()); } + if (!_headers.isCompleted) { + _headers.complete({}); + } + if (!_trailers.isCompleted) { + _trailers.complete({}); + } await Future.wait(futures); } diff --git a/lib/src/client/http2_connection.dart b/lib/src/client/http2_connection.dart index 998410a1..4cce6771 100644 --- a/lib/src/client/http2_connection.dart +++ b/lib/src/client/http2_connection.dart @@ -113,7 +113,7 @@ class Http2ClientConnection implements connection.ClientConnection { transport.ping(); } }, - onPingTimeout: () => shutdown(), + onPingTimeout: () => transport.finish(), ); transport.onFrameReceived .listen((_) => keepAliveManager?.onFrameReceived()); diff --git a/lib/src/client/transport/http2_credentials.dart b/lib/src/client/transport/http2_credentials.dart index 5b20e7af..f2b76690 100644 --- a/lib/src/client/transport/http2_credentials.dart +++ b/lib/src/client/transport/http2_credentials.dart @@ -58,7 +58,7 @@ class ChannelCredentials { if (!isSecure) return null; if (_certificateBytes != null) { return createSecurityContext(false) - ..setTrustedCertificatesBytes(_certificateBytes!, + ..setTrustedCertificatesBytes(_certificateBytes, password: _certificatePassword); } final context = SecurityContext(withTrustedRoots: true); diff --git a/lib/src/client/transport/web_streams.dart b/lib/src/client/transport/web_streams.dart index aba0a7bc..d4100add 100644 --- a/lib/src/client/transport/web_streams.dart +++ b/lib/src/client/transport/web_streams.dart @@ -132,6 +132,16 @@ class _GrpcWebConversionSink implements ChunkedConversionSink { void add(ByteBuffer chunk) { _chunkOffset = 0; final chunkData = chunk.asUint8List(); + // in flutter web, when a hot-restart is requested, the code can get stuck + // in the following loop if there is an active streaming call. the + // switch statement is invoked but doesn't match any of the cases. this + // presumably has something to do how enums work across isolates. + // possibly related to https://github.com/dart-lang/sdk/issues/35626. + // + // in any case, adding a default handler to the switch statement + // causes this loop to end in that case, allowing the old isolate + // to shut down and the hot-restart to work properly. + processingLoop: while (_chunkOffset < chunk.lengthInBytes) { switch (_state) { case _GrpcWebParseState.init: @@ -143,6 +153,10 @@ class _GrpcWebConversionSink implements ChunkedConversionSink { case _GrpcWebParseState.message: _parseMessage(chunkData); break; + // ignore: unreachable_switch_default + default: + // only expected to be hit when hot-restarting, see above + break processingLoop; } } _chunkOffset = 0; diff --git a/lib/src/generated/google/rpc/code.pb.dart b/lib/src/generated/google/rpc/code.pb.dart deleted file mode 100644 index 65802263..00000000 --- a/lib/src/generated/google/rpc/code.pb.dart +++ /dev/null @@ -1,14 +0,0 @@ -// -// Generated code. Do not modify. -// source: google/rpc/code.proto -// -// @dart = 2.12 - -// ignore_for_file: annotate_overrides, camel_case_types, comment_references -// ignore_for_file: constant_identifier_names, library_prefixes -// ignore_for_file: non_constant_identifier_names, prefer_final_fields -// ignore_for_file: unnecessary_import, unnecessary_this, unused_import - -import 'dart:core' as $core; - -export 'code.pbenum.dart'; diff --git a/lib/src/generated/google/rpc/code.pbenum.dart b/lib/src/generated/google/rpc/code.pbenum.dart deleted file mode 100644 index f3d7bf37..00000000 --- a/lib/src/generated/google/rpc/code.pbenum.dart +++ /dev/null @@ -1,79 +0,0 @@ -// -// Generated code. Do not modify. -// source: google/rpc/code.proto -// -// @dart = 2.12 - -// ignore_for_file: annotate_overrides, camel_case_types, comment_references -// ignore_for_file: constant_identifier_names, library_prefixes -// ignore_for_file: non_constant_identifier_names, prefer_final_fields -// ignore_for_file: unnecessary_import, unnecessary_this, unused_import - -import 'dart:core' as $core; - -import 'package:protobuf/protobuf.dart' as $pb; - -/// The canonical error codes for gRPC APIs. -/// -/// -/// Sometimes multiple error codes may apply. Services should return -/// the most specific error code that applies. For example, prefer -/// `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply. -/// Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`. -class Code extends $pb.ProtobufEnum { - static const Code OK = Code._(0, _omitEnumNames ? '' : 'OK'); - static const Code CANCELLED = Code._(1, _omitEnumNames ? '' : 'CANCELLED'); - static const Code UNKNOWN = Code._(2, _omitEnumNames ? '' : 'UNKNOWN'); - static const Code INVALID_ARGUMENT = - Code._(3, _omitEnumNames ? '' : 'INVALID_ARGUMENT'); - static const Code DEADLINE_EXCEEDED = - Code._(4, _omitEnumNames ? '' : 'DEADLINE_EXCEEDED'); - static const Code NOT_FOUND = Code._(5, _omitEnumNames ? '' : 'NOT_FOUND'); - static const Code ALREADY_EXISTS = - Code._(6, _omitEnumNames ? '' : 'ALREADY_EXISTS'); - static const Code PERMISSION_DENIED = - Code._(7, _omitEnumNames ? '' : 'PERMISSION_DENIED'); - static const Code UNAUTHENTICATED = - Code._(16, _omitEnumNames ? '' : 'UNAUTHENTICATED'); - static const Code RESOURCE_EXHAUSTED = - Code._(8, _omitEnumNames ? '' : 'RESOURCE_EXHAUSTED'); - static const Code FAILED_PRECONDITION = - Code._(9, _omitEnumNames ? '' : 'FAILED_PRECONDITION'); - static const Code ABORTED = Code._(10, _omitEnumNames ? '' : 'ABORTED'); - static const Code OUT_OF_RANGE = - Code._(11, _omitEnumNames ? '' : 'OUT_OF_RANGE'); - static const Code UNIMPLEMENTED = - Code._(12, _omitEnumNames ? '' : 'UNIMPLEMENTED'); - static const Code INTERNAL = Code._(13, _omitEnumNames ? '' : 'INTERNAL'); - static const Code UNAVAILABLE = - Code._(14, _omitEnumNames ? '' : 'UNAVAILABLE'); - static const Code DATA_LOSS = Code._(15, _omitEnumNames ? '' : 'DATA_LOSS'); - - static const $core.List values = [ - OK, - CANCELLED, - UNKNOWN, - INVALID_ARGUMENT, - DEADLINE_EXCEEDED, - NOT_FOUND, - ALREADY_EXISTS, - PERMISSION_DENIED, - UNAUTHENTICATED, - RESOURCE_EXHAUSTED, - FAILED_PRECONDITION, - ABORTED, - OUT_OF_RANGE, - UNIMPLEMENTED, - INTERNAL, - UNAVAILABLE, - DATA_LOSS, - ]; - - static final $core.Map<$core.int, Code> _byValue = - $pb.ProtobufEnum.initByValue(values); - static Code? valueOf($core.int value) => _byValue[value]; - - const Code._($core.int v, $core.String n) : super(v, n); -} - -const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/lib/src/generated/google/rpc/code.pbjson.dart b/lib/src/generated/google/rpc/code.pbjson.dart deleted file mode 100644 index 3e9bd5a8..00000000 --- a/lib/src/generated/google/rpc/code.pbjson.dart +++ /dev/null @@ -1,47 +0,0 @@ -// -// Generated code. Do not modify. -// source: google/rpc/code.proto -// -// @dart = 2.12 - -// ignore_for_file: annotate_overrides, camel_case_types, comment_references -// ignore_for_file: constant_identifier_names, library_prefixes -// ignore_for_file: non_constant_identifier_names, prefer_final_fields -// ignore_for_file: unnecessary_import, unnecessary_this, unused_import - -import 'dart:convert' as $convert; -import 'dart:core' as $core; -import 'dart:typed_data' as $typed_data; - -@$core.Deprecated('Use codeDescriptor instead') -const Code$json = { - '1': 'Code', - '2': [ - {'1': 'OK', '2': 0}, - {'1': 'CANCELLED', '2': 1}, - {'1': 'UNKNOWN', '2': 2}, - {'1': 'INVALID_ARGUMENT', '2': 3}, - {'1': 'DEADLINE_EXCEEDED', '2': 4}, - {'1': 'NOT_FOUND', '2': 5}, - {'1': 'ALREADY_EXISTS', '2': 6}, - {'1': 'PERMISSION_DENIED', '2': 7}, - {'1': 'UNAUTHENTICATED', '2': 16}, - {'1': 'RESOURCE_EXHAUSTED', '2': 8}, - {'1': 'FAILED_PRECONDITION', '2': 9}, - {'1': 'ABORTED', '2': 10}, - {'1': 'OUT_OF_RANGE', '2': 11}, - {'1': 'UNIMPLEMENTED', '2': 12}, - {'1': 'INTERNAL', '2': 13}, - {'1': 'UNAVAILABLE', '2': 14}, - {'1': 'DATA_LOSS', '2': 15}, - ], -}; - -/// Descriptor for `Code`. Decode as a `google.protobuf.EnumDescriptorProto`. -final $typed_data.Uint8List codeDescriptor = $convert.base64Decode( - 'CgRDb2RlEgYKAk9LEAASDQoJQ0FOQ0VMTEVEEAESCwoHVU5LTk9XThACEhQKEElOVkFMSURfQV' - 'JHVU1FTlQQAxIVChFERUFETElORV9FWENFRURFRBAEEg0KCU5PVF9GT1VORBAFEhIKDkFMUkVB' - 'RFlfRVhJU1RTEAYSFQoRUEVSTUlTU0lPTl9ERU5JRUQQBxITCg9VTkFVVEhFTlRJQ0FURUQQEB' - 'IWChJSRVNPVVJDRV9FWEhBVVNURUQQCBIXChNGQUlMRURfUFJFQ09ORElUSU9OEAkSCwoHQUJP' - 'UlRFRBAKEhAKDE9VVF9PRl9SQU5HRRALEhEKDVVOSU1QTEVNRU5URUQQDBIMCghJTlRFUk5BTB' - 'ANEg8KC1VOQVZBSUxBQkxFEA4SDQoJREFUQV9MT1NTEA8='); diff --git a/lib/src/protos/google/rpc/code.proto b/lib/src/protos/google/rpc/code.proto deleted file mode 100644 index d115da14..00000000 --- a/lib/src/protos/google/rpc/code.proto +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto3"; - -package google.rpc; - -option go_package = "google.golang.org/genproto/googleapis/rpc/code;code"; -option java_multiple_files = true; -option java_outer_classname = "CodeProto"; -option java_package = "com.google.rpc"; -option objc_class_prefix = "RPC"; - -// The canonical error codes for gRPC APIs. -// -// -// Sometimes multiple error codes may apply. Services should return -// the most specific error code that applies. For example, prefer -// `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply. -// Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`. -enum Code { - // Not an error; returned on success - // - // HTTP Mapping: 200 OK - OK = 0; - - // The operation was cancelled, typically by the caller. - // - // HTTP Mapping: 499 Client Closed Request - CANCELLED = 1; - - // Unknown error. For example, this error may be returned when - // a `Status` value received from another address space belongs to - // an error space that is not known in this address space. Also - // errors raised by APIs that do not return enough error information - // may be converted to this error. - // - // HTTP Mapping: 500 Internal Server Error - UNKNOWN = 2; - - // The client specified an invalid argument. Note that this differs - // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments - // that are problematic regardless of the state of the system - // (e.g., a malformed file name). - // - // HTTP Mapping: 400 Bad Request - INVALID_ARGUMENT = 3; - - // The deadline expired before the operation could complete. For operations - // that change the state of the system, this error may be returned - // even if the operation has completed successfully. For example, a - // successful response from a server could have been delayed long - // enough for the deadline to expire. - // - // HTTP Mapping: 504 Gateway Timeout - DEADLINE_EXCEEDED = 4; - - // Some requested entity (e.g., file or directory) was not found. - // - // Note to server developers: if a request is denied for an entire class - // of users, such as gradual feature rollout or undocumented whitelist, - // `NOT_FOUND` may be used. If a request is denied for some users within - // a class of users, such as user-based access control, `PERMISSION_DENIED` - // must be used. - // - // HTTP Mapping: 404 Not Found - NOT_FOUND = 5; - - // The entity that a client attempted to create (e.g., file or directory) - // already exists. - // - // HTTP Mapping: 409 Conflict - ALREADY_EXISTS = 6; - - // The caller does not have permission to execute the specified - // operation. `PERMISSION_DENIED` must not be used for rejections - // caused by exhausting some resource (use `RESOURCE_EXHAUSTED` - // instead for those errors). `PERMISSION_DENIED` must not be - // used if the caller can not be identified (use `UNAUTHENTICATED` - // instead for those errors). This error code does not imply the - // request is valid or the requested entity exists or satisfies - // other pre-conditions. - // - // HTTP Mapping: 403 Forbidden - PERMISSION_DENIED = 7; - - // The request does not have valid authentication credentials for the - // operation. - // - // HTTP Mapping: 401 Unauthorized - UNAUTHENTICATED = 16; - - // Some resource has been exhausted, perhaps a per-user quota, or - // perhaps the entire file system is out of space. - // - // HTTP Mapping: 429 Too Many Requests - RESOURCE_EXHAUSTED = 8; - - // The operation was rejected because the system is not in a state - // required for the operation's execution. For example, the directory - // to be deleted is non-empty, an rmdir operation is applied to - // a non-directory, etc. - // - // Service implementors can use the following guidelines to decide - // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`: - // (a) Use `UNAVAILABLE` if the client can retry just the failing call. - // (b) Use `ABORTED` if the client should retry at a higher level - // (e.g., when a client-specified test-and-set fails, indicating the - // client should restart a read-modify-write sequence). - // (c) Use `FAILED_PRECONDITION` if the client should not retry until - // the system state has been explicitly fixed. E.g., if an "rmdir" - // fails because the directory is non-empty, `FAILED_PRECONDITION` - // should be returned since the client should not retry unless - // the files are deleted from the directory. - // - // HTTP Mapping: 400 Bad Request - FAILED_PRECONDITION = 9; - - // The operation was aborted, typically due to a concurrency issue such as - // a sequencer check failure or transaction abort. - // - // See the guidelines above for deciding between `FAILED_PRECONDITION`, - // `ABORTED`, and `UNAVAILABLE`. - // - // HTTP Mapping: 409 Conflict - ABORTED = 10; - - // The operation was attempted past the valid range. E.g., seeking or - // reading past end-of-file. - // - // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may - // be fixed if the system state changes. For example, a 32-bit file - // system will generate `INVALID_ARGUMENT` if asked to read at an - // offset that is not in the range [0,2^32-1], but it will generate - // `OUT_OF_RANGE` if asked to read from an offset past the current - // file size. - // - // There is a fair bit of overlap between `FAILED_PRECONDITION` and - // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific - // error) when it applies so that callers who are iterating through - // a space can easily look for an `OUT_OF_RANGE` error to detect when - // they are done. - // - // HTTP Mapping: 400 Bad Request - OUT_OF_RANGE = 11; - - // The operation is not implemented or is not supported/enabled in this - // service. - // - // HTTP Mapping: 501 Not Implemented - UNIMPLEMENTED = 12; - - // Internal errors. This means that some invariants expected by the - // underlying system have been broken. This error code is reserved - // for serious errors. - // - // HTTP Mapping: 500 Internal Server Error - INTERNAL = 13; - - // The service is currently unavailable. This is most likely a - // transient condition, which can be corrected by retrying with - // a backoff. Note that it is not always safe to retry - // non-idempotent operations. - // - // See the guidelines above for deciding between `FAILED_PRECONDITION`, - // `ABORTED`, and `UNAVAILABLE`. - // - // HTTP Mapping: 503 Service Unavailable - UNAVAILABLE = 14; - - // Unrecoverable data loss or corruption. - // - // HTTP Mapping: 500 Internal Server Error - DATA_LOSS = 15; -} \ No newline at end of file diff --git a/lib/src/server/handler.dart b/lib/src/server/handler.dart index ecbebe1e..f28963cc 100644 --- a/lib/src/server/handler.dart +++ b/lib/src/server/handler.dart @@ -170,7 +170,7 @@ class ServerHandler extends ServiceCall { final acceptedEncodings = clientMetadata!['grpc-accept-encoding']?.split(',') ?? []; _callEncodingCodec = acceptedEncodings - .map(_codecRegistry!.lookup) + .map(_codecRegistry.lookup) .firstWhere((c) => c != null, orElse: () => null); } diff --git a/lib/src/shared/codec.dart b/lib/src/shared/codec.dart index 8f771d52..eae77496 100644 --- a/lib/src/shared/codec.dart +++ b/lib/src/shared/codec.dart @@ -13,56 +13,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:archive/archive.dart'; - -abstract class Codec { - /// Returns the message encoding that this compressor uses. - /// - /// This can be values such as "gzip", "deflate", "snappy", etc. - String get encodingName; - - /// Wraps an existing output stream with a compressed output. - List compress(List data); - - /// Wraps an existing output stream with a uncompressed input data. - List decompress(List data); -} - -/// The "identity", or "none" codec. -/// -/// This codec is special in that it can be used to explicitly disable Call -/// compression on a Channel that by default compresses. -class IdentityCodec implements Codec { - const IdentityCodec(); - - @override - final encodingName = 'identity'; - - @override - List compress(List data) { - return data; - } - - @override - List decompress(List data) { - return data; - } -} - -/// A gzip compressor and decompressor. -class GzipCodec implements Codec { - const GzipCodec(); - - @override - final encodingName = 'gzip'; - - @override - List compress(List data) { - return GZipEncoder().encode(data)!; - } - - @override - List decompress(List data) { - return GZipDecoder().decodeBytes(data); - } -} +export 'codec/codec_all.dart'; +export 'codec/codec_io.dart' + if (dart.library.js_interop) 'codec/codec_web.dart'; // package:web implementation diff --git a/lib/src/shared/codec/codec_all.dart b/lib/src/shared/codec/codec_all.dart new file mode 100644 index 00000000..81657eff --- /dev/null +++ b/lib/src/shared/codec/codec_all.dart @@ -0,0 +1,48 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +abstract class Codec { + /// Returns the message encoding that this compressor uses. + /// + /// This can be values such as "gzip", "deflate", "snappy", etc. + String get encodingName; + + /// Wraps an existing output stream with a compressed output. + List compress(List data); + + /// Wraps an existing output stream with a uncompressed input data. + List decompress(List data); +} + +/// The "identity", or "none" codec. +/// +/// This codec is special in that it can be used to explicitly disable Call +/// compression on a Channel that by default compresses. +class IdentityCodec implements Codec { + const IdentityCodec(); + + @override + final encodingName = 'identity'; + + @override + List compress(List data) { + return data; + } + + @override + List decompress(List data) { + return data; + } +} diff --git a/lib/src/shared/codec/codec_io.dart b/lib/src/shared/codec/codec_io.dart new file mode 100644 index 00000000..99bfe783 --- /dev/null +++ b/lib/src/shared/codec/codec_io.dart @@ -0,0 +1,36 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'dart:io'; + +import 'codec_all.dart'; + +/// A gzip compressor and decompressor. +class GzipCodec implements Codec { + const GzipCodec(); + + @override + final encodingName = 'gzip'; + + @override + List compress(List data) { + return gzip.encode(data); + } + + @override + List decompress(List data) { + return gzip.decode(data); + } +} diff --git a/lib/src/shared/codec/codec_web.dart b/lib/src/shared/codec/codec_web.dart new file mode 100644 index 00000000..a3bf59e6 --- /dev/null +++ b/lib/src/shared/codec/codec_web.dart @@ -0,0 +1,34 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'codec_all.dart'; + +/// A gzip compressor and decompressor. +class GzipCodec implements Codec { + const GzipCodec(); + + @override + final encodingName = 'gzip'; + + @override + List compress(List data) { + throw UnsupportedError('Gzip is not supported for grpc web'); + } + + @override + List decompress(List data) { + throw UnsupportedError('Gzip is not supported for grpc web'); + } +} diff --git a/lib/src/shared/message.dart b/lib/src/shared/message.dart index d9d64db1..0533c555 100644 --- a/lib/src/shared/message.dart +++ b/lib/src/shared/message.dart @@ -68,7 +68,8 @@ List frame(List rawPayload, [Codec? codec]) { final payloadLength = compressedPayload.length; final bytes = Uint8List(payloadLength + 5); final header = bytes.buffer.asByteData(0, 5); - header.setUint8(0, codec == null ? 0 : 1); + header.setUint8( + 0, (codec == null || codec.encodingName == 'identity') ? 0 : 1); header.setUint32(1, payloadLength); bytes.setRange(5, bytes.length, compressedPayload); return bytes; diff --git a/lib/src/shared/status.dart b/lib/src/shared/status.dart index 43e7ea8c..cb662326 100644 --- a/lib/src/shared/status.dart +++ b/lib/src/shared/status.dart @@ -13,15 +13,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +// ignore_for_file: prefer_relative_imports + import 'dart:convert'; +import 'package:grpc/src/generated/google/protobuf/any.pb.dart'; +import 'package:grpc/src/generated/google/rpc/error_details.pb.dart'; +import 'package:grpc/src/generated/google/rpc/status.pb.dart'; import 'package:meta/meta.dart'; import 'package:protobuf/protobuf.dart'; -import '../generated/google/protobuf/any.pb.dart'; -import '../generated/google/rpc/code.pbenum.dart'; -import '../generated/google/rpc/error_details.pb.dart'; -import '../generated/google/rpc/status.pb.dart'; import 'io_bits/io_bits.dart' show HttpStatus; class StatusCode { @@ -149,6 +150,28 @@ class StatusCode { static int fromHttpStatus(int status) { return _httpStatusToGrpcStatus[status] ?? StatusCode.unknown; } + + /// Creates a string from a gRPC status code. + static String? name(int status) => switch (status) { + ok => 'OK', + cancelled => 'CANCELLED', + unknown => 'UNKNOWN', + invalidArgument => 'INVALID_ARGUMENT', + deadlineExceeded => 'DEADLINE_EXCEEDED', + notFound => 'NOT_FOUND', + alreadyExists => 'ALREADY_EXISTS', + permissionDenied => 'PERMISSION_DENIED', + resourceExhausted => 'RESOURCE_EXHAUSTED', + failedPrecondition => 'FAILED_PRECONDITION', + aborted => 'ABORTED', + outOfRange => 'OUT_OF_RANGE', + unimplemented => 'UNIMPLEMENTED', + internal => 'INTERNAL', + unavailable => 'UNAVAILABLE', + dataLoss => 'DATA_LOSS', + unauthenticated => 'UNAUTHENTICATED', + int() => null, + }; } class GrpcError implements Exception { @@ -306,7 +329,8 @@ class GrpcError implements Exception { code = StatusCode.unauthenticated; /// Given a status code, return the name - String get codeName => (Code.valueOf(code) ?? Code.UNKNOWN).name; + String get codeName => + StatusCode.name(code) ?? StatusCode.name(StatusCode.unknown)!; @override bool operator ==(other) { @@ -328,6 +352,7 @@ class GrpcError implements Exception { /// This list comes from `error_details.proto`. If any new error detail types are /// added to the protbuf definition, this function should be updated accordingly to /// support them. +@visibleForTesting GeneratedMessage parseErrorDetailsFromAny(Any any) { switch (any.typeUrl) { case 'type.googleapis.com/google.rpc.RetryInfo': @@ -449,7 +474,7 @@ GrpcError? grpcErrorDetailsFromTrailers(Map trailers) { } Map toCustomTrailers(Map trailers) { - return Map.from(trailers) + return Map.of(trailers) ..remove(':status') ..remove('content-type') ..remove('grpc-status') diff --git a/pubspec.yaml b/pubspec.yaml index 48013199..d73273b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,11 @@ name: grpc description: Dart implementation of gRPC, a high performance, open-source universal RPC framework. -version: 3.2.4 +version: 4.0.2 + +repository: https://github.com/open-runtime/grpc-dart -repository: https://github.com/grpc/grpc-dart environment: - sdk: '>=3.3.0 <4.5.0' + sdk: ^3.5.0 dependencies: archive: ^3.4.10 @@ -21,15 +22,20 @@ dependencies: dev_dependencies: build_runner: ^2.4.9 build_test: ^2.2.2 - lints: ">=2.0.0 <4.0.0" + lints: ^5.0.0 mockito: ^5.4.4 path: ^1.9.0 test: ^1.25.3 stream_channel: ^2.1.2 stream_transform: ^2.1.0 - vm_service: ">=11.6.0 <15.0.0" + vm_service: ">=11.6.0 <16.0.0" fake_async: ^1.3.1 false_secrets: - interop/server1.key - test/data/localhost.key + +topics: + - grpc + - rpc + - protocols diff --git a/test/client_certificate_test.dart b/test/client_certificate_test.dart index 238b932e..70bb2b98 100644 --- a/test/client_certificate_test.dart +++ b/test/client_certificate_test.dart @@ -1,6 +1,23 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + // TODO(dartbug.com/26057) currently Mac OS X seems to have some issues with // client certificates so we disable the test. @TestOn('vm && !mac-os') +library; + import 'dart:async'; import 'dart:io'; diff --git a/test/client_handles_bad_connections_test.dart b/test/client_handles_bad_connections_test.dart index 986a7508..75114d7d 100644 --- a/test/client_handles_bad_connections_test.dart +++ b/test/client_handles_bad_connections_test.dart @@ -1,4 +1,21 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + @TestOn('vm') +library; + import 'dart:async'; import 'package:grpc/grpc.dart' as grpc; diff --git a/test/client_tests/call_test.dart b/test/client_tests/call_test.dart index ceff8880..9ce832b5 100644 --- a/test/client_tests/call_test.dart +++ b/test/client_tests/call_test.dart @@ -1,7 +1,38 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:grpc/grpc.dart'; import 'package:grpc/src/client/call.dart'; import 'package:test/test.dart'; +import '../src/client_utils.dart'; + void main() { + const dummyValue = 0; + const cancelDurationMillis = 300; + + late ClientHarness harness; + + setUp(() { + harness = ClientHarness()..setUp(); + }); + + tearDown(() { + harness.tearDown(); + }); + test('WebCallOptions mergeWith CallOptions returns WebCallOptions', () { final options = WebCallOptions(bypassCorsPreflight: true, withCredentials: true); @@ -13,4 +44,57 @@ void main() { expect(mergedOptions.bypassCorsPreflight, true); expect(mergedOptions.withCredentials, true); }); + + test( + 'Cancelling a call correctly complete headers future', + () async { + final clientCall = harness.client.unary(dummyValue); + + Future.delayed( + Duration(milliseconds: cancelDurationMillis), + ).then((_) => clientCall.cancel()); + + expect(await clientCall.headers, isEmpty); + + await expectLater( + clientCall, + throwsA( + isA().having( + (e) => e.codeName, + 'Test codename', + contains('CANCELLED'), + ), + ), + ); + }, + ); + + test( + 'Cancelling a call correctly complete trailers futures', + () async { + final clientCall = harness.client.unary(dummyValue); + + Future.delayed( + Duration(milliseconds: cancelDurationMillis), + ).then((_) { + clientCall.cancel(); + }); + + expect( + await clientCall.trailers, + isEmpty, + ); + + await expectLater( + clientCall, + throwsA( + isA().having( + (e) => e.codeName, + 'Test codename', + contains('CANCELLED'), + ), + ), + ); + }, + ); } diff --git a/test/client_tests/client_interceptor_test.dart b/test/client_tests/client_interceptor_test.dart index d9132fbe..b1763d5c 100644 --- a/test/client_tests/client_interceptor_test.dart +++ b/test/client_tests/client_interceptor_test.dart @@ -1,3 +1,18 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import 'dart:async'; import 'package:grpc/grpc.dart'; diff --git a/test/client_tests/client_keepalive_manager_test.dart b/test/client_tests/client_keepalive_manager_test.dart index 74889d5e..c260dc91 100644 --- a/test/client_tests/client_keepalive_manager_test.dart +++ b/test/client_tests/client_keepalive_manager_test.dart @@ -1,3 +1,18 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import 'package:fake_async/fake_async.dart'; import 'package:grpc/src/client/client_keepalive.dart'; import 'package:mockito/annotations.dart'; diff --git a/test/client_tests/client_keepalive_manager_test.mocks.dart b/test/client_tests/client_keepalive_manager_test.mocks.dart index f03f3cb6..a5a4061f 100644 --- a/test/client_tests/client_keepalive_manager_test.mocks.dart +++ b/test/client_tests/client_keepalive_manager_test.mocks.dart @@ -1,4 +1,20 @@ // Mocks generated by Mockito 5.4.4 from annotations +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Mocks generated by Mockito 5.4.1 from annotations // in grpc/test/client_tests/client_keepalive_manager_test.dart. // Do not manually edit this file. diff --git a/test/client_tests/client_xhr_transport_test.dart b/test/client_tests/client_xhr_transport_test.dart index 4b3f8587..1c58077f 100644 --- a/test/client_tests/client_xhr_transport_test.dart +++ b/test/client_tests/client_xhr_transport_test.dart @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. @TestOn('browser') +library; import 'dart:async'; import 'dart:html'; @@ -64,7 +65,7 @@ class MockHttpRequest extends Mock implements HttpRequest { class MockXhrClientConnection extends XhrClientConnection { MockXhrClientConnection({int? code}) : _statusCode = code ?? 200, - super(Uri.parse('test:8080')); + super(Uri.parse('test:0')); late MockHttpRequest latestRequest; final int _statusCode; diff --git a/test/client_tests/grpc_or_grpcweb_channel_grpc_test.dart b/test/client_tests/grpc_or_grpcweb_channel_grpc_test.dart index bffd8ba8..a2b6e4a2 100644 --- a/test/client_tests/grpc_or_grpcweb_channel_grpc_test.dart +++ b/test/client_tests/grpc_or_grpcweb_channel_grpc_test.dart @@ -13,12 +13,14 @@ // See the License for the specific language governing permissions and // limitations under the License. @TestOn('!browser') +library; + import 'package:grpc/grpc.dart'; import 'package:grpc/grpc_or_grpcweb.dart'; import 'package:test/test.dart'; const host = 'example.com'; -const port = 8080; +const port = 0; void main() { test('Channel on non-web uses gRPC ClientChannel with correct params', () { diff --git a/test/client_tests/grpc_or_grpcweb_channel_web_test.dart b/test/client_tests/grpc_or_grpcweb_channel_web_test.dart index 77fbe70d..8382219e 100644 --- a/test/client_tests/grpc_or_grpcweb_channel_web_test.dart +++ b/test/client_tests/grpc_or_grpcweb_channel_web_test.dart @@ -13,13 +13,14 @@ // See the License for the specific language governing permissions and // limitations under the License. @TestOn('browser') +library; import 'package:grpc/grpc_or_grpcweb.dart'; import 'package:grpc/grpc_web.dart'; import 'package:test/test.dart'; const host = 'example.com'; -const port = 8080; +const port = 0; void main() { test('Channel on web uses GrpcWebClientChannel with correct URI', () { diff --git a/test/connection_server_test.dart b/test/connection_server_test.dart index 4ced9bf9..423b5826 100644 --- a/test/connection_server_test.dart +++ b/test/connection_server_test.dart @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. @TestOn('vm') +library; import 'dart:async'; diff --git a/test/grpc_compression_flag_test.dart b/test/grpc_compression_flag_test.dart new file mode 100644 index 00000000..a41b6995 --- /dev/null +++ b/test/grpc_compression_flag_test.dart @@ -0,0 +1,44 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@TestOn('vm') +library; + +import 'package:grpc/src/shared/codec.dart'; +import 'package:grpc/src/shared/message.dart'; +import 'package:test/test.dart'; + +void main() { + group('GRPC Compression Flag', () { + test('compression flag 0 with null codec', () { + final rawPayload = [1, 2, 3, 4]; + final Codec? codec = null; + final data = frame(rawPayload, codec); + expect(data[0], 0); + }); + test('compression flag 0 with grpc-encoding identity', () { + final rawPayload = [1, 2, 3, 4]; + final Codec codec = IdentityCodec(); + final data = frame(rawPayload, codec); + expect(data[0], 0); + }); + test('compression flag 1 with grpc-encoding gzip', () { + final rawPayload = [1, 2, 3, 4]; + final Codec codec = GzipCodec(); + final data = frame(rawPayload, codec); + expect(data[0], 1); + }); + }); +} diff --git a/test/grpc_web_decoding_test.dart b/test/grpc_web_decoding_test.dart index aa0f3905..96d985aa 100644 --- a/test/grpc_web_decoding_test.dart +++ b/test/grpc_web_decoding_test.dart @@ -1,3 +1,18 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import 'dart:async'; import 'dart:typed_data'; diff --git a/test/grpc_web_server.dart b/test/grpc_web_server.dart index 8f875553..57d80b65 100644 --- a/test/grpc_web_server.dart +++ b/test/grpc_web_server.dart @@ -1,3 +1,18 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import 'dart:async'; import 'dart:convert'; import 'dart:io'; diff --git a/test/grpc_web_test.dart b/test/grpc_web_test.dart index ad58181b..dd5fccb6 100644 --- a/test/grpc_web_test.dart +++ b/test/grpc_web_test.dart @@ -1,4 +1,21 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + @TestOn('browser') +library; + import 'dart:async'; import 'dart:math' as math; diff --git a/test/keepalive_test.dart b/test/keepalive_test.dart index e4ddf2d1..bf48f9b5 100644 --- a/test/keepalive_test.dart +++ b/test/keepalive_test.dart @@ -1,4 +1,21 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + @TestOn('vm') +library; + import 'dart:async'; import 'package:grpc/grpc.dart'; @@ -15,16 +32,20 @@ void main() { late EchoServiceClient fakeClient; late FakeClientChannel fakeChannel; late EchoServiceClient unresponsiveClient; - late ClientChannel unresponsiveChannel; + late FakeClientChannel unresponsiveChannel; + + final pingInterval = Duration(milliseconds: 10); + final timeout = Duration(milliseconds: 30); + final maxBadPings = 5; setUp(() async { final serverOptions = ServerKeepAliveOptions( - maxBadPings: 5, + maxBadPings: maxBadPings, minIntervalBetweenPingsWithoutData: Duration(milliseconds: 10), ); final clientOptions = ClientKeepAliveOptions( - pingInterval: Duration(milliseconds: 10), - timeout: Duration(milliseconds: 30), + pingInterval: pingInterval, + timeout: timeout, permitWithoutCalls: true, ); @@ -32,7 +53,7 @@ void main() { services: [FakeEchoService()], keepAliveOptions: serverOptions, ); - await server.serve(address: 'localhost', port: 8081); + await server.serve(address: 'localhost', port: 0); fakeChannel = FakeClientChannel( 'localhost', port: server.port!, @@ -62,7 +83,7 @@ void main() { test('Server terminates connection after too many pings without data', () async { await fakeClient.echo(EchoRequest()); - await Future.delayed(Duration(milliseconds: 300)); + await Future.delayed(timeout * maxBadPings * 2); await fakeClient.echo(EchoRequest()); // Check that the server closed the connection, the next request then has // to build a new one. @@ -71,23 +92,27 @@ void main() { test('Server doesnt terminate connection after pings, as data is sent', () async { - final timer = Timer.periodic( - Duration(milliseconds: 10), (timer) => fakeClient.echo(EchoRequest())); - await Future.delayed(Duration(milliseconds: 200), () => timer.cancel()); - - // Wait for last request to be sent - await Future.delayed(Duration(milliseconds: 20)); + for (var i = 0; i < 10; i++) { + await fakeClient.echo(EchoRequest()); + await Future.delayed(timeout * 0.2); + } // Check that the server never closed the connection expect(fakeChannel.newConnectionCounter, 1); }); - test('Server doesnt ack the ping, making the client shutdown the connection', + test('Server doesnt ack the ping, making the client shutdown the transport', () async { + //Send a first request, get a connection + await unresponsiveClient.echo(EchoRequest()); + expect(unresponsiveChannel.newConnectionCounter, 1); + + //Ping is not being acked on time + await Future.delayed(timeout * 2); + + //A second request gets a new connection await unresponsiveClient.echo(EchoRequest()); - await Future.delayed(Duration(milliseconds: 200)); - await expectLater( - unresponsiveClient.echo(EchoRequest()), throwsA(isA())); + expect(unresponsiveChannel.newConnectionCounter, 2); }); } @@ -96,7 +121,7 @@ class FakeClientChannel extends ClientChannel { FakeHttp2ClientConnection? fakeHttp2ClientConnection; FakeClientChannel( super.host, { - super.port = 443, + super.port, super.options = const ChannelOptions(), super.channelShutdownHandler, }); @@ -125,20 +150,23 @@ class FakeHttp2ClientConnection extends Http2ClientConnection { } /// A wrapper around a [FakeHttp2ClientConnection] -class UnresponsiveClientChannel extends ClientChannel { +class UnresponsiveClientChannel extends FakeClientChannel { UnresponsiveClientChannel( super.host, { - super.port = 443, + super.port, super.options = const ChannelOptions(), super.channelShutdownHandler, }); @override - ClientConnection createConnection() => - UnresponsiveHttp2ClientConnection(host, port, options); + ClientConnection createConnection() { + fakeHttp2ClientConnection = + UnresponsiveHttp2ClientConnection(host, port, options); + return fakeHttp2ClientConnection!; + } } -class UnresponsiveHttp2ClientConnection extends Http2ClientConnection { +class UnresponsiveHttp2ClientConnection extends FakeHttp2ClientConnection { UnresponsiveHttp2ClientConnection(super.host, super.port, super.options); @override @@ -172,8 +200,6 @@ class FakeEchoService extends EchoServiceBase { @override Stream serverStreamingEcho( - ServiceCall call, ServerStreamingEchoRequest request) { - // TODO: implement serverStreamingEcho - throw UnimplementedError(); - } + ServiceCall call, ServerStreamingEchoRequest request) => + throw UnsupportedError('Not used in this test'); } diff --git a/test/options_test.dart b/test/options_test.dart index c61d82fe..edb3a243 100644 --- a/test/options_test.dart +++ b/test/options_test.dart @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. @TestOn('vm') +library; import 'dart:io'; diff --git a/test/proxy_secure_test.dart b/test/proxy_secure_test.dart index 24f6ff82..806913b2 100644 --- a/test/proxy_secure_test.dart +++ b/test/proxy_secure_test.dart @@ -1,4 +1,21 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + @TestOn('vm') +library; + import 'dart:async'; import 'dart:io'; @@ -16,13 +33,13 @@ void main() { server = Server.create(services: [FakeEchoService()]); await server.serve( address: 'localhost', - port: 8888, + port: 0, security: ServerTlsCredentials( certificate: File('test/data/localhost.crt').readAsBytesSync(), privateKey: File('test/data/localhost.key').readAsBytesSync(), ), ); - final proxy = Proxy(host: 'localhost', port: 8080); + final proxy = Proxy(host: 'localhost', port: 0); final proxyCAName = '/CN=mitmproxy/O=mitmproxy'; fakeChannel = ClientChannel( diff --git a/test/proxy_test.dart b/test/proxy_test.dart index a4f9bcf9..6fc30720 100644 --- a/test/proxy_test.dart +++ b/test/proxy_test.dart @@ -1,4 +1,21 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + @TestOn('vm') +library; + import 'dart:async'; import 'package:grpc/grpc.dart'; @@ -13,9 +30,9 @@ void main() { setUp(() async { server = Server.create(services: [FakeEchoService()]); - await server.serve(address: 'localhost', port: 8888); + await server.serve(address: 'localhost', port: 0); - final proxy = Proxy(host: 'localhost', port: 8080); + final proxy = Proxy(host: 'localhost', port: 0); fakeChannel = ClientChannel( 'localhost', diff --git a/test/round_trip_test.dart b/test/round_trip_test.dart index 4a452248..5d07fc89 100644 --- a/test/round_trip_test.dart +++ b/test/round_trip_test.dart @@ -1,4 +1,21 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + @TestOn('vm') +library; + import 'dart:async'; import 'dart:io'; diff --git a/test/server_cancellation_test.dart b/test/server_cancellation_test.dart index ff4cd855..ab119bb2 100644 --- a/test/server_cancellation_test.dart +++ b/test/server_cancellation_test.dart @@ -14,6 +14,8 @@ // limitations under the License. @TestOn('vm') +library; + import 'package:grpc/grpc.dart'; import 'package:test/test.dart'; @@ -45,7 +47,7 @@ void main() { server = Server.create( services: [EchoService()], ); - await server.serve(address: 'localhost', port: 8081); + await server.serve(address: 'localhost', port: 0); channel = ClientChannel( 'localhost', port: server.port!, diff --git a/test/server_handles_broken_connection_test.dart b/test/server_handles_broken_connection_test.dart index cd6d1fd6..c59c9dad 100644 --- a/test/server_handles_broken_connection_test.dart +++ b/test/server_handles_broken_connection_test.dart @@ -1,4 +1,21 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + @TestOn('vm') +library; + import 'dart:async'; import 'dart:io'; import 'dart:isolate'; diff --git a/test/server_keepalive_manager_test.dart b/test/server_keepalive_manager_test.dart index 230fc3b7..94ab257c 100644 --- a/test/server_keepalive_manager_test.dart +++ b/test/server_keepalive_manager_test.dart @@ -1,3 +1,18 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import 'dart:async'; import 'package:fake_async/fake_async.dart'; diff --git a/test/server_test.dart b/test/server_test.dart index 2de494d3..fc1b9835 100644 --- a/test/server_test.dart +++ b/test/server_test.dart @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. @TestOn('vm') +library; import 'dart:async'; diff --git a/test/shared_tests/codec_registry_test.dart b/test/shared_tests/codec_registry_test.dart index 5a5483bd..55de10ee 100644 --- a/test/shared_tests/codec_registry_test.dart +++ b/test/shared_tests/codec_registry_test.dart @@ -1,3 +1,18 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import 'package:grpc/src/shared/codec.dart'; import 'package:grpc/src/shared/codec_registry.dart'; import 'package:test/test.dart'; diff --git a/test/timeline_test.dart b/test/timeline_test.dart index c4291980..1866ea5c 100644 --- a/test/timeline_test.dart +++ b/test/timeline_test.dart @@ -16,6 +16,8 @@ @TestOn('vm') @Skip( 'Run only as `dart run --enable-vm-service --timeline-streams=Dart test/timeline_test.dart`') +library; + import 'dart:async'; import 'dart:developer' as dev; diff --git a/test/tools/http2_client.dart b/test/tools/http2_client.dart index 46b8b42a..9bd40045 100644 --- a/test/tools/http2_client.dart +++ b/test/tools/http2_client.dart @@ -1,3 +1,18 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import 'dart:convert'; import 'package:grpc/grpc.dart'; @@ -5,7 +20,7 @@ import 'package:grpc/src/client/http2_connection.dart'; import 'package:http2/http2.dart'; Future main(List args) async { - final serverPort = 5678; + final serverPort = 0; final proxyPort = int.tryParse(args.first); final proxy = @@ -22,7 +37,7 @@ Future main(List args) async { final incoming = proxy == null ? connector.socket : await connector.connectToProxy(proxy); - final uri = Uri.parse('http://localhost:8080'); + final uri = Uri.parse('http://localhost:0'); final transport = ClientTransportConnection.viaStreams(incoming, connector.socket); diff --git a/test/tools/http2_server.dart b/test/tools/http2_server.dart index 4ab5eb26..721773d1 100644 --- a/test/tools/http2_server.dart +++ b/test/tools/http2_server.dart @@ -1,3 +1,18 @@ +// Copyright (c) 2024, the gRPC project authors. Please see the AUTHORS file +// for details. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import 'dart:convert'; import 'dart:io';