// Copyright (c) 2014 Tom Zhou<iwebpp@gmail.com> package com.iwebpp.node.http; import java.nio.ByteBuffer; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; import com.iwebpp.node.HttpParser.http_parser_type; import com.iwebpp.node.NodeContext; import com.iwebpp.node.Util; import com.iwebpp.node.http.http.exception_socket_b; import com.iwebpp.node.http.http.request_response_b; import com.iwebpp.node.http.http.request_socket_head_b; import com.iwebpp.node.net.AbstractServer; import com.iwebpp.node.net.AbstractSocket; import com.iwebpp.node.others.TripleState; import com.iwebpp.node.net.TCP; public final class HttpServer extends TCP.Server { private boolean httpAllowHalfOpen; private NodeContext context; private static class connectionListenerImpl implements connectionListener { private final static String TAG = "connectionListenerImpl"; private HttpServer self; private NodeContext context; private int maxHeadersCount = 4000; public connectionListenerImpl(NodeContext ctx, HttpServer srv) { this.context = ctx; this.self = srv; } @SuppressWarnings("unused") private connectionListenerImpl(){} @Override public void onConnection(final AbstractSocket socket) throws Exception { debug(TAG, "SERVER new HTTP connection"); http.httpSocketSetup(socket); // If the user has added a listener to the server, // request, or response, then it's their responsibility. // otherwise, destroy on timeout by default // TBD... /*if (self.timeout > 0) socket.setTimeout(self.timeout); socket.on('timeout', function() { var req = socket.parser && socket.parser.incoming; var reqTimeout = req && !req.complete && req.emit('timeout', socket); var res = socket._httpMessage; var resTimeout = res && res.emit('timeout', socket); var serverTimeout = self.emit('timeout', socket); if (!reqTimeout && !resTimeout && !serverTimeout) socket.destroy(); });*/ ///var parser = parsers.alloc(); final parserOnIncoming parser = new parserOnIncoming(context, self, socket); parser.Reinitialize(http_parser_type.HTTP_REQUEST); parser.socket = socket; socket.setParser(parser); parser.incoming = null; // Propagate headers limit from server instance to parser ///if (util.isNumber(this.maxHeadersCount)) { if (this.maxHeadersCount > 0) { parser.maxHeaderPairs = this.maxHeadersCount << 1; } else { // Set default value because parser may be reused from FreeList parser.maxHeaderPairs = 2000; } final Listener serverSocketCloseListener = new Listener() { @Override public void onEvent(Object data) throws Exception { debug(TAG, "server socket close"); // mark this parser as reusable if (socket.getParser() != null) IncomingParser.freeParser(socket.getParser(), null); parser.abortIncoming(); } }; // TODO(isaacs): Move all these functions out of here Listener socketOnError = new Listener() { @Override public void onEvent(Object e) throws Exception { self.emit("clientError", new exception_socket_b(e!=null? e.toString() : null, socket)); } }; final Listener socketOnEnd = new Listener(){ public void onEvent(final Object data) throws Exception { ///var socket = this; int ret = parser.Finish(); if (ret != 0/*instanceof Error*/) { debug(TAG, "parse error"); socket.destroy("parse error"); return; } if (!self.httpAllowHalfOpen) { parser.abortIncoming(); if (socket.writable()) socket.end(null, null, null); } else if (parser.outgoings.size() > 0) { ///outgoing[outgoing.length - 1]._last = true; parser.outgoings.get(parser.outgoings.size()-1).set_last(true); } else if (socket.get_httpMessage() != null) { ServerResponse srvres = (ServerResponse)(socket.get_httpMessage()); srvres._last = true; } else { if (socket.writable()) socket.end(null, null, null); } } }; final Listener socketOnData = new Listener() { @Override public void onEvent(Object raw) throws Exception { if (!Util.isBuffer(raw)) throw new Exception("onData Not ByteBuffer"); ByteBuffer d = (ByteBuffer)raw; assert(!socket.is_paused()); debug(TAG, "SERVER socketOnData " + Util.chunkLength(d)); debug(TAG, "\t\t\t"+Util.chunkToString(d, "utf-8")); int ret = parser.Execute(d); if (ret < 0 /*instanceof Error*/) { debug(TAG, "parse error"); socket.destroy("parse error"); } else if (parser.incoming!=null && parser.incoming.isUpgrade()) { // Upgrade or CONNECT int bytesParsed = ret; IncomingMessage req = parser.incoming; debug(TAG, "SERVER upgrade or connect " + req.method()); socket.removeListener("data", this); socket.removeListener("end", socketOnEnd); socket.removeListener("close", serverSocketCloseListener); parser.Finish(); ///freeParser(parser, req); IncomingParser.freeParser(parser, req); String eventName = req.method() == "CONNECT" ? "connect" : "upgrade"; if (self.listenerCount(eventName) > 0) { debug(TAG, "SERVER have listener for " + eventName); ///var bodyHead = d.slice(bytesParsed, d.length); ByteBuffer bodyHead = (ByteBuffer) Util.chunkSlice(d, bytesParsed, d.capacity()); // TODO(isaacs): Need a way to reset a stream to fresh state // IE, not flowing, and not explicitly paused. socket.get_readableState().setFlowing(TripleState.MAYBE); self.emit(eventName, new request_socket_head_b(req, socket, bodyHead)); } else { // Got upgrade header or CONNECT method, but have no handler. socket.destroy(null); } } if (socket.is_paused()) { // onIncoming paused the socket, we should pause the parser as well debug(TAG, "pause parser"); ///socket.parser.pause(); socket.getParser().Pause(false); } } }; // The following callback is issued after the headers have been read on a // new message. In this callback we setup the response object and pass it // to the user. socket.set_paused(false); Listener socketOnDrain = new Listener() { public void onEvent(final Object data) throws Exception { // If we previously paused, then start reading again. if (socket.is_paused()) { socket.set_paused(false); // TDB... ///socket.parser.resume(); socket.getParser().Pause(false); socket.resume(); } } }; socket.on("drain", socketOnDrain); socket.addListener("error", socketOnError); socket.addListener("close", serverSocketCloseListener); ///parser.onIncoming = parserOnIncoming; socket.on("end", socketOnEnd); // set flowing ??? TBD... socket.get_readableState().setFlowing(TripleState.TRUE); socket.on("data", socketOnData); } } public HttpServer(NodeContext ctx) throws Exception { super(ctx, new AbstractServer.Options(false), null); this.context = ctx; // Similar option to this. Too lazy to write my own docs. // http://www.squid-cache.org/Doc/config/half_closed_clients/ // http://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3F this.httpAllowHalfOpen = false; ///this.addListener('connection', connectionListener); this.onConnection(new connectionListenerImpl(context, this)); /*this.addListener('clientError', function(err, conn) { conn.destroy(err); });*/ this.onClientError(new clientErrorListener(){ @Override public void onClientError(String err, AbstractSocket conn) throws Exception { conn.destroy(err); } }); } public HttpServer(NodeContext ctx, requestListener onreq) throws Exception { super(ctx, new AbstractServer.Options(false), null); this.context = ctx; // Similar option to this. Too lazy to write my own docs. // http://www.squid-cache.org/Doc/config/half_closed_clients/ // http://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3F this.httpAllowHalfOpen = false; ///this.addListener('connection', connectionListener); this.onConnection(new connectionListenerImpl(context, this)); /*this.addListener('clientError', function(err, conn) { conn.destroy(err); });*/ this.onClientError(new clientErrorListener(){ @Override public void onClientError(String err, AbstractSocket conn) throws Exception { conn.destroy(err); } }); if (onreq != null) this.onRequest(onreq); } ///server.listen(port, [hostname], [backlog], [callback]) public HttpServer listen( int port, String hostname, int backlog, ListeningCallback cb) throws Exception { if (cb != null) onListening(cb); super.listen(hostname, port, Util.ipFamily(hostname), backlog, -1, null); return this; } public HttpServer listen( int port, String hostname, ListeningCallback cb) throws Exception { return listen(port, hostname, 256, cb); } public HttpServer listen( int port, String hostname) throws Exception { return listen(port, hostname, 256, null); } /*public void close(final closeListener cb) throws Exception { if (cb != null) onClose(cb); super.close(null); }*/ public int maxHeadersCount(int max) { return 2000; } ///server.setTimeout(msecs, callback) ///server.timeout // Event listeners public void onListening(final ListeningCallback cb) throws Exception { this.on("listening", new Listener(){ @Override public void onEvent(Object raw) throws Exception { cb.onListening(); } }); } public void onRequest(final requestListener cb) throws Exception { this.on("request", new Listener(){ @Override public void onEvent(Object raw) throws Exception { request_response_b data = (request_response_b)raw; cb.onRequest(data.getRequest(), data.getResponse()); } }); } public interface requestListener { public void onRequest(IncomingMessage req, ServerResponse res) throws Exception; } public void onConnection(final connectionListener cb) throws Exception { this.on("connection", new Listener(){ @Override public void onEvent(Object raw) throws Exception { AbstractSocket data = (AbstractSocket)raw; cb.onConnection(data); } }); } public interface connectionListener { public void onConnection(AbstractSocket socket) throws Exception; } public void onClose(final closeListener cb) throws Exception { this.on("close", new Listener(){ @Override public void onEvent(Object data) throws Exception { cb.onClose(); } }); } public interface closeListener { public void onClose() throws Exception; } public void onCheckContinue(final checkContinueListener cb) throws Exception { this.on("checkContinue", new Listener(){ @Override public void onEvent(Object raw) throws Exception { request_response_b data = (request_response_b)raw; cb.onCheckContinue(data.getRequest(), data.getResponse()); } }); } public interface checkContinueListener { public void onCheckContinue(IncomingMessage req, ServerResponse res) throws Exception; } public void onConnect(final connectListener cb) throws Exception { this.on("connect", new Listener(){ @Override public void onEvent(Object raw) throws Exception { request_socket_head_b data = (request_socket_head_b)raw; cb.onConnect(data.getRequest(), data.getSocket(), data.getHead()); } }); } public interface connectListener { public void onConnect(IncomingMessage request, AbstractSocket socket, ByteBuffer head) throws Exception; } public void onUpgrade(final upgradeListener cb) throws Exception { this.on("upgrade", new Listener(){ @Override public void onEvent(Object raw) throws Exception { request_socket_head_b data = (request_socket_head_b)raw; cb.onUpgrade(data.getRequest(), data.getSocket(), data.getHead()); } }); } public interface upgradeListener { public void onUpgrade(IncomingMessage request, AbstractSocket socket, ByteBuffer head) throws Exception; } public void onClientError(final clientErrorListener cb) throws Exception { this.on("clientError", new Listener(){ @Override public void onEvent(Object raw) throws Exception { exception_socket_b data = (exception_socket_b)raw; cb.onClientError(data.getException(), data.getSocket()); } }); } public interface clientErrorListener { public void onClientError(String exception, AbstractSocket socket) throws Exception; } // Parser on request private static class parserOnIncoming extends IncomingParser { private static final String TAG = "parserOnIncoming"; private NodeContext context; private List<IncomingMessage> incomings; private List<ServerResponse> outgoings; private HttpServer self; public parserOnIncoming(NodeContext ctx, HttpServer srv, AbstractSocket socket) { super(ctx, http_parser_type.HTTP_REQUEST, socket); this.context = ctx; this.self = srv; incomings = new LinkedList<IncomingMessage>(); outgoings = new LinkedList<ServerResponse>(); } private parserOnIncoming() {super(null, null, null);} @Override protected boolean onIncoming(final IncomingMessage req, boolean shouldKeepAlive) throws Exception { final IncomingParser ips = this; incomings.add(req); // If the writable end isn't consuming, then stop reading // so that we don't become overwhelmed by a flood of // pipelined requests that may never be resolved. if (!socket.is_paused()) { boolean needPause = socket.get_writableState().isNeedDrain(); if (needPause) { socket.set_paused(true); // We also need to pause the parser, but don't do that until after // the call to execute, because we may still be processing the last // chunk. socket.pause(); } } // TBD... final ServerResponse res = new ServerResponse(context, req); res.setShouldKeepAlive(shouldKeepAlive); //DTRACE_HTTP_SERVER_REQUEST(req, socket); //COUNTER_HTTP_SERVER_REQUEST(); if (socket.get_httpMessage() != null) { // There are already pending outgoing res, append. outgoings.add(res); debug(TAG, "outgoings.add(res)"); } else { res.assignSocket(socket); debug(TAG, "res.assignSocket(socket)"); } // When we're finished writing the response, check if this is the last // response, if so destroy the socket. Listener resOnFinish = new Listener() { @Override public void onEvent(Object data) throws Exception { // Usually the first incoming element should be our request. it may // be that in the case abortIncoming() was called that the incoming // array will be empty. assert(incomings.size() == 0 || incomings.get(0) == req); if (incomings.size() > 0) incomings.remove(0); // if the user never called req.read(), and didn't pipe() or // .resume() or .on('data'), then we call req._dump() so that the // bytes will be pulled off the wire. if (!req.is_consuming() && !req.get_readableState().isResumeScheduled()) req._dump(); res.detachSocket(socket); debug(TAG, "res.detachSocket(socket)"); // Reset Parser state ips.Reinitialize(ips.getType()); if (res.is_last()) { debug(TAG, "res.is_last()"); socket.destroySoon(); } else { // start sending the next message ServerResponse m = outgoings.remove(0); if (m != null) { m.assignSocket(socket); } } } }; res.on("prefinish", resOnFinish); if ( req.getHeaders().containsKey("expect") && !req.getHeaders().get("expect").isEmpty() && (req.getHttpVersionMajor() == 1 && req.getHttpVersionMinor() == 1) && ///http.continueExpression == req.headers.get("expect").get(0)) { Pattern.matches(http.continueExpression, req.getHeaders().get("expect").get(0))) { res.set_expect_continue(true); if (self.listenerCount("checkContinue") > 0) { self.emit("checkContinue", new request_response_b(req, res)); } else { res.writeContinue(null); self.emit("request", new request_response_b(req, res)); } } else { self.emit("request", new request_response_b(req, res)); } return false; // Not a HEAD response. (Not even a response!) } public void abortIncoming() throws Exception { while (incomings.size() > 0) { IncomingMessage req = incomings.remove(0); req.emit("aborted"); req.emit("close"); } // abort socket._httpMessage ? } } }