httpheaderreader.java

来自「一款Java实现的HTTP代理服务器」· Java 代码 · 共 363 行

JAVA
363
字号
package rabbit.proxy;import java.io.IOException;import java.io.UnsupportedEncodingException;import java.nio.ByteBuffer;import java.nio.channels.SocketChannel;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import rabbit.http.Header;import rabbit.http.HttpHeader;import rabbit.util.Logger;import rabbit.util.TrafficLogger;/** A handler that reads http headers * * @author <a href="mailto:robo@khelekore.org">Robert Olofsson</a> */public class HttpHeaderReader extends BaseSocketHandler     implements LineListener {        private HttpHeader header;    private Header head = null;        private boolean append = false;    private boolean request = true;    private boolean strictHttp = true;    private HttpHeaderListener reader;    private boolean headerRead = false;    // State variables.    private boolean keepalive = true;    private boolean ischunked = false;    private long dataSize = -1;   // -1 for unknown.    private int startParseAt = 0;    private TrafficLogger tl;    private static final ByteBuffer HTTP_IDENTIFIER =     ByteBuffer.wrap (new byte[]{(byte)'H', (byte)'T', (byte)'T', 				(byte)'P', (byte)'/'});        private static final ByteBuffer EXTRA_LAST_CHUNK =     ByteBuffer.wrap (new byte[]{(byte)'0', (byte)'\r', (byte)'\n', 				(byte)'\r', (byte)'\n'});        /**      * @param request true if a request is read, false if a response is read.     *                Servers may respond without header (HTTP/0.9) so try to      *                handle that.     */     public HttpHeaderReader (SocketChannel channel, ByteBuffer buffer, 			     Selector selector, Logger logger, TrafficLogger tl,			     boolean request, boolean strictHttp,			     HttpHeaderListener reader) 	throws IOException {	super (channel, buffer, selector, logger);	this.tl = tl;	this.request = request;	this.strictHttp = strictHttp;	this.reader = reader;	if (buffer != null) { 	    if (buffer.hasRemaining ()) {		startParseAt = buffer.position ();		parseBuffer ();	    } else {		buffer.clear ();	    }	}    }    public void timeout () {	reader.timeout ();    }        public void run () {	Logger logger = getLogger ();	try {	    if (buffer == null)		allocateBuffer ();	    // read http request	    // make sure we have room for reading.	    int pos = buffer.position ();	    buffer.limit (buffer.capacity ());	    int read = channel.read (buffer);	    if (read == -1) {		closeDown ();		reader.closed ();		return;	    }	    tl.read (read);	    buffer.position (startParseAt);	    buffer.limit (read + pos);	    parseBuffer ();	} catch (IOException e) {	    logger.logWarn ("Failed to handle connection: " + e);	    reader.failed (e);	}    }        private void parseBuffer () throws IOException {	int startPos = buffer.position ();	buffer.mark ();	boolean done = handleBuffer ();	if (!done) {	    register ();	    int fullPosition = buffer.position ();	    buffer.reset ();	    int pos = buffer.position ();	    if (pos == startPos) {		if (buffer.remaining () + pos >= buffer.capacity ()) {		    // ok, we did no progress, abort, client is sending 		    // too long lines. 		    // TODO: perhaps grow buffer....		    throw new RequestLineTooLongException ();		} else {		    // set back position so the next read aligns...		    buffer.position (fullPosition);		}			    } else {		// ok, some data handled, make space for more.		buffer.compact ();		startParseAt = 0;	    }	} else {	    setState ();	    	    unregister ();	    reader.httpHeaderRead (header, buffer, 				   keepalive, ischunked, dataSize);	}    }    @Override protected int getSocketOperations () {	return 	    buffer != null && buffer.hasRemaining () ? 0 : SelectionKey.OP_READ;    }    private void setState () {	dataSize = -1;	String cl = header.getHeader ("Content-Length");	if (cl != null) {	    try {		dataSize = Long.parseLong (cl);	    } catch (NumberFormatException e) {		dataSize = -1;	    }	}	String con = header.getHeader ("Connection");	// Netscape specific header...	String pcon = header.getHeader ("Proxy-Connection");	if (con != null && con.equalsIgnoreCase ("close"))	    setKeepAlive (false);	if (keepalive && pcon != null && pcon.equalsIgnoreCase ("close"))	    setKeepAlive (false);		if (header.isResponse ()) {	    if (header.getResponseHTTPVersion ().equals ("HTTP/1.1")) {		String chunked = header.getHeader ("Transfer-Encoding");		setKeepAlive (true);		ischunked = false;				if (chunked != null && chunked.equalsIgnoreCase ("chunked")) {		    /* If we handle chunked data we must read the whole page		     * before continuing, since the chunk footer must be 		     * appended to the header (read the RFC)...		     * 		     * As of RFC 2616 this is not true anymore...		     * this means that we throw away footers and it is legal.		     */		    ischunked = true;		    header.removeHeader ("Content-Length");		    dataSize = -1;		}	    } else {		setKeepAlive (false);	    }	    	    if (!(dataSize > -1 || ischunked))		setKeepAlive (false);	} else {	    String httpVersion = header.getHTTPVersion ();	    if (httpVersion != null && httpVersion.equals ("HTTP/1.1")) {		String chunked = header.getHeader ("Transfer-Encoding");		if (chunked != null && chunked.equalsIgnoreCase ("chunked")) {		    ischunked = true;		    header.removeHeader ("Content-Length");		    dataSize = -1;		}	    }	}    }    /** read the data from the buffer and try to build a http header.     *      * @return true if a full header was read, false if more data is needed.     */    private boolean handleBuffer () throws IOException {	if (!request && header == null) {	    if (!verifyResponse ())		return true;	}	LineReader lr = new LineReader (strictHttp);	while (!headerRead && buffer.hasRemaining ())	    lr.readLine (buffer, this);	return headerRead;    }    /** Verify that the response starts with "HTTP/"      *  Failure to verify response => treat all of data as content = HTTP/0.9.     */    private boolean verifyResponse () throws IOException {		// some broken web servers (apache/2.0.4x) send multiple last-chunks	if (buffer.remaining () > 4 && matchBuffer (EXTRA_LAST_CHUNK)) {	    Logger log = getLogger ();	    log.logWarn ("Found a last-chunk, trying to ignore it.");	    buffer.position (buffer.position () + EXTRA_LAST_CHUNK.capacity ());	    return verifyResponse ();	}	if (buffer.remaining () > 4 && !matchBuffer (HTTP_IDENTIFIER)) {	    Logger log = getLogger ();	    log.logWarn ("http response header with odd start:" + 			 getBufferStartString (5));	    header = new HttpHeader ();	    header.setStatusLine ("HTTP/1.1 200 OK");	    header.setHeader ("Connection", "close");	    return true;	}	return true;    }    private boolean matchBuffer (ByteBuffer test) {	int len = test.remaining ();	if (buffer.remaining () < len)	    return false;	int pos = buffer.position ();	for (int i = 0; i < len; i++)	    if (buffer.get (pos + i) != test.get (i))		return false;	return true;    }    private String getBufferStartString (int size) {	try {	    byte[] arr = new byte[size];	    buffer.get (arr);	    return new String (arr, "ASCII");	} catch (UnsupportedEncodingException e) {	    return "unable to get ASCII: " + e.toString ();	}    }            /** Handle a newly read line. */    public void lineRead (String line) throws IOException {	if (line.length () == 0) {	    headerRead = header != null;	    return;	}	if (header == null) {	    header = new HttpHeader ();	    header.setRequestLine (line);	    headerRead = false;	    return;	}	if (header.isDot9Request ()) {	    headerRead = true;	    return;	}	char c;	if (header.size () == 0 &&	    line.length () > 0 && 	    ((c = line.charAt (0)) == ' ' || c == '\t')) {	    header.setReasonPhrase (header.getReasonPhrase () + line);	    headerRead = false;	    return;	} 	readHeader (line);	headerRead = false;    }    public void readHeader (String msg) throws IOException {	if (msg == null) 	    throw (new IOException ("Couldnt read headers, connection must be closed"));	char c = msg.charAt (0);	if (c == ' ' || c == '\t' || append) {	    if (head != null) {		head.append (msg);		append = checkQuotes (head.getValue ());	    } else {		throw (new IOException ("Malformed header from: " + 					channel.socket ().getInetAddress () + 					", msg: " + msg));	    }	    return;	}	int i = msg.indexOf (':');	    	if (i < 0) {	    switch (msg.charAt (0)) {	    case 'h':	    case 'H':		if (msg.toLowerCase ().startsWith ("http/")) {		    /* ignoring header since it looks		     * like a duplicate responseline		     */		    return;		}		// fallthrough	    default:		throw (new IOException ("Malformed header:" + msg));	    }	}	int j = i;	while (j > 0 && ((c = msg.charAt (j - 1)) == ' ' || c == '\t'))	    j--;	// ok, the header may be empty, so trim away whites.	String value = msg.substring (i + 1);		/* there are some sites with broken headers	 * like http://docs1.excite.com/functions.js	 * which returns lines such as this (20040416) /robo	 * msg is: 'Cache-control: must-revalidate"'	 * so we only check for append when in strict mode...	 */	if (strictHttp) 	    append = checkQuotes (value);	if (!append)	    value = value.trim ();	head = new Header (msg.substring (0, j), value);	header.addHeader (head);    }    private boolean checkQuotes (String v) {	int q = v.indexOf ('"');	if (q == -1)	    return false;	boolean halfquote = false;	int l = v.length ();	for (; q < l; q++) {	    char c = v.charAt (q);	    switch (c) {	    case '\\':		q++;    // skip one...		break;	    case '"':		halfquote = !halfquote;		break;	    }	}	return halfquote;    }    /** Set the keep alive value to currentkeepalive & keepalive     * @param keepalive the new keepalive value.     */    private void setKeepAlive (boolean keepalive) {	this.keepalive = (this.keepalive && keepalive);    }}

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?