/*
* Copyright 2016 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package io.reactivex.netty.protocol.http.server;
import io.netty.buffer.ByteBuf;
import rx.annotations.Beta;
/**
* A utility to create an interceptor chain to be used with a {@link HttpServer} to modify behavior of requests
* processed by that server.
*
* <h2>What are interceptors?</h2>
*
* Interceptors can be used to achieve use-cases that involve instrumentation related to any behavior of the requests,
* they can even be used to short-circuit the rest of the chain or to provide canned responses. In order to achieve such
* widely different use-cases, an interceptor is modelled as a simple function that takes one {@link RequestHandler}
* and returns another {@link RequestHandler} instance. With this low level abstraction, any use-case pertaining to
* connection instrumentation can be achieved.
*
* <h2>Interceptor chain anatomy</h2>
*
<PRE>
------- ------- ------- ------- -------
| | | | | | | | | |
| | ---> | | ---> | | ---> | | ---> | |
| | | | | | | | | |
------- ------- ------- ------- -------
Interceptor Interceptor Interceptor Interceptor Request
1 2 3 4 Handler
</PRE>
*
* An interceptor chain always starts with an interceptor and ends with a {@link RequestHandler} and any number of
* other interceptors can exist between the start and end. <p>
*
* A chain can be created by using the various {@code start*()} methods available in this class, eg:
* {@link #start()}, {@link #startRaw()}.<p>
*
* After starting a chain, any number of other interceptors can be added by using the various {@code next*()} methods
* available in this class, eg: {@link #next(Interceptor)}, {@link #nextWithTransform(TransformingInterceptor)},
* {@link #nextWithRequestTransform(TransformingInterceptor)} and
* {@link #nextWithResponseTransform(TransformingInterceptor)}<p>
*
* After adding the required interceptors, by providing a {@link RequestHandler} via the
* {@link #end(RequestHandler)} method, the chain can be ended and the returned {@link RequestHandler} can be used
* with any {@link HttpServer}<p>
*
* So, a typical interaction with this class would look like: <p>
*
* {@code
* HttpServer.newServer().start(HttpServerInterceptorChain.start(first).next(second).next(third).end(handler))
* }
*
* <h2>Simple Interceptor</h2>
*
* For interceptors that do not change the types of objects read or written to the underlying request/response, the
* interface {@link HttpServerInterceptorChain.Interceptor} defines the interceptor contract.
*
* <h2>Modifying the type of data read/written to the request/response</h2>
*
* Sometimes, it is required to change the type of objects read from a request or written to a response handled by
* a {@link HttpServer}. For such cases, the interface {@link HttpServerInterceptorChain.TransformingInterceptor}
* defines the interceptor contract. Since, this included 4 generic arguments to the interceptor, this is not the base
* type for all interceptors and should be used only when the types of the request/response are actually to be
* changed.
*
* <h2>Execution order</h2>
*
<PRE>
------- ------- ------- ------- ------- ------- -------
| | | | | | | | | | | | | |
| | ---> | | ---> | | ---> | | ---> | | ---> | | ---> | |
| | | | | | | | | | | | | |
------- ------- ------- ------- ------- ------- -------
Http Request Interceptor Interceptor Interceptor Interceptor Request
Server Handler 1 2 3 4 Handler
(Internal) (User)
</PRE>
*
* The above diagram depicts the execution order of interceptors. The first request handler (internal) is created by
* this class and as is returned by {@link #end(RequestHandler)} method by providing a {@link RequestHandler} that
* does the actual processing of the connection. {@link HttpServer} with which this interceptor chain is used, will
* invoke the internal request handler provided by this class. <p>
*
* The interceptors are invoked in the order that they are added to this chain.
*
* @param <I> The type of objects received as content from a request to this server.
* @param <O> The type of objects written as content from a response from this server.
* @param <II> The type of objects received as content from a request to this server after applying these interceptors.
* @param <OO> The type of objects written as content from a response from this server after applying these
* interceptors.
*/
@Beta
public final class HttpServerInterceptorChain<I, O, II, OO> {
private final TransformingInterceptor<I, O, II, OO> interceptor;
private HttpServerInterceptorChain(TransformingInterceptor<I, O, II, OO> interceptor) {
this.interceptor = interceptor;
}
/**
* Add the next interceptor to this chain.
*
* @param next Next interceptor to add.
*
* @return A new interceptor chain with the interceptors currently existing and the passed interceptor added to the
* end.
*/
public HttpServerInterceptorChain<I, O, II, OO> next(final Interceptor<II, OO> next) {
return new HttpServerInterceptorChain<>(new TransformingInterceptor<I, O, II, OO>() {
@Override
public RequestHandler<I, O> intercept(RequestHandler<II, OO> handler) {
return interceptor.intercept(next.intercept(handler));
}
});
}
/**
* Add the next interceptor to this chain, which changes the type of objects read from the request accepted by
* the associated {@link HttpServer}.
*
* @param next Next interceptor to add.
*
* @return A new interceptor chain with the interceptors currently existing and the passed interceptor added to the
* end.
*/
public <III> HttpServerInterceptorChain<I, O, III, OO> nextWithRequestTransform(final TransformingInterceptor<II, OO, III, OO> next) {
return new HttpServerInterceptorChain<>(new TransformingInterceptor<I, O, III, OO>() {
@Override
public RequestHandler<I, O> intercept(RequestHandler<III, OO> handler) {
return interceptor.intercept(next.intercept(handler));
}
});
}
/**
* Add the next interceptor to this chain, which changes the type of objects written to the response sent by
* the associated {@link HttpServer}.
*
* @param next Next interceptor to add.
*
* @return A new interceptor chain with the interceptors current existing and the passed interceptor added to the
* end.
*/
public <OOO> HttpServerInterceptorChain<I, O, II, OOO> nextWithResponseTransform(final TransformingInterceptor<II, OO, II, OOO> next) {
return new HttpServerInterceptorChain<>(new TransformingInterceptor<I, O, II, OOO>() {
@Override
public RequestHandler<I, O> intercept(RequestHandler<II, OOO> handler) {
return interceptor.intercept(next.intercept(handler));
}
});
}
/**
* Add the next interceptor to this chain, which changes the type of objects read read from the request and written
* to the response sent by the associated {@link HttpServer}.
*
* @param next Next interceptor to add.
*
* @return A new interceptor chain with the interceptors current existing and the passed interceptor added to the
* end.
*/
public <RRR, WWW> HttpServerInterceptorChain<I, O, RRR, WWW> nextWithTransform(final TransformingInterceptor<II, OO, RRR, WWW> next) {
return new HttpServerInterceptorChain<>(new TransformingInterceptor<I, O, RRR, WWW>() {
@Override
public RequestHandler<I, O> intercept(RequestHandler<RRR, WWW> handler) {
return interceptor.intercept(next.intercept(handler));
}
});
}
/**
* Terminates this chain with the passed {@link RequestHandler} and returns a {@link RequestHandler} to be
* used by a {@link HttpServer}
*
* @param handler Request handler to use.
*
* @return A request handler that wires the interceptor chain, to be used with {@link HttpServer} instead of
* directly using the passed {@code handler}
*/
public RequestHandler<I, O> end(RequestHandler<II, OO> handler) {
return interceptor.intercept(handler);
}
/**
* Starts a new interceptor chain.
*
* @return A new interceptor chain.
*/
public static <R, W> HttpServerInterceptorChain<R, W, R, W> start() {
return new HttpServerInterceptorChain<>(new TransformingInterceptor<R, W, R, W>() {
@Override
public RequestHandler<R, W> intercept(RequestHandler<R, W> handler) {
return handler;
}
});
}
/**
* Starts a new interceptor chain with {@link ByteBuf} read and written from request and to responses.
*
* @return A new interceptor chain.
*/
public static HttpServerInterceptorChain<ByteBuf, ByteBuf, ByteBuf, ByteBuf> startRaw() {
return new HttpServerInterceptorChain<>(new TransformingInterceptor<ByteBuf, ByteBuf, ByteBuf, ByteBuf>() {
@Override
public RequestHandler<ByteBuf, ByteBuf> intercept(RequestHandler<ByteBuf, ByteBuf> handler) {
return handler;
}
});
}
/**
* An interceptor that preserves the type of content of request and response.
*
* @param <I> The type of objects received as content from a request to this server.
* @param <O> The type of objects written as content from a response from this server.
*/
public interface Interceptor<I, O> {
/**
* Intercepts and optionally changes the passed {@code RequestHandler}.
*
* @param handler Handler to intercept.
*
* @return Handler to use after this transformation.
*/
RequestHandler<I, O> intercept(RequestHandler<I, O> handler);
}
/**
* An interceptor that changes the type of content of request and response.
*
* @param <I> The type of objects received as content from a request to this server.
* @param <O> The type of objects written as content from a response from this server.
* @param <II> The type of objects received as content from a request to this server after applying this
* interceptor.
* @param <OO> The type of objects written as content from a response from this server after applying this
* interceptor.
*/
public interface TransformingInterceptor<I, O, II, OO> {
/**
* Intercepts and changes the passed {@code RequestHandler}.
*
* @param handler Handler to intercept.
*
* @return Handler to use after this transformation.
*/
RequestHandler<I, O> intercept(RequestHandler<II, OO> handler);
}
}