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 Client
s 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
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 or by monitoring when WriteObserver signals that more items are requested to write. |