Skip to main content

Using clients

Configuration

Supported protocols

Connect-Swift currently supports 2 protocols:

  • The new Connect protocol, a simple, POST-only 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. By default, JSON- and binary-encoded Protobuf is supported.
  • The gRPC-Web protocol: Allows clients to communicate with existing gRPC-Web services. If your back-end services are already using gRPC today, Envoy provides support for converting between gRPC and gRPC-Web, enabling you to use gRPC-Web through Connect-Swift without having to change any existing gRPC APIs.

Switching between these protocols is a simple 1-line code change when configuring the ProtocolClient:

let protocolClient = ProtocolClient(
httpClient: URLSessionHTTPClient(),
config: ProtocolClientConfig(
host: "https://demo.connect.build",
networkProtocol: .connect, // Or .grpcWeb
codec: ProtoCodec()
)
)

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

Client config

Examples of built-in config options include gzip compression for requests and responses, support for interceptors, and the ability to switch between JSON and Protobuf binary encodings. The full list of config options can be found in the Connect-Swift repository.

Compression

Request compression and response decompression are provided through compression pools that contain logic for compressing and decompressing data. Gzip support is available by default, and support for additional compression algorithms can be provided by replicating the functionality provided by the GzipCompressionPool and passing it to the ProtocolClientConfig.

HTTP stack

By default, HTTP networking is done using URLSession via the URLSessionHTTPClient wrapper. Its init() accepts a URLSessionConfiguration to use with the underlying session. Furthermore, URLSessionHTTPClient can also be subclassed if desired.

If you wish to write a custom HTTP client, this can be achieved by conforming to the HTTPClientInterface protocol and passing it as the httpClient when initializing a ProtocolClient.

Using generated clients

async/await

Generated clients support Swift's latest async/await APIs, making it easy to use code generated by Connect in async contexts.

Async interfaces are provided by default, and are configurable using the GenerateAsyncMethods option:

import Connect

// ProtocolClient is usually stored and passed to generated clients.
let protocolClient = ProtocolClient(
httpClient: URLSessionHTTPClient(),
config: ProtocolClientConfig(
host: "https://demo.connect.build", // Base URL for APIs
networkProtocol: .connect, // Or .grpcWeb
codec: ProtoCodec() // Or JSONCodec()
)
)
let elizaClient = Eliza_V1_ElizaServiceClient(client: protocolClient)

...

// Performed within an async context.
let request = Eliza_V1_SayRequest.with { $0.sentence = "hello world" }
let response = await elizaClient.say(request: request, headers: [:])
print(response.message?.sentence)

For server-streaming RPCs, the corresponding method on the client returns an *AsyncStream object which allows the caller to send data over the stream and to iterate over updates from the server using the for await...in statement:

let stream = elizaClient.converse(headers: [:])
Task {
for await result in stream.results() {
switch result {
case .headers(let headers):
...
case .message(let message):
...
case .complete(let code, let error, let trailers):
...
}
}
}

let request = ConverseRequest.with { $0.sentence = "hello world" }
try stream.send(request)

Callbacks

If you prefer a callback-based approach, callbacks/closures can be used instead.

These interfaces are not generated by default, but are configurable using the GenerateCallbackMethods option:

import Connect

// ProtocolClient is usually stored and passed to generated clients.
let protocolClient = ProtocolClient(
httpClient: URLSessionHTTPClient(),
config: ProtocolClientConfig(
host: "https://demo.connect.build", // Base URL for APIs
networkProtocol: .connect, // Or .grpcWeb
codec: ProtoCodec() // Or JSONCodec()
)
)
let elizaClient = Eliza_V1_ElizaServiceClient(client: protocolClient)

...

let request = Eliza_V1_SayRequest.with { $0.sentence = "hello world" }
let cancelable = elizaClient.say(request: request, headers: [:]) { response in
print(response.message?.sentence)
}

// cancelable.cancel()

For server-streaming RPCs, the corresponding method on the client accepts an onResult closure which is called each time an update is received from the server, and returns a *Stream object which allows the caller to send data over the stream:

let stream = elizaClient.converse(onResult: { result in
switch result {
case .headers(let headers):
...
case .message(let message):
...
case .complete(let code, let error, let trailers):
...
}
})

let request = ConverseRequest.with { $0.sentence = "hello world" }
try stream.send(request)
...
stream.close()