HTTP

This module defines the ServiceTalk client and server API for the HTTP/1.x protocol. 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.

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. The LoadBalancer interface is extensible and the reference implementation provides a Round Robin algorithm.

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 |
                                             +---------------------+     +---------------------+     +--------------+     +----------------------+     +--------+

Service Discovery

Another core component of the Client is the ServiceDiscoverer. The ServiceDiscoverer is responsible for resolving a service address into a set of resolved addresses used to create Connection(s) by the LoadBalancer. The default implementation for HTTP is DNS and will resolve the IP addresses of each service address every TTL seconds. ServiceDiscoverers are typically not invoked in the request/response path and addresses are resolved "out of band", a.k.a in the background.

                     +------------+
                     |   Service  |
                     | Discoverer |
                     +------------+
                           ^
                           |
                           |                 +--------------+
                           |            /--->| Connection 1 |
                           V            |    +--------------+
+--------+ request  +--------------+    |
|        |--------->|              |    |    +--------------+
| Client |          | LoadBalancer |<---+--->| Connection 2 |
|        |<---------|              |    |    +--------------+
+--------+ response +--------------+    |
                                        |    +--------------+
                                        \--->| Connection x |
                                             +--------------+

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.