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 + -
显示快捷键?