Tomcat源碼分析(一):HTTP協(xié)議解析

前幾天面試阿里轮锥,面試官問我如何解析HTTP協(xié)議,我大概說了一下的思路毡琉,他最后得出的結(jié)論是我對HTTP協(xié)議不了解铁瞒,讓我很受打擊∥ψ蹋回來看《深入剖析Tomcat》慧耍,研究一下Tomcat是如何解析HTTP協(xié)議的

1. 環(huán)境說明

  • 《深入剖析Tomcat》是基于tomcat-4.1.12進行分析,這個版本在2002年發(fā)布,可以說是老古董了丐谋。不過就學習而言還是很好的工具.
  • Http協(xié)議的解析在連接器(connector) 中進行芍碧,連接器是一個獨立的模塊,可以被插入到容器中号俐,tomcat-4.1.12里提供了默認連接器泌豆,但已被標注為過時。

2. 源碼分析

2.1 連接器

默認連接器在org.apache.catalina.connector.http包下,它實現(xiàn)了Connector接口和Runnable接口吏饿。分析的入口在run方法中

2.1.1 run()

public void run() {
    while (!stopped) {
        //從ServerSocket中接受下一個進入的連接
        Socket socket = null;
        try {
            serverSocket.accept()");
            socket = serverSocket.accept();
            if (connectionTimeout > 0)
                socket.setSoTimeout(connectionTimeout);
            socket.setTcpNoDelay(tcpNoDelay);//這個有點意思踪危,關(guān)閉TCP延遲確認
        } catch (AccessControlException ace) {
            log("socket accept security exception", ace);
            continue;
        } catch (IOException e) {
            try {
                // 如果重新打開失敗蔬浙,退出
                synchronized (threadSync) {
                    if (started && !stopped)
                        log("accept error: ", e);
                    if (!stopped) {
                        serverSocket.close();
                        serverSocket = open();
                    }
                }
            } catch (IOException ioe) {
                log("socket reopen, io problem: ", ioe);
                break;
            } catch (KeyStoreException kse) {
                log("socket reopen, keystore problem: ", kse);
                break;
            } catch (NoSuchAlgorithmException nsae) {
                log("socket reopen, keystore algorithm problem: ", nsae);
                break;
            } catch (CertificateException ce) {
                log("socket reopen, certificate problem: ", ce);
                break;
            } catch (UnrecoverableKeyException uke) {
                log("socket reopen, unrecoverable key: ", uke);
                break;
            } catch (KeyManagementException kme) {
                log("socket reopen, key management problem: ", kme);
                break;
            }
            continue;
        }
        // 把socket給適當?shù)奶幚砥?        HttpProcessor processor = createProcessor();//2.1.2
        if (processor == null) {
            try {
                log(sm.getString("httpConnector.noProcessor"));
                socket.close();
            } catch (IOException e) {
                ;
            }
            continue;
        }
        processor.assign(socket);//2.2.3
        // The processor will recycle itself when it finishes
    }
    synchronized (threadSync) {
        threadSync.notifyAll();
    }
}

2.1.2 createProcessor()

具體的處理將在HttpProcessor中進行,一個連接器會創(chuàng)建多個處理器,連接器的數(shù)量通過maxProcessorsminProcessors進行控制贞远。今天的重點在http協(xié)議的解析畴博,創(chuàng)建HttpProcessor的一些細節(jié)就不說了

private HttpProcessor createProcessor() {
    synchronized (processors) {
        if (processors.size() > 0) {
            return ((HttpProcessor) processors.pop());
        }
        if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
            return (newProcessor());
        } else {
            if (maxProcessors < 0) {
                return (newProcessor());
            } else {
                return (null);
            }
        }
    }
}

2.2 處理器

HttpProcessor在獨立的線程中對請求進行處理,連接器將請求分配給處理器(調(diào)用處理器的assign()方法)蓝仲,處理器處理完成后將進行回收重復利用

2.2.1 run()

HttpProcessor同樣實現(xiàn)了Runnable接口绎晃,在后臺一直運行(被設(shè)置為守護線程),等待處理請求

public void run() {
    while (!stopped) {
        // 等待下一個socket
        Socket socket = await();//2.2.2
        if (socket == null)
            continue;
        // 處理請求
        try {
            process(socket);//2.2.4
        } catch (Throwable t) {
            log("process.invoke", t);
        }
        // 完成此次請求
        connector.recycle(this);
    }
    synchronized (threadSync) {
        threadSync.notifyAll();
    }
}

2.2.2 await()

await()監(jiān)視available變量杂曲,如果沒有新的請求,就進入阻塞狀態(tài)袁余,同時run()方法也會被阻塞

private synchronized Socket await() {
    // Wait for the Connector to provide a new Socket
    while (!available) {
        try {
            wait();
        } catch (InterruptedException e) {
        }
    }
    // Notify the Connector that we have received this Socket
    Socket socket = this.socket;
    available = false;
    notifyAll();
    if ((debug >= 1) && (socket != null))
        log("  The incoming request has been awaited");
    return (socket);
}

2.2.3 assign(Socket socket)

連接器調(diào)用assign方法分配請求擎勘,它會喚醒阻塞的線程.這實際上是一個生產(chǎn)者-,消費者模型颖榜,通過available變量棚饵,將請求從連接器傳遞到處理器。但這個實現(xiàn)并不優(yōu)雅掩完,并且效率也不高

synchronized void assign(Socket socket) {
    // Wait for the Processor to get the previous Socket
    while (available) {
        try {
            wait();
        } catch (InterruptedException e) {
        }
    }
    // Store the newly available Socket and notify our thread
    this.socket = socket;
    available = true;
    notifyAll();
    if ((debug >= 1) && (socket != null))
        log(" An incoming request is being assigned");
}

2.2.4 process(Socket socket)

process(Socket socket)方法對請求進行處理,此處省略了很多

private void process(Socket socket) {
    boolean ok = true;
    boolean finishResponse = true;
    SocketInputStream input = null;
    OutputStream output = null;
    // 構(gòu)造和初始化需要的對象
    try {
        input = new SocketInputStream(socket.getInputStream(),
                                      connector.getBufferSize());
    } catch (Exception e) {
        log("process.create", e);
        ok = false;
    }
    keepAlive = true;
    while (!stopped && ok && keepAlive) {
        finishResponse = true;
        try {
            //此處的request,response是循環(huán)利用的
            request.setStream(input);
            request.setResponse(response);
            output = socket.getOutputStream();
            response.setStream(output);
            response.setRequest(request);
            ((HttpServletResponse) response.getResponse()).setHeader
                ("Server", SERVER_INFO);
        } catch (Exception e) {
            //...
        }
        // 解析請求
        try {
            if (ok) {
                parseConnection(socket);//2.2.5
                parseRequest(input, output);//2.2.6
                if (!request.getRequest().getProtocol().startsWith("HTTP/0"))
                    parseHeaders(input);//2.2.8
                if (http11) {
                    // 若在請求頭中發(fā)現(xiàn)"EXpect:100-continue",則設(shè)置sendAck為true
                    //ackRequest方法檢查sendAck的值和是否允許分塊噪漾,如果為true向客戶端發(fā)送HTTP/1.1 100 Continue\r\n\r\n
                    ackRequest(output);
                    // If the protocol is HTTP/1.1, chunking is allowed.
                    if (connector.isChunkingAllowed())
                        response.setAllowChunking(true);
                }
            }
        } catch (EOFException e) {
            //很可能client或server中的一方斷開連接
            ok = false;
            finishResponse = false;
        } catch (ServletException e) {
            //...
        } catch (InterruptedIOException e) {
            //...
        } catch (Exception e) {
            //...
        }
        try {
            ((HttpServletResponse) response).setHeader("Date", FastHttpDateFormat.getCurrentDate());
            if (ok) {
                connector.getContainer().invoke(request, response);//如果處理正常調(diào)用容器的invoke方法
            }
        } catch (ServletException e) {
            //...
        } catch (InterruptedIOException e) {
            //...
        } catch (Throwable e) {
            //...
        }
        // 完成處理請求
        if (finishResponse) {
            //省略...
            //主要是調(diào)用response.finishResponse();
        }
        //必須檢查Connection是否被設(shè)置為close或者在HTTP/1.0下
        if ( "close".equals(response.getHeader("Connection")) ) {
            keepAlive = false;
        }
        // 如果keepAlive為true并且解析沒有發(fā)生錯誤,則繼續(xù)while循環(huán)
        status = Constants.PROCESSOR_IDLE;
        // 回收request和response對象
        request.recycle();
        response.recycle();
    }
    try {
        shutdownInput(input);
        socket.close();
    } catch (IOException e) {
        //...
    } catch (Throwable e) {
        //...
    }
    socket = null;
}

2.2.5 parseConnection(Socket socket)

解析連接信息且蓬,獲取Internet地址欣硼,檢查是否使用代理

private void parseConnection(Socket socket)
    throws IOException, ServletException {
    if (debug >= 2)
        log("  parseConnection: address=" + socket.getInetAddress() +
            ", port=" + connector.getPort());
    ((HttpRequestImpl) request).setInet(socket.getInetAddress());
    if (proxyPort != 0)
        request.setServerPort(proxyPort);
    else
        request.setServerPort(serverPort);
    request.setSocket(socket);
}

2.2.6 parseRequest(SocketInputStream input, OutputStream output)

requestLine是一個HttpRequest實例,其中包含3個char[],分別對應method,uri,protocol.調(diào)用SocketInputStreamreadRequestLine()方法填充請求行恶阴,再獲得對應的請求方法诈胜,URI,協(xié)議版本,(查詢參數(shù),session ID)

private void parseRequest(SocketInputStream input, OutputStream output)
    throws IOException, ServletException {
    // 解析請求行
    input.readRequestLine(requestLine);//2.2.7
    status = Constants.PROCESSOR_ACTIVE;
    String method = new String(requestLine.method, 0, requestLine.methodEnd);//獲得請求方法
    String uri = null;
    String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);//獲得協(xié)議版本信息
    if (protocol.length() == 0)
        protocol = "HTTP/0.9";
    // 如果是HTTP/1.1需要在解析請求后保持連接
    if ( protocol.equals("HTTP/1.1") ) {
        http11 = true;
        sendAck = false;
    } else {
        http11 = false;
        sendAck = false;
        // 對于HTTP/1.0, 默認不保持連接冯事,除非指定Connection:Keep-Alive
        keepAlive = false;
    }
    // 驗證請求行
    if (method.length() < 1) {
        throw new ServletException(sm.getString("httpProcessor.parseRequest.method"));
    } else if (requestLine.uriEnd < 1) {
        throw new ServletException(sm.getString("httpProcessor.parseRequest.uri"));
    }
    // 解析URI上的查詢參數(shù)
    int question = requestLine.indexOf("?");
    if (question >= 0) {
        request.setQueryString(new String(requestLine.uri, question + 1,requestLine.uriEnd - question - 1));//設(shè)置查詢參數(shù)
        if (debug >= 1)
            log(" Query string is " +
                ((HttpServletRequest) request.getRequest())
                .getQueryString());
        uri = new String(requestLine.uri, 0, question);//獲得URI
    } else {
        request.setQueryString(null);
        uri = new String(requestLine.uri, 0, requestLine.uriEnd);
    }
    // Checking for an absolute URI (with the HTTP protocol)
    //檢驗絕對URI路徑和HTTP協(xié)議
    if (!uri.startsWith("/")) {
        int pos = uri.indexOf("://");
        // 解析協(xié)議和主機名
        if (pos != -1) {
            pos = uri.indexOf('/', pos + 3);
            if (pos == -1) {
                uri = "";
            } else {
                uri = uri.substring(pos);
            }
        }
    }
    // 從請求URI解析session ID
    int semicolon = uri.indexOf(match);//match=";jsessionid="0
    if (semicolon >= 0) {
        String rest = uri.substring(semicolon + match.length());
        int semicolon2 = rest.indexOf(';');
        if (semicolon2 >= 0) {
            request.setRequestedSessionId(rest.substring(0, semicolon2));//設(shè)置session ID
            rest = rest.substring(semicolon2);
        } else {
            request.setRequestedSessionId(rest);
            rest = "";
        }
        request.setRequestedSessionURL(true);
        uri = uri.substring(0, semicolon) + rest;
        if (debug >= 1)
            log(" Requested URL session id is " +
                ((HttpServletRequest) request.getRequest())
                .getRequestedSessionId());
    } else {
        request.setRequestedSessionId(null);
        request.setRequestedSessionURL(false);
    }
    //修正RUI(使用字符串操作)
    String normalizedUri = normalize(uri);
    if (debug >= 1) log("Normalized: '" + uri + "' to '" + normalizedUri + "'");
    // 設(shè)置請求屬性
    ((HttpRequest) request).setMethod(method);//設(shè)置請求方法
    request.setProtocol(protocol);//設(shè)置協(xié)議版本
    if (normalizedUri != null) {
        ((HttpRequest) request).setRequestURI(normalizedUri);
    } else {
        ((HttpRequest) request).setRequestURI(uri);
    }
    request.setSecure(connector.getSecure());
    request.setScheme(connector.getScheme());
    if (normalizedUri == null) {
        log(" Invalid request URI: '" + uri + "'");
        throw new ServletException("Invalid URI: " + uri + "'");
    }
    if (debug >= 1)
        log(" Request is '" + method + "' for '" + uri +
            "' with protocol '" + protocol + "'");
}

2.2.7 readRequestLine(HttpRequestLine requestLine)

readRequestLine方法會分別填充請求行,URI,協(xié)議版本

public void readRequestLine(HttpRequestLine requestLine)
    throws IOException {
    // 檢查是否已回收
    if (requestLine.methodEnd != 0)
        requestLine.recycle();
    // 檢查空白行
    int chr = 0;
    do { // 跳過 CR(\r) 或 LF(\n)
        try {
            chr = read();
        } catch (IOException e) {
            chr = -1;
        }
    } while ((chr == CR) || (chr == LF));
    if (chr == -1)
        throw new EOFException (sm.getString("requestStream.readline.error"));
    pos--;
    // 讀取方法名
    int maxRead = requestLine.method.length;//這里的char[]數(shù)組的長度為8
    int readStart = pos;
    int readCount = 0;
    boolean space = false;
    //讀取到空格說明方法名已解析完成
    while (!space) {
        // 如果char[]已滿焦匈,將容量翻倍
        if (readCount >= maxRead) {
            if ((2 * maxRead) <= HttpRequestLine.MAX_METHOD_SIZE) {
                char[] newBuffer = new char[2 * maxRead];
                System.arraycopy(requestLine.method, 0, newBuffer, 0,
                                 maxRead);
                requestLine.method = newBuffer;
                maxRead = requestLine.method.length;
            } else {
                throw new IOException
                    (sm.getString("requestStream.readline.toolong"));
            }
        }
        // 檢查是否讀取到末尾
        if (pos >= count) {
            int val = read();
            if (val == -1) {
                throw new IOException
                    (sm.getString("requestStream.readline.error"));
            }
            pos = 0;
            readStart = 0;
        }
        // 檢查是否讀取到空格
        if (buf[pos] == SP) {
            space = true;
        }
        //填充char[] method
        requestLine.method[readCount] = (char) buf[pos];
        readCount++;
        pos++;
    }
    requestLine.methodEnd = readCount - 1;//設(shè)置請求方法結(jié)束位置
    // 解析URI
    maxRead = requestLine.uri.length;
    readStart = pos;
    readCount = 0;
    space = false;
    boolean eol = false;
    while (!space) {
        if (readCount >= maxRead) {
            if ((2 * maxRead) <= HttpRequestLine.MAX_URI_SIZE) {
                char[] newBuffer = new char[2 * maxRead];
                System.arraycopy(requestLine.uri, 0, newBuffer, 0,
                                 maxRead);
                requestLine.uri = newBuffer;
                maxRead = requestLine.uri.length;
            } else {
                throw new IOException(sm.getString("requestStream.readline.toolong"));
            }
        }
        // 檢查是否讀取到末尾
        if (pos >= count) {
            int val = read();
            if (val == -1)
                throw new IOException(sm.getString("requestStream.readline.error"));
            pos = 0;
            readStart = 0;
        }
        // 檢查是否讀取到空格
        if (buf[pos] == SP) {
            space = true;
        } else if ((buf[pos] == CR) || (buf[pos] == LF)) {
            // HTTP/0.9 風格的請求
            eol = true;
            space = true;
        }
        //填充 char[] uri
        requestLine.uri[readCount] = (char) buf[pos];
        readCount++;
        pos++;
    }
    requestLine.uriEnd = readCount - 1;//設(shè)置uri結(jié)束位置
    // 解析協(xié)議
    maxRead = requestLine.protocol.length;
    readStart = pos;
    readCount = 0;
    //是否結(jié)束
    while (!eol) {
        if (readCount >= maxRead) {
            if ((2 * maxRead) <= HttpRequestLine.MAX_PROTOCOL_SIZE) {
                char[] newBuffer = new char[2 * maxRead];
                System.arraycopy(requestLine.protocol, 0, newBuffer, 0,
                                 maxRead);
                requestLine.protocol = newBuffer;
                maxRead = requestLine.protocol.length;
            } else {
                throw new IOException(sm.getString("requestStream.readline.toolong"));
            }
        }
         // 檢查是否讀取到末尾
        if (pos >= count) {
            int val = read();
            if (val == -1)
                throw new IOException(sm.getString("requestStream.readline.error"));
            pos = 0;
            readStart = 0;
        }
        //是否結(jié)束
        if (buf[pos] == CR) {
            // 跳過\r
        } else if (buf[pos] == LF) {
            eol = true;
        } else {
            //填充char[] protocol
            requestLine.protocol[readCount] = (char) buf[pos];
            readCount++;
        }
        pos++;
    }
    requestLine.protocolEnd = readCount;//設(shè)置協(xié)議版本結(jié)束位置
}

2.2.8 parseHeaders(SocketInputStream input)

一個HttpHeader包含一個name數(shù)組和value數(shù)組,通過SocketInputStream中的readHeader方法填充HttpHeader對象,整個過程和readRequestLine類似.
通過HttpHeader對象昵仅,設(shè)置request對象對應的屬性

private void parseHeaders(SocketInputStream input)
    throws IOException, ServletException {
    while (true) {
        HttpHeader header = request.allocateHeader();//分配一個HttpHeader對象缓熟,從對象池中
        // 解析請求頭
        input.readHeader(header);
        if (header.nameEnd == 0) {
            if (header.valueEnd == 0) {
                return;
            } else {
                throw new ServletException
                    (sm.getString("httpProcessor.parseHeaders.colon"));
            }
        }
        String value = new String(header.value, 0, header.valueEnd);//獲得value值
        if (debug >= 1)
            log(" Header " + new String(header.name, 0, header.nameEnd)+ " = " + value);
        // 設(shè)置對應的請求頭
        if (header.equals(DefaultHeaders.AUTHORIZATION_NAME)) {//authorization頭
            request.setAuthorization(value);
        } else if (header.equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {//accept-language頭
            parseAcceptLanguage(value);
        } else if (header.equals(DefaultHeaders.COOKIE_NAME)) {//cookie頭
            Cookie cookies[] = RequestUtil.parseCookieHeader(value);//將value解析成Cookie數(shù)組
            for (int i = 0; i < cookies.length; i++) {
                if (cookies[i].getName().equals
                    (Globals.SESSION_COOKIE_NAME)) {//判斷cookie名是否為JSESSIONID
                    if (!request.isRequestedSessionIdFromCookie()) {
                        // 只接受第一個session ID
                        request.setRequestedSessionId(cookies[i].getValue());//設(shè)置session ID
                        request.setRequestedSessionCookie(true);
                        request.setRequestedSessionURL(false);
                        if (debug >= 1)
                            log(" Requested cookie session id is " +
                                ((HttpServletRequest) request.getRequest())
                                .getRequestedSessionId());
                    }
                }
                if (debug >= 1)
                    log(" Adding cookie " + cookies[i].getName() + "=" +
                        cookies[i].getValue());
                request.addCookie(cookies[i]);//添加cookie到request對象
            }
        } else if (header.equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {//content-length頭
            int n = -1;
            try {
                n = Integer.parseInt(value);
            } catch (Exception e) {
                throw new ServletException
                    (sm.getString("httpProcessor.parseHeaders.contentLength"));
            }
            request.setContentLength(n);
        } else if (header.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {//content-type頭
            request.setContentType(value);
        } else if (header.equals(DefaultHeaders.HOST_NAME)) {//host頭
            int n = value.indexOf(':');
            if (n < 0) {
                if (connector.getScheme().equals("http")) {
                    request.setServerPort(80);//設(shè)置http協(xié)議端口
                } else if (connector.getScheme().equals("https")) {
                    request.setServerPort(443);//設(shè)置https協(xié)議端口
                }
                if (proxyName != null)
                    request.setServerName(proxyName);
                else
                    request.setServerName(value);
            } else {
                if (proxyName != null)
                    request.setServerName(proxyName);
                else
                    request.setServerName(value.substring(0, n).trim());
                if (proxyPort != 0)
                    request.setServerPort(proxyPort);
                else {
                    int port = 80;
                    try {
                        port =Integer.parseInt(value.substring(n+1).trim());
                    } catch (Exception e) {
                        throw new ServletException
                            (sm.getString("httpProcessor.parseHeaders.portNumber"));
                    }
                    request.setServerPort(port);
                }
            }
        } else if (header.equals(DefaultHeaders.CONNECTION_NAME)) {//connection頭
            if (header.valueEquals(DefaultHeaders.CONNECTION_CLOSE_VALUE)) {//close值
                keepAlive = false;
                response.setHeader("Connection", "close");
            }
        } else if (header.equals(DefaultHeaders.EXPECT_NAME)) {//expect頭
            if (header.valueEquals(DefaultHeaders.EXPECT_100_VALUE))//100-continue值
                sendAck = true;
            else
                throw new ServletException
                    (sm.getString("httpProcessor.parseHeaders.unknownExpectation"));
        } else if (header.equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {//transfer-encoding頭
            //request.setTransferEncoding(header);
        }
        request.nextHeader();//讀取下一個請求頭
    }
}

2.3 Request對象

在調(diào)用getParameter,getParameterMap,getParameterNames,getParameterValues時會先調(diào)用parseParameters方法解析請求參數(shù)

2.3.1 parseParameters()

parseParameters方法將解析結(jié)果放入ParameterMap對象中;ParameterMap基礎(chǔ)自HashMap,添加了鎖定屬性,當被鎖定時不允許修改

protected void parseParameters() {
    if (parsed)//如果已解析直接返回
        return;
    ParameterMap results = parameters;//初始化ParameterMap對象
    if (results == null)
        results = new ParameterMap();
    results.setLocked(false);//解除鎖定
    String encoding = getCharacterEncoding();//獲得編碼信息
    if (encoding == null) encoding = "ISO-8859-1";//默認編碼
    // 解析查詢字符串中的參數(shù)
    String queryString = getQueryString();
    try {
        RequestUtil.parseParameters(results, queryString, encoding);//解析查詢字符串
    } catch (UnsupportedEncodingException e) {
        ;
    }
    // 從正文中的參數(shù)
    String contentType = getContentType();
    if (contentType == null)
        contentType = "";
    int semicolon = contentType.indexOf(';');
    if (semicolon >= 0) {
        contentType = contentType.substring(0, semicolon).trim();
    } else {
        contentType = contentType.trim();
    }
    if ("POST".equals(getMethod()) && (getContentLength() > 0)&& (this.stream == null)
        && "application/x-www-form-urlencoded".equals(contentType)) {
        //判斷條件:POST方法,content-length>0,有ServletInputStream,content-type=application/x-www-form-urlencoded
        try {
            int max = getContentLength();
            int len = 0;
            byte buf[] = new byte[getContentLength()];
            ServletInputStream is = getInputStream();
            while (len < max) {//讀取數(shù)據(jù)
                int next = is.read(buf, len, max - len);
                if (next < 0 ) {
                    break;
                }
                len += next;
            }
            is.close();
            if (len < max) {
                //FIX ME,當實際接收長度小于content-length聲明的長度時
                //上面的代碼中檢查next=-1可以預防出現(xiàn)死循環(huán)
                //但是這個bug必須在mod_jk模塊中
                //記錄額外的信息用于debug mod_jk
                StringBuffer msg = new StringBuffer();
                msg.append("HttpRequestBase.parseParameters content length mismatch\n");
                msg.append("  URL: ");
                msg.append(getRequestURL());
                msg.append(" Content Length: ");
                msg.append(max);
                msg.append(" Read: ");
                msg.append(len);
                msg.append("\n  Bytes Read: ");
                if ( len > 0 ) {
                    msg.append(new String(buf,0,len));
                }
                log(msg.toString());
                throw new RuntimeException
                    (sm.getString("httpRequestBase.contentLengthMismatch"));
            }
            RequestUtil.parseParameters(results, buf, encoding);//解析參數(shù)
        } catch (UnsupportedEncodingException ue) {
            ;
        } catch (IOException e) {
            throw new RuntimeException
                    (sm.getString("httpRequestBase.contentReadFail") + 
                     e.getMessage());
        }
    }
    results.setLocked(true);
    parsed = true;
    parameters = results;
}

至此整個HTTP協(xié)議的解析流程就完成了

3. 總結(jié)

  • 連接器負責接收請求,處理器負責解析請求,每個處理器擁有自己的Request和Response對象,這兩個對象可以重復使用
  • 處理器處理流程
    1. 解析連接信息:設(shè)置Internet地址和代理信息
    2. 解析請求行:請求方法,URI,協(xié)議版本摔笤,查詢參數(shù)(如果有),keep-alive屬性,session ID(如果禁用了cookie),標準化URI地址
    3. 解析請求頭:將請求頭設(shè)置到對應的屬性中,其中有幾個重要的屬性,cookie,content-length和content-type(在處理正文時會用到),conection(主要檢查是否close值)
    4. 解析參數(shù):先解析URI中的查詢參數(shù),再解析正文中的參數(shù)
    5. 調(diào)用容器的invoke方法

PS:
看完Tomcat如何解析才發(fā)現(xiàn)自己對HTTP協(xié)議不了解,只考慮到了HTTP協(xié)議的格式,沒有考慮到不同版本的區(qū)別,特殊請求頭的處理,不同請求方法的處理,cookie的解析,session的處理够滑。
隨著HTTP協(xié)議的發(fā)展,解析的難度越來越大,要求也越來越高(高效+正確);以上的代碼在Tomcat4中已經(jīng)廢棄,換了更高效的連接器.等有時間去看一下Tomcat8的源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末籍茧,一起剝皮案震驚了整個濱河市版述,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寞冯,老刑警劉巖渴析,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晚伙,死亡現(xiàn)場離奇詭異,居然都是意外死亡俭茧,警方通過查閱死者的電腦和手機咆疗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來母债,“玉大人午磁,你說我怎么就攤上這事≌泵牵” “怎么了迅皇?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長衙熔。 經(jīng)常有香客問我登颓,道長,這世上最難降的妖魔是什么红氯? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任框咙,我火速辦了婚禮,結(jié)果婚禮上痢甘,老公的妹妹穿的比我還像新娘喇嘱。我一直安慰自己,他們只是感情好塞栅,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布者铜。 她就那樣靜靜地躺著,像睡著了一般放椰。 火紅的嫁衣襯著肌膚如雪王暗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天庄敛,我揣著相機與錄音俗壹,去河邊找鬼。 笑死藻烤,一個胖子當著我的面吹牛绷雏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播怖亭,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼涎显,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了兴猩?” 一聲冷哼從身側(cè)響起期吓,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎倾芝,沒想到半個月后讨勤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體箭跳,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年潭千,在試婚紗的時候發(fā)現(xiàn)自己被綠了谱姓。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡刨晴,死狀恐怖屉来,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情狈癞,我是刑警寧澤茄靠,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站蝶桶,受9級特大地震影響嘹黔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜莫瞬,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望郭蕉。 院中可真熱鬧疼邀,春花似錦、人聲如沸召锈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涨岁。三九已至拐袜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梢薪,已是汗流浹背蹬铺。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留秉撇,地道東北人甜攀。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像琐馆,于是被迫代替她去往敵國和親规阀。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理瘦麸,服務發(fā)現(xiàn)谁撼,斷路器,智...
    卡卡羅2017閱讀 134,656評論 18 139
  • Http協(xié)議詳解 標簽(空格分隔): Linux 聲明:本片文章非原創(chuàng)滋饲,內(nèi)容來源于博客園作者MIN飛翔的HTTP協(xié)...
    Sivin閱讀 5,223評論 3 82
  • 1.OkHttp源碼解析(一):OKHttp初階2 OkHttp源碼解析(二):OkHttp連接的"前戲"——HT...
    隔壁老李頭閱讀 20,851評論 24 176
  • 一厉碟、概念(載錄于:http://www.cnblogs.com/EricaMIN1987_IT/p/3837436...
    yuantao123434閱讀 8,353評論 6 152
  • 外面的世界很精彩喊巍,外面的世界很無奈,外面的世界很孤獨…… 當我坐著長達五六個小時的大巴墨榄,跨省玄糟,只為了參加一次考試。...
    二月木子禽閱讀 289評論 0 0