Java tutorial
/* * Copyright (c) 2011-2013 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package io.jsync.http.impl; import io.jsync.Async; import io.jsync.AsyncResult; import io.jsync.Handler; import io.jsync.VoidHandler; import io.jsync.buffer.Buffer; import io.jsync.http.HttpServerRequest; import io.jsync.http.ServerWebSocket; import io.jsync.http.impl.ws.WebSocketFrameInternal; import io.jsync.impl.DefaultContext; import io.jsync.net.NetSocket; import io.jsync.net.impl.AsyncNetHandler; import io.jsync.net.impl.ConnectionBase; import io.jsync.net.impl.DefaultNetSocket; import io.netty.channel.*; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.LastHttpContent; import io.netty.util.ReferenceCountUtil; import java.io.File; import java.util.ArrayDeque; import java.util.HashMap; import java.util.Map; import java.util.Queue; /** * @author <a href="http://tfox.org">Tim Fox</a> */ class ServerConnection extends ConnectionBase { private static final int CHANNEL_PAUSE_QUEUE_SIZE = 5; private final Queue<Object> pending = new ArrayDeque<>(8); private final String serverOrigin; private final DefaultHttpServer server; private Handler<HttpServerRequest> requestHandler; private Handler<ServerWebSocket> wsHandler; private DefaultHttpServerRequest currentRequest; private DefaultHttpServerResponse pendingResponse; private DefaultServerWebSocket ws; private boolean channelPaused; private boolean paused; private boolean sentCheck; private ChannelFuture lastWriteFuture; ServerConnection(DefaultHttpServer server, Channel channel, DefaultContext context, String serverOrigin) { super(server.async, channel, context); this.serverOrigin = serverOrigin; this.server = server; } public void pause() { if (!paused) { paused = true; } } public void resume() { if (paused) { paused = false; checkNextTick(); } } void handleMessage(Object msg) { if (paused || (pendingResponse != null && msg instanceof HttpRequest) || !pending.isEmpty()) { //We queue requests if paused or a request is in progress to prevent responses being written in the wrong order pending.add(msg); if (pending.size() == CHANNEL_PAUSE_QUEUE_SIZE) { //We pause the channel too, to prevent the queue growing too large, but we don't do this //until the queue reaches a certain size, to avoid pausing it too often super.doPause(); channelPaused = true; } } else { processMessage(msg); } } void responseComplete() { pendingResponse = null; checkNextTick(); } void requestHandler(Handler<HttpServerRequest> handler) { this.requestHandler = handler; } void wsHandler(Handler<ServerWebSocket> handler) { this.wsHandler = handler; } String getServerOrigin() { return serverOrigin; } Async async() { return async; } @Override public ChannelFuture write(Object obj) { return lastWriteFuture = super.write(obj); } NetSocket createNetSocket() { DefaultNetSocket socket = new DefaultNetSocket(async, channel, context, server.tcpHelper, false); Map<Channel, DefaultNetSocket> connectionMap = new HashMap<Channel, DefaultNetSocket>(1); connectionMap.put(channel, socket); // Flush out all pending data endReadAndFlush(); // remove old http handlers and replace the old handler with one that handle plain sockets ChannelPipeline pipeline = channel.pipeline(); ChannelHandler compressor = pipeline.get(HttpChunkContentCompressor.class); if (compressor != null) { pipeline.remove(compressor); } pipeline.remove("httpDecoder"); if (pipeline.get("chunkedWriter") != null) { pipeline.remove("chunkedWriter"); } channel.pipeline().replace("handler", "handler", new AsyncNetHandler(server.async, connectionMap) { @Override public void exceptionCaught(ChannelHandlerContext chctx, Throwable t) throws Exception { // remove from the real mapping server.connectionMap.remove(channel); super.exceptionCaught(chctx, t); } @Override public void channelInactive(ChannelHandlerContext chctx) throws Exception { // remove from the real mapping server.connectionMap.remove(channel); super.channelInactive(chctx); } @Override public void channelRead(ChannelHandlerContext chctx, Object msg) throws Exception { if (msg instanceof HttpContent) { ReferenceCountUtil.release(msg); return; } super.channelRead(chctx, msg); } }); // check if the encoder can be removed yet or not. if (lastWriteFuture == null) { channel.pipeline().remove("httpEncoder"); } else { lastWriteFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { channel.pipeline().remove("httpEncoder"); } }); } return socket; } private void handleRequest(DefaultHttpServerRequest req, DefaultHttpServerResponse resp) { setContext(); try { this.currentRequest = req; pendingResponse = resp; if (requestHandler != null) { requestHandler.handle(req); } } catch (Throwable t) { handleHandlerException(t); } } private void handleChunk(Buffer chunk) { try { setContext(); currentRequest.handleData(chunk); } catch (Throwable t) { handleHandlerException(t); } } private void handleEnd() { try { setContext(); currentRequest.handleEnd(); currentRequest = null; } catch (Throwable t) { handleHandlerException(t); } } @Override public void handleInterestedOpsChanged() { try { if (!doWriteQueueFull()) { setContext(); if (pendingResponse != null) { pendingResponse.handleDrained(); } else if (ws != null) { ws.writable(); } } } catch (Throwable t) { handleHandlerException(t); } } void handleWebsocketConnect(DefaultServerWebSocket ws) { try { if (wsHandler != null) { setContext(); wsHandler.handle(ws); this.ws = ws; } } catch (Throwable t) { handleHandlerException(t); } } private void handleWsFrame(WebSocketFrameInternal frame) { try { if (ws != null) { setContext(); ws.handleFrame(frame); } } catch (Throwable t) { handleHandlerException(t); } } protected void handleClosed() { super.handleClosed(); if (ws != null) { ws.handleClosed(); } if (pendingResponse != null) { pendingResponse.handleClosed(); } } protected DefaultContext getContext() { return super.getContext(); } @Override protected void handleException(Throwable t) { super.handleException(t); if (currentRequest != null) { currentRequest.handleException(t); } if (pendingResponse != null) { pendingResponse.handleException(t); } if (ws != null) { ws.handleException(t); } } protected void addFuture(Handler<AsyncResult<Void>> doneHandler, ChannelFuture future) { super.addFuture(doneHandler, future); } @Override protected boolean supportsFileRegion() { return super.supportsFileRegion() && channel.pipeline().get(HttpChunkContentCompressor.class) == null; } protected ChannelFuture sendFile(File file) { return super.sendFile(file); } private void processMessage(Object msg) { if (msg instanceof HttpRequest) { HttpRequest request = (HttpRequest) msg; DefaultHttpServerResponse resp = new DefaultHttpServerResponse(async, this, request); DefaultHttpServerRequest req = new DefaultHttpServerRequest(this, request, resp); handleRequest(req, resp); } if (msg instanceof HttpContent) { HttpContent chunk = (HttpContent) msg; if (chunk.content().isReadable()) { Buffer buff = new Buffer(chunk.content()); handleChunk(buff); } //TODO chunk trailers if (msg instanceof LastHttpContent) { if (!paused) { handleEnd(); } else { // Requeue pending.add(LastHttpContent.EMPTY_LAST_CONTENT); } } } else if (msg instanceof WebSocketFrameInternal) { WebSocketFrameInternal frame = (WebSocketFrameInternal) msg; handleWsFrame(frame); } checkNextTick(); } private void checkNextTick() { // Check if there are more pending messages in the queue that can be processed next time around if (!pending.isEmpty() && !sentCheck && !paused && (pendingResponse == null || pending.peek() instanceof HttpContent)) { sentCheck = true; async.runOnContext(new VoidHandler() { public void handle() { sentCheck = false; if (!paused) { Object msg = pending.poll(); if (msg != null) { processMessage(msg); } if (channelPaused && pending.isEmpty()) { //Resume the actual channel ServerConnection.super.doResume(); channelPaused = false; } } } }); } } }