HTTP requests and streams

Streams are first-class citizens in Envoy Mobile, and are supported out-of-the-box.

Unary requests (single request / single response) are also supported using the same interfaces.

Quick start

Below are some quick examples for getting started with HTTP streams. See the individual class references in the later sections of this page for in-depth information on how each type is used.

Start and interact with an HTTP stream in Kotlin:

val streamClient = AndroidStreamClientBuilder(application).build()

val headers = RequestHeadersBuilder(method = RequestMethod.POST, scheme = "https",  authority = "api.envoyproxy.io", path = "/foo")
  .build()
val stream = streamClient
  .newStreamPrototype()
  .setOnResponseHeaders { headers, endStream ->
    Log.d("MainActivity", "[${headers.httpStatus}] Headers received: $headers, end stream: $endStream")
  }
  .setOnResponseData { data, endStream ->
    Log.d("MainActivity", "Received data, end stream: $endStream")
  }
  .setOnResponseTrailers { trailers ->
    Log.d("MainActivity", "Trailers received: $trailers")
  }
  .setOnError { ... }
  .setOnCancel { ... }
  .start(Executors.newSingleThreadExecutor())
  .sendHeaders(...)
  .sendData(...)

...
stream.close(...)

Start and interact with an HTTP stream in Swift:

let headers = RequestHeadersBuilder(method: .post, scheme: "https", authority: "api.envoyproxy.io", path: "/foo")
  .build()

let streamClient = try StreamClientBuilder().build()
let stream = streamClient
  .newStreamPrototype()
  .setOnResponseHeaders { headers, endStream in
    print("[\(headers.httpStatus)] Headers received: \(headers), end stream: \(endStream)")
  }
  .setOnResponseData { data, endStream in
    print("Received data, end stream: \(endStream)")
  }
  .setOnResponseTrailers { trailers in
    print("Trailers received: \(trailers)")
  }
  .setOnError { ... }
  .setOnCancel { ... }
  .start(queue: .main)
  .sendHeaders()
  .sendData(...)

...
stream.close(...)

RequestHeaders

Creating a stream is done by initializing a RequestHeaders instance via a RequestHeadersBuilder, then passing it to a previously created StreamClient instance.

Kotlin:

val headers = RequestHeadersBuilder(RequestMethod.POST, "https", "api.envoyproxy.io", "/foo")
  .addRetryPolicy(RetryPolicy(...))
  .addUpstreamHttpProtocol(UpstreamRequestProtocol.HTTP2)
  .add("x-custom-header", "foobar")
  ...
  .build()

Swift:

let headers = RequestHeadersBuilder(method: .post, scheme: "https", authority: "api.envoyproxy.io", path: "/foo")
  .addRetryPolicy(RetryPolicy(...))
  .addUpstreamHttpProtocol(.http2)
  .add(name: "x-custom-header", value: "foobar")
  ...
  .build()

StreamPrototype

A StreamPrototype is used to configure streams prior to starting them by assigning callbacks to be invoked when response data is received on the stream.

To create a StreamPrototype, use an instance of StreamClient.

Kotlin:

val prototype = streamClient
  .newStreamPrototype()
  .setOnResponseHeaders { headers, endStream ->
    Log.d("MainActivity", "[${headers.httpStatus}] Headers received: $headers, end stream: $endStream")
  }
  .setOnResponseData { data, endStream ->
    Log.d("MainActivity", "Received data, end stream: $endStream")
  }
  .setOnResponseTrailers { trailers ->
    Log.d("MainActivity", "Trailers received: $trailers")
  }
  .setOnError { ... }
  .setOnCancel { ... }

Swift:

let prototype = streamClient
  .newStreamPrototype()
  .setOnResponseHeaders { headers, endStream in
    print("[\(headers.httpStatus)] Headers received: \(headers), end stream: \(endStream)")
  }
  .setOnResponseData { data, endStream in
    print("Received data, end stream: \(endStream)")
  }
  .setOnResponseTrailers { trailers in
    print("Trailers received: \(trailers)")
  }
  .setOnError { ... }
  .setOnCancel { ... }

RetryPolicy

The RetryPolicy type allows for customizing retry rules that should be applied to an outbound request. These rules are added by calling addRetryPolicy(...) on the RequestHeadersBuilder, and are applied when the request headers are sent.

For full documentation of how these retry rules perform, see Envoy’s documentation:

Stream

Streams are started by calling start() on a StreamPrototype.

Doing so returns a Stream which allows the sender to interact with the stream.

Kotlin:

val streamClient = AndroidStreamClientBuilder()
  ...
  .build()

val requestHeaders = RequestHeadersBuilder()
  ...
  .build()
val prototype = streamClient
  .newStreamPrototype()
  ...
val stream = prototype
  .start(Executors.newSingleThreadExecutor())
  .sendHeaders(...)
  .sendData(...)

...
stream.close(...)

Swift:

let streamClient = StreamClientBuilder()
  ...
  .build()

let requestHeaders = RequestHeadersBuilder()
  ...
  .build()
let prototype = streamClient
  .newStreamPrototype()
  ...
let stream = prototype
  .start(queue: .main)
  .sendHeaders(...)
  .sendData(...)

...
stream.close(...)

Unary requests

As mentioned above, unary requests are made using the same types that handle streams.

Sending a unary request is done simply by closing the Stream after the set of headers/data/trailers has been written.

For example:

Kotlin:

val streamClient = AndroidStreamClientBuilder()
  ...
  .build()

val requestHeaders = RequestHeadersBuilder()
  ...
  .build()
val stream = streamClient
  .newStreamPrototype()
  .start(Executors.newSingleThreadExecutor())

// Headers-only
stream.sendHeaders(requestHeaders, true)

// Close with data
stream.close(ByteBuffer(...))

// Close with trailers
stream.close(RequestTrailersBuilder().build())

// Cancel the stream
stream.cancel()

Swift:

let streamClient = StreamClientBuilder()
  ...
  .build()

let requestHeaders = RequestHeadersBuilder()
  ...
  .build()
let stream = streamClient
  .newStreamPrototype()
  .start(queue: .main)

// Headers-only
stream.sendHeaders(requestHeaders, endStream: true)

// Close with data
stream.close(Data(...))

// Close with trailers
stream.close(RequestTrailersBuilder().build())

// Cancel the stream
stream.cancel()