You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by co...@apache.org on 2008/04/23 19:34:23 UTC

svn commit: r650949 - /tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/tomcat/util/http/Http11Parser.java

Author: costin
Date: Wed Apr 23 10:34:22 2008
New Revision: 650949

URL: http://svn.apache.org/viewvc?rev=650949&view=rev
Log:
Extracted from apr and nio connectors, transformed to completely non-blocking, independent of the io.


Added:
    tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/tomcat/util/http/Http11Parser.java   (with props)

Added: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/tomcat/util/http/Http11Parser.java
URL: http://svn.apache.org/viewvc/tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/tomcat/util/http/Http11Parser.java?rev=650949&view=auto
==============================================================================
--- tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/tomcat/util/http/Http11Parser.java (added)
+++ tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/tomcat/util/http/Http11Parser.java Wed Apr 23 10:34:22 2008
@@ -0,0 +1,761 @@
+/*
+ *  Licensed 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.
+ */
+package org.apache.tomcat.util.http;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.tomcat.util.buf.MessageBytes;
+
+/**
+ * Non-blocking parser for request and response line and headers. 
+ * 
+ * This could/should replace the parsing parts of InternalAprInputBuffer, 
+ * InternalNioInputBuffer, but the main goal is to be used in non-blocking
+ * client code.
+ * 
+ * All parse methods will return a negative number if more data is needed 
+ * and leave the buffer position/limit unchanged. After more data is 
+ * available, call again setBuffer() and the same parse method. If enough
+ * data ia available, 'pos' will be moved to the first byte after the 
+ * parsed data.
+ * 
+ * The next get() will be a header (after parseRequestLine, 
+ * parseResponseLine, parseHeader), a LF after the last parseHeader, or
+ * the first byte of the payload for parseRequest()/parseResponse().
+ * 
+ * TODO: use a ByteChunk instead of byte[], enhance methods using ByteBuffer
+ * 
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ * @author Costin Manolache 
+ */
+public class Http11Parser {
+
+    /**
+     * CRLF.
+     */
+    public static final String CRLF = "\r\n";
+    
+    /**
+     * CR.
+     */
+    public static final byte CR = (byte) '\r';
+
+
+    /**
+     * LF.
+     */
+    public static final byte LF = (byte) '\n';
+
+
+    /**
+     * SP.
+     */
+    public static final byte SP = (byte) ' ';
+
+
+    /**
+     * HT.
+     */
+    public static final byte HT = (byte) '\t';
+
+
+    /**
+     * COLON.
+     */
+    public static final byte COLON = (byte) ':';
+    
+    /**
+     * SEMI_COLON.
+     */
+    public static final byte SEMI_COLON = (byte) ';';
+
+    /**
+     * 'A'.
+     */
+    public static final byte A = (byte) 'A';
+
+
+    /**
+     * 'a'.
+     */
+    public static final byte a = (byte) 'a';
+
+
+    /**
+     * 'Z'.
+     */
+    public static final byte Z = (byte) 'Z';
+    
+
+    /**
+     * Lower case offset.
+     */
+    public static final byte LC_OFFSET = A - a;
+
+    /**
+     * '?'.
+     */
+    public static final byte QUESTION = (byte) '?';
+
+    /**
+     * HTTP/1.0.
+     */
+    public static final String HTTP_10 = "HTTP/1.0";
+
+    public static final byte[] _200_BYTES = {'2', '0', '0'};
+    
+    public static final byte[] _400_BYTES = {'4', '0' , '0'};
+
+    public static final byte[] _404_BYTES = { '4', '0', '4' }; 
+
+    /**
+     * HTTP/1.1.
+     */
+    public static final String HTTP_11 = "HTTP/1.1";
+
+    public static final byte[] HTTP_11_BYTES = HTTP_11.getBytes();
+
+    
+    // ============== Buffer ==================== 
+    
+    /** Last valid byte in the buf[]
+     */
+    public int lastValid; // limit - 1
+
+    /** Position in the buffer.
+     */
+    public int pos;
+
+    /**
+     * Pointer to the current read buffer.
+     */
+    public byte[] buf;
+
+    // TODO: same thing with ByteChunk, ByteBuffer
+    // Since ByteChunk can be easily wrapped as ByteBuffer - only second is 
+    // needed. Replace:
+    // buf[pos++] with bb.get();
+    // pos-- with bb.position(bb.position() - 1);
+    //ByteBuffer bb;
+    
+    
+    
+    // =====================================
+
+    // restart from this position
+    int lastParsed = 0;
+
+    // 0: parsing request line
+    // 1: parsing headers
+    // 2: request done
+    public int state = 0;
+    
+    public static int STATE_REQUEST_LINE = 0;
+    public static int STATE_HEADERS = 1;
+    public static int STATE_BODY = 2;
+
+        
+    public Http11Parser() {
+      state = STATE_REQUEST_LINE;
+    }
+
+    /**
+     * Must be called every time new data is read.
+     * 
+     * @param data
+     * @param start
+     * @param end
+     */
+    public void setBuffer(byte[] data, int start, int end) {
+        buf = data;
+        pos = start;
+        lastValid = end;
+    }
+
+    // ---------- Utilities ---------------
+    
+    public final int skipBlank() {
+      // Skipping blank lines
+      byte chr = 0;
+      do {
+        // Read new bytes if needed
+        if (pos >= lastValid) {
+          return -1;
+        }
+        chr = buf[pos++];
+      } while ((chr == CR) || (chr == LF));
+
+      pos--;
+      return pos;
+    }
+    
+    public final int readToDelim(MessageBytes res, byte delim) {
+      boolean space = false;
+      // Mark the current buffer position
+      int start = pos;
+      while (!space) {
+        if (pos >= lastValid) {
+          return -1;
+        }
+        if (buf[pos] == SP) {
+          space = true;
+          res.setBytes(buf, start, pos - start);
+        }
+        pos++;
+      }
+
+      return pos;
+    }
+
+    public final int readToDelimAndLowerCase(byte delim, boolean lower) {
+      boolean space = false;
+      while (!space) {
+        if (pos >= lastValid) {
+          return -1;
+        }
+        byte chr = buf[pos];
+        if (chr == delim || chr == SP) {
+          space = true;
+        }
+        if (lower && (chr >= A) && (chr <= Z)) {
+            buf[pos] = (byte) (chr - LC_OFFSET);
+        }
+        pos++;
+      }
+
+      return pos;
+    }
+
+    public final int readToEnd(MessageBytes res) {
+      int start = pos;
+      int endpos = 0;
+      boolean eol = false;
+      while (!eol) {
+        if (pos >= lastValid) {
+          return -1;
+        }
+        if (buf[pos] == CR) {
+          endpos = pos;
+        } else if (buf[pos] == LF) {
+          if (endpos == 0)
+            endpos = pos;
+          eol = true;
+        }
+        pos++;
+      }
+      if ((endpos - start) > 0) {
+        res.setBytes(buf, start, endpos - start);
+      } else {
+        res.setString("");
+      }
+      return pos;
+    }
+
+    public boolean skipSpace() {
+      boolean space = true;
+      while (space) {
+        if (pos >= lastValid) {
+          return false;
+        }
+        if ((buf[pos] == SP) || (buf[pos] == HT)) {
+          pos++;
+        } else {
+          space = false;
+        }
+      }
+      return true;
+    }
+    
+    
+    // ------------ Same utils, with ByteBuffer param ------------
+    // Currently used to evaluate the overhead.
+    
+    public final int skipBlank(ByteBuffer bb, int start) {
+      // Skipping blank lines
+      byte chr = 0;
+      do {
+        if (!bb.hasRemaining()) {
+          return -1;
+        }
+        chr = bb.get();
+      } while ((chr == CR) || (chr == LF));
+      return bb.position();
+    }
+    
+    public final int readToDelim(ByteBuffer bb,
+                                 MessageBytes res, 
+                                 byte delim) {
+      byte chr = 0;
+      // Mark the current buffer position
+      int start = bb.position();
+      while (true) {
+        if (!bb.hasRemaining()) {
+          return -1;
+        }
+        chr = bb.get();
+        if (chr == delim) {
+          res.setBytes(bb, start, bb.position() - start);
+          break;
+        }
+      }
+
+      return bb.position();
+    }
+
+    public final int readToDelimAndLowerCase(ByteBuffer bb,
+                                             byte delim, 
+                                             boolean lower) {
+      boolean space = false;
+      byte chr = 0;
+      while (!space) {
+        if (!bb.hasRemaining()) {
+          return -1;
+        }
+        chr = bb.get();
+        if (chr == delim) {
+          space = true;
+        }
+        if (lower && (chr >= A) && (chr <= Z)) {
+          bb.put(bb.position() - 1, 
+              (byte) (chr - LC_OFFSET));
+        }
+      }
+      return bb.position();
+    }
+
+    public final int readToEnd(ByteBuffer bb, MessageBytes res) {
+      int start = bb.position();
+      int endpos = 0;
+      boolean eol = false;
+      byte chr = 0;
+      
+      while (!eol) {
+        if (!bb.hasRemaining()) {
+          return -1;
+        }
+        chr = bb.get();
+        if (chr == CR) {
+          endpos = bb.position();
+        } else if (buf[pos] == LF) {
+          if (endpos == 0)
+            endpos = bb.position();
+          eol = true;
+        }
+      }
+      if ((endpos - start) > 0) {
+        res.setBytes(bb, start, endpos - start);
+      } else {
+        res.setString("");
+      }
+      return bb.position();
+    }
+
+    public boolean skipSpace(ByteBuffer bb) {
+      boolean space = true;
+      while (space) {
+        if (!bb.hasRemaining()) {
+          return false;
+        }
+        byte chr = bb.get();
+        if ((chr == SP) || (chr == HT)) {
+          //
+        } else {
+          space = false;
+          bb.position(bb.position() -1); // move back
+        }
+      }
+      return true;
+    }
+    
+    
+    // ---------- Parsing request/response line, headers -------- 
+
+    public boolean parseRequest(MessageBytes methodMB, 
+                                MessageBytes unparsedURIMB,
+                                MessageBytes queryMB,
+                                MessageBytes uriMB,
+                                MessageBytes protoMB,
+                                MimeHeaders headers) throws IOException {
+      if (state == STATE_REQUEST_LINE) {
+        boolean res = 
+          parseRequestLine(methodMB, unparsedURIMB, queryMB, uriMB, protoMB);
+        if (!res) {
+          return false;
+        }
+        state = STATE_HEADERS;
+      }
+      if (state == STATE_HEADERS) {
+        int res = parseHeaders(headers);
+        if (res < 0) {
+          return false;
+        }
+        state = STATE_BODY;
+      }
+      
+      return true;
+    }
+
+    public boolean parseResponse(MessageBytes status,
+                                 MessageBytes msg,
+                                 MessageBytes protoMB,
+                                 MimeHeaders headers) throws IOException {
+      if (state == STATE_REQUEST_LINE) {
+        boolean res = 
+          parseResponseLine(protoMB, status, msg);
+        if (!res) {
+          return false;
+        }
+        state = STATE_HEADERS;
+      }
+      if (state == STATE_HEADERS) {
+        int res = parseHeaders(headers);
+        if (res < 0) {
+          return false;
+        }
+      }
+      
+      return true;
+    }
+    
+    public int parseHeaders(MimeHeaders headers)
+        throws IOException {
+
+      while (true) {
+        int newPos = parseHeader(headers);
+        if (newPos < 0) {
+          return -1; // need more data
+        }
+        pos = newPos;
+        byte chr = buf[pos];
+        if (chr == CR) {
+          pos++;
+          chr = buf[pos];
+        }
+        if (chr == LF) {
+          pos++;
+          chr = buf[pos];
+          //end = pos;
+          state = STATE_BODY;
+          return pos;
+        }
+      }
+    }
+    
+    
+    /**
+     * Parse an HTTP header, non-blocking
+     * 
+     * @param headers a new header will be added if found.
+     * @return -1 if more data is needed, pos remains at the end of the
+     *  previously read header + 1.
+     *         pos - start of the new header, of CR/LF if last header. 
+     */
+    public int parseHeader(MimeHeaders headers)
+          throws IOException {
+
+        // Check for blank line
+        byte chr = 0;
+        while (true) {
+            if (pos >= lastValid) {
+              return -1;
+            }
+            chr = buf[pos];
+            if ((chr == CR) || (chr == LF)) {
+                if (chr == LF) {
+                    pos++;
+                    return pos;
+                }
+            } else {
+                break;
+            }
+            pos++;
+        }
+
+        // Mark the current buffer position
+        int start = pos;
+        
+        int startName = pos;
+        int newPos = readToDelimAndLowerCase(COLON, false);
+        if (newPos < 0) {
+          return -1;
+        }
+        pos = newPos;
+        int endName = pos - 1;
+        
+        // Mark the current buffer position
+        start = pos;
+        int realPos = pos;
+
+        //
+        // Reading the header value (which can be spanned over multiple lines)
+        //
+
+        boolean eol = false;
+        boolean validLine = true;
+
+        while (validLine) {
+          if (!skipSpace()) { 
+            return -1;
+          }
+          int lastSignificantChar = realPos;
+
+            // Reading bytes until the end of the line
+            while (!eol) {
+
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                  return -1;
+                }
+
+                if (buf[pos] == CR) {
+                } else if (buf[pos] == LF) {
+                    eol = true;
+                } else if (buf[pos] == SP) {
+                    buf[realPos] = buf[pos];
+                    realPos++;
+                } else {
+                    buf[realPos] = buf[pos];
+                    
+                    // TODO: reentrant modification ?
+                    buf[pos] = SP; // so next time we skip it, if we parse 
+                    // again this line                    
+                    realPos++;
+                    lastSignificantChar = realPos;
+                }
+
+                pos++;
+
+            }
+
+            realPos = lastSignificantChar;
+
+            // Checking the first character of the new line. If the character
+            // is a LWS, then it's a multiline header
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+              return -1;
+            }
+
+            chr = buf[pos];
+            if ((chr != SP) && (chr != HT)) {
+                validLine = false;
+            } else {
+                eol = false;
+                // Copying one extra space in the buffer (since there must
+                // be at least one space inserted between the lines)
+                buf[realPos] = chr;
+                realPos++;
+            }
+
+        }
+
+        // Set the header value
+        MessageBytes headerValue = headers.addValue(buf, startName, 
+            endName - startName);
+        headerValue.setBytes(buf, start, realPos - start);
+
+        return pos;
+    }
+
+    
+    /**
+     * Read the request line. This function is meant to be used during the 
+     * HTTP request header parsing. Do NOT attempt to read the request body 
+     * using it.
+     *
+     * @throws IOException If an exception occurs during the underlying socket
+     * read operations, or if the given buffer is not big enough to accomodate
+     * the whole line.
+     */
+    public boolean parseRequestLine(MessageBytes methodMB, 
+                                    MessageBytes unparsedURIMB,
+                                    MessageBytes queryMB,
+                                    MessageBytes uriMB,
+                                    MessageBytes protoMB)
+            throws IOException {
+
+        int start = 0;
+        
+        lastParsed = pos;
+        state = STATE_REQUEST_LINE;
+        
+        // Skipping blank lines
+        byte chr = 0;
+        do {
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                return false;
+            }
+
+            chr = buf[pos++];
+
+        } while ((chr == CR) || (chr == LF));
+
+        pos--;
+
+        // Mark the current buffer position
+        start = pos;
+
+        //
+        // Reading the method name
+        // Method name is always US-ASCII
+        //
+        boolean space = false;
+
+        while (!space) {
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                return false;
+            }
+
+            if (buf[pos] == SP) {
+                space = true;
+                methodMB.setBytes(buf, start, pos - start);
+            }
+            pos++;
+        }
+
+        // Mark the current buffer position
+        start = pos;
+        int end = 0;
+        int questionPos = -1;
+
+        // Reading the URI
+        space = false;
+        boolean eol = false;
+        while (!space) {
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                return false;
+            }
+            if (buf[pos] == SP) {
+                space = true;
+                end = pos;
+            } else if ((buf[pos] == CR) 
+                       || (buf[pos] == LF)) {
+                // HTTP/0.9 style request
+                eol = true;
+                space = true;
+                end = pos;
+            } else if ((buf[pos] == QUESTION) 
+                       && (questionPos == -1)) {
+                questionPos = pos;
+            }
+            pos++;
+        }
+
+        unparsedURIMB.setBytes(buf, start, end - start);
+        if (questionPos >= 0) {
+            queryMB.setBytes(buf, questionPos + 1, 
+                                           end - questionPos - 1);
+            uriMB.setBytes(buf, start, questionPos - start);
+        } else {
+            uriMB.setBytes(buf, start, end - start);
+        }
+
+        // Mark the current buffer position
+        start = pos;
+        end = 0;
+
+        // Reading the protocol. Protocol is always US-ASCII
+        while (!eol) {
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                return false;
+            }
+            if (buf[pos] == CR) {
+                end = pos;
+            } else if (buf[pos] == LF) {
+                if (end == 0)
+                    end = pos;
+                eol = true;
+            }
+            pos++;
+        }
+
+        if ((end - start) > 0) {
+            protoMB.setBytes(buf, start, end - start);
+        } else {
+            protoMB.setString("");
+        }
+        
+        state = STATE_HEADERS;
+        lastParsed = pos;
+        return true;
+    }
+    
+
+    public boolean parseResponseLine(MessageBytes protoMB,
+                                 MessageBytes statusCode,
+                                 MessageBytes status)
+            throws IOException {
+      int res = skipBlank();
+      if (res < 0) {
+        return false;
+      }
+
+      res = readToDelim(protoMB, SP);
+      if (res < 0) {
+        return false;
+      }
+
+      res = readToDelim(statusCode, SP);
+      if (res < 0) {
+        return false;
+      }
+      
+      res = readToEnd(status);
+      if (res < 0) {
+        return false;
+      }
+
+      state = STATE_HEADERS;
+      return true;
+    }
+
+    /**
+     * Recycle the input buffer. This should be called when closing the 
+     * connection.
+     */
+    public void recycle() {
+        lastValid = 0;
+        pos = 0;
+        state = STATE_REQUEST_LINE;
+        buf = null;
+    }
+
+    /**
+     * End processing of current HTTP request.
+     * Note: All bytes of the current request should have been already 
+     * consumed. This method only resets all the pointers so that we are ready
+     * to parse the next HTTP request.
+     */
+    public void nextRequest() {
+        if (pos < lastValid) {
+            System.arraycopy(buf, pos, buf, 0, lastValid - pos);
+            lastValid -= pos;
+        } else {
+            lastValid = 0;
+        }
+        pos = 0;
+        state = STATE_REQUEST_LINE;
+    }
+
+    public String toString() {
+      return state + " " + pos + " " + lastValid + " " + 
+        new String(buf, 0, lastValid);
+    }
+}

Propchange: tomcat/sandbox/tomcat-lite/coyote-extensions/org/apache/tomcat/util/http/Http11Parser.java
------------------------------------------------------------------------------
    svn:eol-style = native



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org