-
Notifications
You must be signed in to change notification settings - Fork 79
Styx 1.0 API
The Styx API as in 0.7 release train was developed for a small internal audience. It was also an experimental "best guess" before any "real" plugins existed.
Since then the audience has expanded and many new plugins have been implemented by different development teams. In the Styx core team we have received feedback and questions. We have made observations. We have identified areas of improvements in the 3rd party API.
Based on these observations we are introducing a new API. It better decouples the API fromStyx implementation aspects. It makes the API safer and more convenient to use. It will be better for 3rd party consumption.
The new API:
-
The new API is no longer pinned to 3rd party libraries like Rx Java. We have removed any 3rd party definitions like
rx.Observale
from the public API. This is a significant improvement because we will be able to change the underlying implementation and libraries without invalidating backwards compatibility. -
The new API is safer. This is because
rx.Observable
is very generic reactive stream abstraction, but Styx has a very specific use case of processing live network data streams. Manyrx.Observable
operations are not useful, and others are outright dangerous in this context. By replacingrx.Observable
with classes more relevant to HTTP proxying, the new 1.0 API only allows safe and meaningful data processing operations. -
Revamped HTTP message class hierarchy. In particular:
-
The old
HttpRequest
andHttpResponse
classes have been renamed toLiveHttpRequest
andLiveHttpResponse
to emphasise that the content body streams through as a sequence of byte buffer events. Rationale: We have witnessed new plugin developers breaking their application by treating "live" HTTP streams as normal Java beans. Often this would lead to a broken content stream and errors further up the plugin pipeline. TheLive
mnemonic in the class name is supposed to attract plugin developer's attention hoping to reduce unintentional mistakes. -
The new
HttpRequest
andHttpResponse
objects are now immutable and "full" message objects containing both HTTP headers and body payload in a single object. They are designed to be far more convenient in other applications like unit tests, admin handlers and pretty much everywhere outside of proxy interceptors.
-
-
New
ByteStream
class represents the body content stream inLiveHttpRequest
andLiveHttpResponse
objects. It is no longer anObservable<ByteBuf>
. -
New
Eventual
class replacesObservable<HttpResponse>
in theHttpInterceptor.intercept
method. -
Removed leaked abstractions from Netty, Guava, etc.
The old HttpRequest
and HttpResponse
classes have been renamed to LiveHttpRequest
and LiveHttpResponse
, respectively.
The LiveHttpRequest
and LiveHttpResponse
appear prominently in HttpInterceptor.intercept
and Chain.proceed
methods. Plugin code must be renamed accordingly.
The Interceptor.intercept
and Chain.proceed
methods now return an Eventual<LiveHttpResponse>
.
Replace any usage of Observable
with new Eventual
type. It supports:
- Creating new
Eventual
objects from value:of
- Creating new
Eventual
objects from a Reactive StreamsPublisher
- Creating new
Eventual
objects from a Java 8CompletionStage
- Creating new
Eventual
that fails with aThrowable
:error
- Synchronously mapping values with
map
- Asynchronously mapping values with
flatMap
- Mapping errors with:
onError
Replace Netty HTTP response status codes with Styx equivalents:
- from:
import static io.netty.handler.codec.http.HttpResponseStatus
- to:
import static com.hotels.styx.api.messages.HttpResponseStatus
Replace Netty HTTP method names with Styx equivalents:
- from:
import io.netty.handler.codec.http.HttpMethod
- to:
import com.hotels.styx.api.messages.HttpMethod
and
- from:
import static io.netty.handler.codec.http.HttpMethod.*
- to:
import static com.hotels.styx.api.messages.HttpMethod.*
Static methods for building HTTP messages used to be part of the Builder class. To reduce line noise we have moved the builders up to the message class itself like so:
- from:
import static com.hotels.styx.api.HttpResponse.Builder.response
- to:
import static com.hotels.styx.api.LiveHttpResponse.response
The relevant builders from the HttpRequest class have also been moved:
- from:
import static com.hotels.styx.api.HttpRequest.Builder.head
import static com.hotels.styx.api.HttpRequest.Builder.post
import static com.hotels.styx.api.HttpRequest.Builder.delete
import static com.hotels.styx.api.HttpRequest.Builder.put
import static com.hotels.styx.api.HttpRequest.Builder.patch
- to:
import static com.hotels.styx.api.LiveHttpRequest.*
private HttpRequest httpRequest = new HttpRequest.Builder(HttpMethod.GET).uri("www.google.com").build();
Now becomes:
private LiveHttpRequest httpRequest = new LiveHttpRequest.Builder(HttpMethod.GET, "www.google.com").build();
The new API will represent HTTP messages in two different flavours:
- Streaming HTTP messages:
LiveHttpRequest
andLiveHttpResponse
. - Immutable HTTP messages:
HttpRequest
,HttpResponse
.
The live and full variants are not interface compatible as per Liskov substitution principle and therefore they form two separate class hierarchies. However it is easy to convert between the two:
From immutable to live:
// In immutable HttpRequest:
LiveHttpRequest stream()
// In immutable HttpResponse:
LiveHttpResponse stream()
From live message to immutable:
// In LiveHttpRequest
Eventual<HttpRequest> aggregate(int maxContentBytes)
// In LiveHttpResponse
Eventual<HttpResponse> aggregate(int maxContentBytes)
In the spirit of streaming HTTP messages, the LiveHttpRequest
and LiveHttpResponse
content can only be set as ByteStream
object. Therefore, the following builder methods
have been removed:
body(HttpMessage body)
body(ByteBuf content)
body(String content)
body(byte[] content)
body(ByteBuf content)
The only way to set the content via the streaming message builders (LiveHttpRequest.Builder
or LiveHttpResponse.Builder
) is
body(ByteStream content)
This is to emphasise the streaming nature of LiveHttpRequest
and LiveHttpResponse
messages.
Use the immutable HttpRequest
and HttpResponse
classes to construct a HTTP message with
full content. They have the following builder methods for the message body:
body(String content, Charset charset)
body(String content, Charset charset, boolean setContentLength)
body(byte[] content, boolean setContentLength)
In 0.7 API you can:
response(OK).body("Hello").build();
This is no longer possible. Now you must:
response(OK).body(Eventual.of(new ByteStream(aPublisher))).build()
Where aPublisher
is a Reactive Streams compatible Publisher
.
That is, the content stream must be created as a ByteStream
instance, which is constructed
from some compatible publisher.
The preferred way, when the response body is of a fixed size and known in advance, is to create an immutable HTTP response and convert that to a live response:
HttpResponse.response(OK)
.body("Hello", UTF_8)
.build()
.stream();
Previously it was necessary to use response.decode
method which returns a HttpResponse.DecodedResponse
instance. Like so:
private ResponseWithBody doRequest(HttpRequest request) {
return client.sendRequest(request)
.flatMap(response -> response.decode(byteBuf -> byteBuf.toString(UTF_8), 0x100000))
.map(ResponseWithBody::new)
.toBlocking()
.single();
}
protected static class ResponseWithBody {
private final HttpResponse response;
private final String body;
private ResponseWithBody(HttpResponse.DecodedResponse<String> decodedResponse) {
this.response = decodedResponse.responseBuilder().body(decodedResponse.body()).build();
this.body = decodedResponse.body();
}
public HttpResponseStatus status() {
return response.status();
}
public HttpResponse response() {
return response;
}
public String body() {
return body;
}
}
With new immutable HTTP messages this is much easier:
private HttpResponse doRequest(LiveHttpRequest request) {
return await(client.sendRequest(request)
.flatMap(response -> response.aggregate(0x100000))
.asCompletableFuture());
}
Notes:
- The above example is from a unit test for a Styx plugin. Obviously you wouldn't block the thread in a Styx interceptor!
- A call to
asCompletableFuture
convertsStyxObservable<FullHttpResponse>
to a Java 8CompletableFuture<FullHttpResponse>
. - A call to
await
blocks and waits for the result fromCompletableFuture<FullHttpResponse>
. It conveniently converts the checked exceptions from CompletableFuture.get() into RuntimeExceptions, so the doRequest method signature is not polluted.
-
The
HttpHandler.handle
now has a new argumentHttpInterceptor.Context
which needs to be added to each implementation as a second parameter. However, in most cases the parameter can be safely ignored. -
Admin interface handlers now return a
Eventual<LiveHttpResponse>
instead ofrx.Observable
.
The response.request()
is removed. You need to capture the request object from the intercept
closure. Also, we might provide the original request via HTTP context. But will do this only if there are some really compelling reasons to do so.