HTTP

This module defines the ServiceTalk client and server API for the HTTP/1.x and HTTP/2 protocols. This module supports all the different Programming Paradigms for client and server. Here is a quick start example of the blocking and aggregated paradigm:

Blocking Client

try (BlockingHttpClient client = HttpClients.forSingleAddress("localhost", 8080).buildBlocking()) {
  HttpResponse response = client.request(client.get("/sayHello"));
  // use the response
}

Blocking Server

HttpServers.forPort(8080)
  .listenBlockingAndAwait((ctx, request, responseFactory) ->
    responseFactory.ok().payloadBody("Hello World!", textSerializer()))
  .awaitShutdown();

Extensibility and Filters

The design of this protocol involves configuring builders for core protocol concerns, and then appending Filters for extensibility. Filters are described in more detail below (e.g. Service Filters, Client Filters) but in general they facilitate user code to filter/intercept/modify the request/response processing. Filters can be used for cross-cutting concerns such as authentication, authorization, logging, metrics, tracing, etc…​

Server

The server side is built around the concept of Service. A Service is where your business logic lives. ServiceTalk’s HTTP module will interact with a single service which is provided by the user via HttpServers. The flow of data from the socket to the HTTP Service is visualized as follows:

+--------+ request  +---------+       +---------+
|        |--------->|  HTTP   |------>|  HTTP   |
| Socket |          | Decoder |       | Service |
|        |<---------| Encoder |<------|         |
+--------+ response +---------+       +---------+

Each Service has access to a HttpServiceContext which provides additional context (via ConnectionContext) into the Connection/transport details for each request/response. This means that the HttpService may be invoked for multiple connections, from different threads, and even concurrently.

Service Filters

Filters provide a means to filter/intercept and modify each request/response life cycle. Service Filters are used to implement tracing metrics, logging, basic auth, and any other extension that needs request/response level visibility. The diagram below describes the control flow as related to Service filters:

+--------+ request  +---------+       +---------+       +---------+       +---------+
|        |--------->|  HTTP   |------>|  HTTP   |------>|  HTTP   |------>|  HTTP   |
| Socket |          | Decoder |       | Service |       | Service |       | Service |
|        |<---------| Encoder |<------| Filter 1|<------| Filter n|<------|         |
+--------+ response +---------+       +---------+       +---------+       +---------+

To implement a Service filter you should implement the Service Filter Factory and append it on the HttpServerBuilder via HttpServerBuilder#appendServiceFilter(..).

Currently we only support writing Filters for the Asynchronous and Streaming programming paradigm. An Asynchronous and Streaming filter can be used with a Service in any other programming paradigm.

Routers

In practice it is common for a HTTP Service to handle many different types of request(s) that all have unique processing requirements. The control flow in ServiceTalk is represented by a "Router". A "Router" is a Service that owns the responsibility of multiplexing the control flow. ServiceTalk does not mandate a specific "Router" implementation but provides a couple reference implementations for common use cases (e.g. Predicate Router and JAX-RS via Jersey). The general component diagram of a "Router" is as follows:

                                                                +---------+
                                                        /------>| Route 1 |
                                                        |       +---------+
+--------+ request  +---------+       +---------+       |
|        |--------->|  HTTP   |------>|  HTTP   |       |       +---------+
| Socket |          | Decoder |       | Service |<------+------>| Route 2 |
|        |<---------| Encoder |<------| Router  |       |       +---------+
+--------+ response +---------+       +---------+       |
                                                        |       +---------+
                                                        \------>| Route n |
                                                                +---------+

Client

A Client is generally responsible for managing multiple Connections. There are a few flavors of HTTP Clients:

SingleAddress Client

This Client will connect to a single unresolved address, that is provided while creating the client. The unresolved address is resolved via an asynchronous DNS resolver by default (see Service Discovery). This Client is for use cases where you want to issue requests to a single service (that may have multiple instances).

MultiAddress Client

This Client parses the request-target to determine the remote address for each request. This Client simulates a browser type of use case.

Each of the above Clients can be created via the HttpClients static factory.

Connections

The Client manages multiple Connections via a LoadBalancer. The control flow of a request/response can be visualized in the below diagram:

                                             +--------------+     +----------------------+     +--------+
                                        /--->| Connection 1 |<--->| HTTP Decoder/Encoder |<--->| Socket |
                                        |    +--------------+     +----------------------+     +--------+
+--------+ request  +--------------+    |
|        |--------->|              |    |    +--------------+     +----------------------+     +--------+
| Client |          | LoadBalancer |<---+--->| Connection 2 |<--->| HTTP Decoder/Encoder |<--->| Socket |
|        |<---------|              |    |    +--------------+     +----------------------+     +--------+
+--------+ response +--------------+    |
                                        |    +--------------+     +----------------------+     +--------+
                                        \--->| Connection x |<--->| HTTP Decoder/Encoder |<--->| Socket |
                                             +--------------+     +----------------------+     +--------+

The LoadBalancer is consulted for each request to determine which connection should be used.

Client Filters

Filters provide a means to filter/intercept and modify each request/response life cycle. Client Filters are used to implement tracing metrics, logging, authorization, and any other extension that needs request/response level visibility.

                                                                             +--------------+     +----------------------+     +--------+
                                                                        /--->| Connection 1 |<--->| HTTP Decoder/Encoder |<--->| Socket |
                                                                        |    +--------------+     +----------------------+     +--------+
+--------+ request  +---------+     +---------+     +--------------+    |
|        |--------->|         |---->|         |---->|              |    |    +--------------+     +----------------------+     +--------+
| Client |          | Client  |     | Client  |     | LoadBalancer |<---+--->| Connection 2 |<--->| HTTP Decoder/Encoder |<--->| Socket |
|        |<---------| Filter 1|<----| Filter n|<----|              |    |    +--------------+     +----------------------+     +--------+
+--------+ response +---------+     +---------+     +--------------+    |
                                                                        |    +--------------+     +----------------------+     +--------+
                                                                        \--->| Connection x |<--->| HTTP Decoder/Encoder |<--->| Socket |
                                                                             +--------------+     +----------------------+     +--------+

To implement a Client filter you should implement the Client Filter Factory and append it on the HttpClientBuilder via HttpClientBuilder#appendClientFilter(..).

Currently we only support writing Filters for the Asynchronous and Streaming programming paradigm. An Asynchronous and Streaming filter can be used with a Client in any other programming paradigm.

Connection Filters

The Client doesn’t have visibility into Connection specific information. For example, the Connection layer knows about transport details such as connected remote address and other elements in the ConnectionContext. If you have use cases that require this information in the request/response control flow you can use a Connection Filter. The diagram below illustrates how the Connection Filter interacts with the request/response control flow.

                                             +---------------------+     +---------------------+     +--------------+     +----------------------+     +--------+
                                        /--->| Connection Filter 1 |<--->| Connection Filter n |<--->| Connection 1 |<--->| HTTP Decoder/Encoder |<--->| Socket |
                                        |    +---------------------+     +---------------------+     +--------------+     +----------------------+     +--------+
+--------+ request  +--------------+    |
|        |--------->|              |    |    +---------------------+     +---------------------+     +--------------+     +----------------------+     +--------+
| Client |          | LoadBalancer |<---+--->| Connection Filter 1 |<--->| Connection Filter n |<--->| Connection 2 |<--->| HTTP Decoder/Encoder |<--->| Socket |
|        |<---------|              |    |    +---------------------+     +---------------------+     +--------------+     +----------------------+     +--------+
+--------+ response +--------------+    |
                                        |    +---------------------+     +---------------------+     +--------------+     +----------------------+     +--------+
                                        \--->| Connection Filter 1 |<--->| Connection Filter n |<--->| Connection x |<--->| HTTP Decoder/Encoder |<--->| Socket |
                                             +---------------------+     +---------------------+     +--------------+     +----------------------+     +--------+

Using HTTP/2 transport

ServiceTalk supports both HTTP/1.x and HTTP/2 protocols.

HTTP/2 provides many benefits over HTTP/1.1 ranging from improving performance (eg: multiplexing, binary framing) to adding new features (eg: server push, request prioritization). ServiceTalk intends to provide all these features to users eventually, but for a majority of cases, performance benefits of HTTP/2 are more beneficial than the effort required to use the new features. As an interim measure, ServiceTalk provides an option for users to use HTTP/2 as the underlying transport for HTTP clients/servers while using the same API as HTTP/1.1. This makes it easy for users to leverage HTTP/2 performance benefits with minimal code change. We intend to make HTTP/2 specific features available in ServiceTalk eventually.

For the cleartext TCP connections, users have to configure the desired protocol version upfront, using builder methods. For secure TLS connections, a protocol version must be negotiated using ALPN extension.

For more information about how to configure different HTTP protocol versions see these examples.

Serialization

Serialization factories are made available in the HttpSerializationProviders static factory class.

The core abstractions HttpDeserializer and HttpSerializer are designed to be coupled to a specific Java type T and accessed via a HttpSerializationProvider. The HttpDeserializer and HttpSerializer are also designed to handle the HTTP headers data behind the scenes. This means either checking if content-type format is compatible with the deserialization format and also adding a content-type header identifying the resulting serialization format.

For more information about how to use serializers and deserializers see these examples.

Expect: 100-continue

HTTP protocol defines Expect header with 100-continue expectation. This feature allows users to defer sending a request payload body until after the server responds with 100 Continue status code signaling that it’s ready to receive and process the payload body.

Aggregated client will automatically de-optimize its flush strategy to flush on each element of the request to force flushing of the request meta-data and deffer flushing payload body until after a 100 Continue response is received.

Server-side users should subscribe to the request payload body when they are ready to consume it. It will generate 100 Continue interim response. After the request is consumed, service should produce a final response with an appropriate status code. If service decides to reject a request before consuming a request payload body, an appropriate 4xx status code, like 417 Expectation Failed, should be returned. Put meaningful tracing headers in the final response.

Aggregated service will automatically subscribe to the request payload body before invoking the handle method, that will send 100 Continue response back to the client. For manual control consider migrating to streaming API or implement a filter to accept or reject requests before processing their payload body.
The interim response is not propagated as a response returned by the client because the client has to wait for a final status code returned by the server. There are a couple of other ways how to get visibility into interim response: using wire-logging for HTTP/1.x or frame-logging for HTTP/2 (see DebuggingExampleClient as an example) or by monitoring when WriteObserver signals that more items are requested to write.