/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ /* * This class accepts a client connection and parses http data from it. */ // TODO: shouldClose_ is not used correctly. It should be used to close the socket? When? package com.facebook.infrastructure.net.http; import java.util.*; import java.io.*; import java.nio.*; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import com.facebook.infrastructure.net.*; import com.facebook.infrastructure.service.*; import com.facebook.infrastructure.utils.LogUtil; import org.apache.log4j.Logger; /** * * @author kranganathan */ public class HttpConnection extends SelectionKeyHandler implements HttpStartLineParser.Callback, HttpHeaderParser.Callback { private static Logger logger_ = Logger.getLogger(StorageService.class); public static final String httpRequestVerbHandler_ = "HTTP-REQUEST-VERB-HANDLER"; public static final String httpStage_ = "HTTP-STAGE"; /* * These are the callbacks into who ever intends * to listen on the client socket. */ public interface HttpConnectionListener { public void onRequest(com.facebook.infrastructure.net.http.HttpRequest httpRequest); public void onResponse(HttpResponse httpResponse); } enum HttpMessageType { UNKNOWN, REQUEST, RESPONSE } enum ParseState { IN_NEW, IN_START, IN_HEADERS, IN_BODY } private ParseState parseState_ = ParseState.IN_NEW; private long parseStartTime_ = 0; private HttpMessageType currentMsgType_ = HttpMessageType.UNKNOWN; private int contentLength_ = 0; private List<ByteBuffer> bodyBuffers_ = new LinkedList<ByteBuffer>(); private boolean shouldClose_ = false; private String defaultContentType_ = "text/html"; private com.facebook.infrastructure.net.http.HttpRequest currentRequest_ = null; private HttpResponse currentResponse_ = null; private HttpStartLineParser startLineParser_ = new HttpStartLineParser(this); private HttpHeaderParser headerParser_ = new HttpHeaderParser(this); /* Selection Key associated with this HTTP Connection */ private SelectionKey httpKey_; /* SocketChannel associated with this HTTP Connection */ private SocketChannel httpChannel_; /* HTTPReader instance associated with this HTTP Connection */ private HTTPReader httpReader_ = new HTTPReader(); /* * This abstraction starts reading the data that comes in * on a HTTP request. It accumulates the bytes read into * a buffer and passes the buffer to the HTTP parser. */ class HTTPReader implements Runnable { /* We read 256 bytes at a time from a HTTP connection */ private static final int bufferSize_ = 256; /* * Read buffers from the input stream into the byte buffer. */ public void run() { ByteBuffer readBuffer = ByteBuffer.allocate(HTTPReader.bufferSize_); try { int bytesRead = httpChannel_.read(readBuffer); readBuffer.flip(); if ( readBuffer.remaining() > 0 ) HttpConnection.this.parse(readBuffer); } catch ( IOException ex ) { logger_.warn(LogUtil.throwableToString(ex)); } } } public static class HttpRequest { private com.facebook.infrastructure.net.http.HttpRequest httpRequest_; private HttpConnection httpConnection_; HttpRequest(com.facebook.infrastructure.net.http.HttpRequest httpRequest, HttpConnection httpConnection) { httpRequest_ = httpRequest; httpConnection_ = httpConnection; } public com.facebook.infrastructure.net.http.HttpRequest getHttpRequest() { return httpRequest_; } public HttpConnection getHttpConnection() { return httpConnection_; } } /* * Read called on the Selector thread. This is called * when there is some HTTP request that needs to be * processed. */ public void read(SelectionKey key) { if ( httpKey_ == null ) { httpKey_ = key; httpChannel_ = (SocketChannel)key.channel(); } /* deregister interest for read */ key.interestOps( key.interestOps() & ( ~SelectionKey.OP_READ ) ); /* Add a task to process the HTTP request */ MessagingService.getReadExecutor().execute(httpReader_); } public void modifyKeyForRead(SelectionKey key) { key.interestOps( httpKey_.interestOps() | SelectionKey.OP_READ ); } private void resetParserState() { startLineParser_.resetParserState(); headerParser_.resetParserState(); parseState_ = ParseState.IN_NEW; contentLength_ = 0; bodyBuffers_ = new LinkedList<ByteBuffer>(); currentMsgType_ = HttpMessageType.UNKNOWN; currentRequest_ = null; currentResponse_ = null; } public void close() { logger_.info("Closing HTTP socket ..."); if ( httpKey_ != null ) SelectorManager.getSelectorManager().cancel(httpKey_); } /* * Process the HTTP commands sent from the client. Reads * the socket and parses the HTTP request. */ public void parse(ByteBuffer bb) { try { logger_.debug("Processing http requests from socket ..."); switch (parseState_) { case IN_NEW: parseState_ = ParseState.IN_START; parseStartTime_ = System.currentTimeMillis(); // fall through case IN_START: if (startLineParser_.onMoreBytesNew(bb) == false) { break; } else { /* Already done through the callback */ parseState_ = ParseState.IN_HEADERS; } // fall through case IN_HEADERS: if (headerParser_.onMoreBytesNew(bb) == false) { break; // need more bytes } else { String len; if (currentMsgType_ == HttpMessageType.REQUEST) { len = currentRequest_.getHeader(HttpProtocolConstants.CONTENT_LENGTH); // find if we should close method if (currentRequest_.getVersion().equalsIgnoreCase("HTTP/1.1")) { /* * Scan all of the headers for close messages */ String val = currentRequest_.getHeader(HttpProtocolConstants.CONNECTION); if (val != null && val.equalsIgnoreCase(HttpProtocolConstants.CLOSE)) { shouldClose_ = true; } } else if (currentRequest_.getVersion().equalsIgnoreCase("HTTP/1.0")) { /* By default no keep-alive */ shouldClose_ = true; /* * Scan all of the headers for keep-alive * messages */ String val = currentRequest_.getHeader(HttpProtocolConstants.CONNECTION); if (val != null && val.equalsIgnoreCase(HttpProtocolConstants.KEEP_ALIVE)) { shouldClose_ = false; } } else { /* Assume 0.9 */ shouldClose_ = true; } } else if (currentMsgType_ == HttpMessageType.RESPONSE) { len = currentResponse_.getHeader(HttpProtocolConstants.CONTENT_LENGTH); // TODO: pay attention to keep-alive and // close headers } else { logger_.warn("in HttpConnection::processInput_() Message type is not set"); return; } if (len != null) { try { if(len == null || len.equals("")) contentLength_ = 0; else contentLength_ = Integer.parseInt(len); } catch (NumberFormatException ex) { throw new HttpParsingException(); } } parseState_ = ParseState.IN_BODY; } // fall through case IN_BODY: boolean done = false; if (contentLength_ > 0) { if (bb.remaining() > contentLength_) { int newLimit = bb.position() + contentLength_; bodyBuffers_.add(((ByteBuffer) bb.duplicate().limit(newLimit)).slice()); bb.position(newLimit); contentLength_ = 0; } else { contentLength_ -= bb.remaining(); bodyBuffers_.add(bb.duplicate()); bb.position(bb.limit()); } } if (contentLength_ == 0) { done = true; } if (done) { if (currentMsgType_ == HttpMessageType.REQUEST) { //currentRequest_.setParseTime(env_.getCurrentTime() - parseStartTime_); currentRequest_.setBody(bodyBuffers_); if (currentRequest_.getHeader("Content-Type") == null) { currentRequest_.addHeader("Content-Type", defaultContentType_); } handleRequest(currentRequest_); } else if (currentMsgType_ == HttpMessageType.RESPONSE) { logger_.info("Holy shit! We are not supposed to be here - ever !!!"); } else { logger_.error("Http message type is still" + " unset after we finish parsing the body?"); } resetParserState(); } } } catch (final Throwable e) { logger_.warn(LogUtil.throwableToString(e)); //close(); } finally { SelectorManager.getSelectorManager().modifyKeyForRead(httpKey_); } } public void write(ByteBuffer buffer) { /* * TODO: Make this a non blocking write. */ try { while ( buffer.remaining() > 0 ) { httpChannel_.write(buffer); } close(); } catch ( IOException ex ) { logger_.warn(LogUtil.throwableToString(ex)); } } private void handleRequest(com.facebook.infrastructure.net.http.HttpRequest request) { HttpRequest httpRequest = new HttpRequest(request, this); Message httpMessage = new Message<HttpRequest>(null, HttpConnection.httpStage_, HttpConnection.httpRequestVerbHandler_, httpRequest); MessagingService.receive(httpMessage); } // HttpStartLineParser.Callback interface implementation public void onStartLine(String method, String path, String query, String version) { logger_.debug("Startline method=" + method + " path=" + path + " query=" + query + " version=" + version); if (method.startsWith("HTTP")) { // response currentMsgType_ = HttpMessageType.RESPONSE; currentResponse_ = new HttpResponse(); currentResponse_.setStartLine(method, path, version); } else { // request currentMsgType_ = HttpMessageType.REQUEST; currentRequest_ = new com.facebook.infrastructure.net.http.HttpRequest(); currentRequest_.setStartLine(method, path, query, version); } } // HttpHeaderParser.Callback interface implementation public void onHeader(String name, String value) { if (currentMsgType_ == HttpMessageType.REQUEST) { currentRequest_.addHeader(name, value); } else if (currentMsgType_ == HttpMessageType.RESPONSE) { currentResponse_.addHeader(name, value); } else { logger_.warn("Unknown message type -- HttpConnection::onHeader()"); } logger_.debug(name + " : " + value); } }