Skip to main content

Using clients

Configuration

Supported protocols

Connect-Dart currently supports 3 protocols:

  • The new Connect protocol, a simple, HTTP-based protocol that works over HTTP/1.1 or HTTP/2. It takes the best parts of gRPC/gRPC-Web, including streaming, and packages them into a protocol that works well on all platforms, including mobile and web. JSON- and binary-encoded Protobuf is supported out of the box.
  • The gRPC protocol: Allows clients to communicate with existing gRPC services.
  • The gRPC-Web protocol: Allows clients to communicate with existing gRPC-Web services. The main difference between gRPC and gRPC-Web is that gRPC-Web does not utilize HTTP trailers in the protocol.

If your backend services are already using gRPC today, Envoy provides support for converting requests made using the Connect and gRPC-Web protocols to gRPC, enabling you to use Connect-Swift without the SwiftNIO dependency.

Switching between the Connect and gRPC/gRPC-Web protocols is a simple 2-line change when configuring the Transport:

import 'package:connectrpc/http2.dart';
import 'package:connectrpc/connect.dart';
import 'package:connectrpc/protobuf.dart';
import 'package:connectrpc/protocol/connect.dart' as protocol;
// import 'package:connectrpc/protocol/grpc.dart' as protocol;
// import 'package:connectrpc/protocol/grpc_web.dart' as protocol;

final transport = protocol.Transport(
baseUrl: "https://demo.connectrpc.com",
codec: const ProtoCodec(), // Or JsonCodec()
httpClient: createHttpClient(),
// statusParser: StatusParser(), // This is required for gRPC and gRPC-Web
);

Note that these options are mutually exclusive. If you'd like to use different protocols with different APIs, create one Transport for each protocol.

Compression

Request compression and response decompression can be enabled for io platforms by providing the compression transport option. Gzip compression is provided in package:connectrpc/io.dart, and support for other compression algorithms can be added by implementing the Compression interface.

HTTP stack

Connect-Dart provides three different HTTP client implementations out of the box.

  • dart:io based HTTP/1 client. This supports Connect and gRPC-Web protocols. Full duplex Bidi streaming is not supported. Exported from package:connectrpc/io.dart.
  • dart:js_interop powered and fetch based implementation to use on web platforms. It supports Connect and gRPC-Web protocols, limited to unary and server streaming RPCs. Exported from package:connectrpc/web.dart.
  • http2 package based HTTP/2 client. This supports all three protocols and all four RPC types. This is not available on the web platforms. Exported from package:connectrpc/http2.dart.

All of them export a function called createHttpClient that accepts options for configuring each of them.

Using generated clients

Generated clients take adavantage of Dart's Future and Stream types to provide idiomatic APIs.

import './gen/eliza.pb.dart';
import './gen/eliza.connect.client.dart';

import 'package:connectrpc/http2.dart';
import 'package:connectrpc/connect.dart';
import 'package:connectrpc/protobuf.dart';
import 'package:connectrpc/protocol/connect.dart' as protocol;

final transport = protocol.Transport(
baseUrl: "https://demo.connectrpc.com",
codec: const ProtoCodec(), // Or JsonCodec()
httpClient: createHttpClient(),
);

final elizaClient = ElizaServiceClient(transport);

...

// In an async function
final response = await elizaClient.say(SayRequest(sentence: 'hello, world'));
print(response.message.sentence);

For server-streaming RPCs, the corresponding method on the client returns a Stream object which allows the caller to iterate over updates from the server using the await for:

final stream = elizaClient.introduce(IntroduceRequest());
await for (final next in stream) {
print(next);
}

For client-streaming and bidi-streaming RPCs, the corresponding method on the client accepts a Stream. For bidi it also returns a stream:

final stream = elizaClient.converse(Stream.fromIterable([ConverseRequest()]));
await for (final next in stream) {
print(next);
}

Headers and Trailers

Headers can be sent using the optional headers parameter of client methods:

elizaClient.say(
SayRequest(),
headers: Headers()..['authorization'] = 'Bearer <token>',
);

Response headers and trailers can be accessed using the onHeader and onTrailer parameters:

elizaClient.say(
SayRequest(),
onHeader: (headers) {
print(headers);
},
onTrailer: (trailer) {
print(trailer);
},
);

Timeouts and Cancellation

The methods on the generated client accept an optional signal parameter that can be used to configure timeouts:

final response = await elizaClient.say(
SayRequest(),
signal: TimeoutSignal(Duration(milliseconds: 200)), // Or a DeadlineSignal that accepts a DateTime
);

They can also accept a CancelableSignal that can be cancelled adhoc:

final signal = CancelableSignal();
final stream = elizaClient.introduce(
IntroduceRequest(),
signal: signal,
);
var count = 0;
await for (final next in stream) {
count++;
if (count == 5) {
break;
}
print(next);
}
signal.cancel();