Sometimes servlets just doesn't fit you, sometimes you need to support some protocols except HTTP, sometimes you need something really fast. Allow me to show you the Netty that can suit for these needs.
First of all you need to understand what pipeline is, see Interface ChannelPipeline. Pipeline is like processing line where various ChannelHandlers convert input bytes into output. Pipeline corresponds one to one to connection, thus ChannelHandlers in our case will convert HTTP Request into HTTP Response, handlers will be responsible for such auxiliary things like parsing incoming packets and assembling outcoming and also call business logic to handle requests and produce responses.
Full source is available.
There are 2 types of handlers.
In our case we'll create following inbound handlers
Now, given a result object, one need to produce output in HTTP way. For this purpose following outbound handlers are used.
See the next post Performance analysis of our own full blown HTTP server with Netty 4.
Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. (http://netty.io/)Netty has everything one needs for HTTP, thus web server on Netty is like a low hanging fruit.
First of all you need to understand what pipeline is, see Interface ChannelPipeline. Pipeline is like processing line where various ChannelHandlers convert input bytes into output. Pipeline corresponds one to one to connection, thus ChannelHandlers in our case will convert HTTP Request into HTTP Response, handlers will be responsible for such auxiliary things like parsing incoming packets and assembling outcoming and also call business logic to handle requests and produce responses.
Full source is available.
There are 2 types of handlers.
- ChannelInboundHandlers that process incoming data.
- ChannelOutboundHandlers that produce data to be sent.
In our case we'll create following inbound handlers
--(ByteBuf)--> HttpRequestDecoder --(HttpObject)--> HttpObjectAggregator --(FullHttpRequest)--> RequestDecoder* --(Request*)--> FormPayloadDecoder* --(FullDecodedRequest*)--> DefaultHandler* --(Response*)--> DefaultExceptionHandler*In braces you see messages being passed between handlers. With * marked our custom classes, all others are from Netty.
- HttpRequestDecoder is responsible for low level parsing and converts raw bytes into HttpObject that correspond to HTTP request header and body chunks.
- HttpObjectAggregator composes from these objects one single FullHttpRequest per HTTP request. It can be configured with maxContentLength to prevent too big requests.
- RequestDecoder simply wraps FullHttpRequest into custom Request and adds auxiliary parameters namely orderNumber. HTTP client can send multiple requests in one connection and it expects responses in the same order. We process those requests in parallel and for instance second one can be processed before first. Thus we need to assembly responses in the right order before sending them out. For this purpose we assign index number to each incoming Request in our pipeline.
telnet localhost 9999 Trying ::1... Connected to localhost. Escape character is '^]'. GET /?duration=5 HTTP/1.1 <-- sleep for 5 seconds User-Agent: curl/7.33.0 Host: localhost:9999 Accept: */* GET /?duration=3 HTTP/1.1 <-- sleep for 3 seconds User-Agent: curl/7.33.0 Host: localhost:9999 Accept: */* HTTP/1.1 200 OK <-- despite second request processed first we get both responses in the right order after 5 seconds Content-Type: application/json Content-Length: 18 Set-Cookie: JSESSIOINID=e9ma1foeretnh19u4demqta7tr Connection: keep-alive "Slept for 5 secs"HTTP/1.1 200 OK Content-Type: application/json Content-Length: 18 Set-Cookie: JSESSIOINID=8ti5pbfc0dmd4r09i6or005r6b Connection: keep-alive "Slept for 3 secs"
- FormPayloadDecoder implements incoming parameters parsing. It uses QueryStringDecoder and HttpPostRequestDecoder from netty to parse application/x-www-form-urlencoded and multipart/form-data encoded GET and POST data. It produces FullDecodedRequest that differs from Request by containing Values - simple key-value mapping for parameter names and values. You can easily replace FormPayloadDecoder with any other handler that converts Request into FullDecodedRequest, for instance, parse JSON encoded data in POST body.
- DefaultHandler uses a separate set of threads to handle business logic. It uses netty EventExecutorGroup that is like ExecutorService in Java SE. EventExecutorGroup provides methods to submit tasks and returns futures to observe (in contrast to Java SE Future one can add listeners to netty Future). Provider below is a callable that given input parameters produces some output object.
protected void channelRead0(final ChannelHandlerContext ctx, final FullDecodedRequest decodedRequest) throws Exception { Callable<? extends Object> callable = new Provider( decodedRequest.getPath(), decodedRequest.getValues()); final Future<? extends Object> future = executor.submit(callable); future.addListener(new GenericFutureListener<Future<Object>>() { @Override public void operationComplete(Future<Object> future) throws Exception { if (future.isSuccess()) { ctx.writeAndFlush(new Response(decodedRequest.getRequest(), future.get())); } else { ctx.fireExceptionCaught(future.cause()); } } }); }
- DefaultExceptionHandler is a catch all handler that receives all exceptions from any handler above, makes pretty output from java stack trace and closes connection.
Now, given a result object, one need to produce output in HTTP way. For this purpose following outbound handlers are used.
--(Response*)--> JacksonJsonResponseEncoder* --(FullEncodedResponse*)--> ResponseEncoder* --(FullHttpResponse)--> HttpResponseEncoder --(ByteBuf)-->
- Response contains result and reference to request. JacksonJsonResponseEncoder as you may guess uses Jackson library to convert response to JSON, produces FullEncodedResponse that contains FullHttpResponse with appropriate Content-Type and Content-Length set. You can replace JacksonJsonResponseEncoder with any other way to encode response, for instance, to xml or depending to Accept-Encoding request header.
- ResponseEncoder evicts FullHttpResponse from FullEncodedResponse and handles such aspects as cookies, session ids, keep-alive, etc. Also it assembles responses in the right order.
- HttpResponseEncoder is a netty thing that obviously produces raw bytes from FullHttpResponse. Pay attention at ReferenceCountUtil.release(httpRequest). Netty uses pool of buffers and reference counting to omit problems with GC. See Reference counted objects.
See the next post Performance analysis of our own full blown HTTP server with Netty 4.
No comments:
Post a Comment