1. 程式人生 > 實用技巧 >How tomcat works(深入剖析tomcat)閱讀筆記1-4章

How tomcat works(深入剖析tomcat)閱讀筆記1-4章

How tomcat works

chapter 1 簡單的web伺服器

這一張的主要內容就是實現一個簡單的靜態資源伺服器,socket程式設計,利用java提供的socket和serverSocket程式設計

整體過程如下:

HttpServer通過serverSocket監聽埠,通過阻塞的accept方法接收請求,然後建立resquest和response物件,

//伺服器啟動主方法
public static void main(String[] args) {
    HttpServer server = new HttpServer();
    server.await();
  }

//await方法,幾乎所有的任務都在這裡完成
public void await() {
    ServerSocket serverSocket = null;
    int port = 8080;
    try {
      serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
    }
    catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }

    // Loop waiting for a request
    //shutdown為一個成員變數,這樣的伺服器的較為優雅的停止方式為接收一個uri為"/SHUTDOWN_COMMAND"的請求
    while (!shutdown) {
      Socket socket = null;
      InputStream input = null;
      OutputStream output = null;
      try {
        socket = serverSocket.accept();
        input = socket.getInputStream();
        output = socket.getOutputStream();

        //建立request物件,把inputstream給他
        // create Request object and parse
        Request request = new Request(input);
        request.parse();

        // create Response object
        Response response = new Response(output);
        response.setRequest(request);
        response.sendStaticResource();

        // Close the socket
        socket.close();

        //check if the previous URI is a shutdown command
        shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
      }
      catch (Exception e) {
        e.printStackTrace();
        continue;
      }
    }
  }

request物件主要就做一件事情,從inputstream中解析uri

parse()

從inputstream中讀取資料轉成字串,然後呼叫parseUri方法

public void parse() {
    // Read a set of characters from the socket
    StringBuffer request = new StringBuffer(2048);
    int i;
    byte[] buffer = new byte[2048];
    try {
      i = input.read(buffer);
    }
    catch (IOException e) {
      e.printStackTrace();
      i = -1;
    }
    for (int j=0; j<i; j++) {
      request.append((char) buffer[j]);
    }
    System.out.print(request.toString());
    uri = parseUri(request.toString());
  }

parseUri(String requestString)

private String parseUri(String requestString) {
    int index1, index2;
    index1 = requestString.indexOf(' ');
    if (index1 != -1) {
      index2 = requestString.indexOf(' ', index1 + 1);
      if (index2 > index1)
          //切割出uri
        return requestString.substring(index1 + 1, index2);
    }
    return null;
  }

response物件,拿到request和outputstream,按照request中的uri找到對應的資源,然後塞入outputstream中就完事兒

sendStaticResource()

public void sendStaticResource() throws IOException {
  byte[] bytes = new byte[BUFFER_SIZE];
  FileInputStream fis = null;
  try {
    File file = new File(HttpServer.WEB_ROOT, request.getUri());
    if (file.exists()) {
      fis = new FileInputStream(file);
      int ch = fis.read(bytes, 0, BUFFER_SIZE);
      while (ch!=-1) {
        output.write(bytes, 0, ch);
        ch = fis.read(bytes, 0, BUFFER_SIZE);
      }
    }
    else {
      // file not found
      String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
              "Content-Type: text/html\r\n" +
              "Content-Length: 23\r\n" +
              "\r\n" +
              "<h1>File Not Found</h1>";
      output.write(errorMessage.getBytes());
    }
  }
  catch (Exception e) {
    // thrown if cannot instantiate a File object
    System.out.println(e.toString() );
  }
  finally {
    if (fis!=null)
      fis.close();
  }
}

tips:由於這個響應沒有任何響應頭,所以chrome現在的版本不會接受它,但是firefox可以,但也只是以純文字形式

chapter 2 一個簡單的servlet容器

第一章利用了java的socket程式設計構建了一個只能響應只能響應靜態資源的http伺服器,這一章實現一個簡單的servlet容器,它只能處理簡單的servlet和響應靜態資源

熟悉servlet介面

servlet介面中聲明瞭5個方法

//初始化方法
public void init(ServletConfig config)throws ServletException;

//主要的方法,接受一個request物件和response物件,然後進行處理
public void service(ServletRequest request,ServletResponse response)throws ServletException,Java.io.IOException;

//在將servlet例項從服務中移除時,servlet容器會呼叫servlet例項的destory()方法,清理一些資源
public void destory();

public ServletConfig getServletConfig();

public java.lang.String getServletInfo();

一個功能齊全的servlet容器處理流程

  • 第一次呼叫某servlet時,將它載入,然後執行init()方法
  • 針對每個request請求,建立ServletRequest例項和ServletResponse例項,傳遞給service()方法
  • 關閉servlet時,呼叫destroy()方法,並且解除安裝該類

本章是一個簡單的實現,就不呼叫init方法和destroy方法,建立好ServletRequest和ServletResponse然後作為引數傳遞給service方法即可

tips:在這樣的servlet容器中,每次對於該servlet的請求,都會導致載入相應的servlet類,可想而知,效率是非常低的

主要類以及流程分析

本章主要包含了以下幾個類:

  • HttpServer1
  • Request
  • Response
  • StaticResourceProcessor
  • ServletProcessor1
  • Constants

該servlet容器的UML如下

處理流程

入口類,HttpServer1,監聽埠8080,構建request,response,根據是請求靜態資源還是servlet分別處理

//伺服器主方法
public static void main(String[] args) {
    HttpServer1 server = new HttpServer1();
    server.await();
  }

//await
public void await() {
    ServerSocket serverSocket = null;
    int port = 8080;
    try {
      serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
    }
    catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }

    // Loop waiting for a request
    while (!shutdown) {
      Socket socket = null;
      InputStream input = null;
      OutputStream output = null;
      try {
        socket = serverSocket.accept();
        input = socket.getInputStream();
        output = socket.getOutputStream();

        // create Request object and parse
        Request request = new Request(input);
        request.parse();

        // create Response object
        Response response = new Response(output);
        response.setRequest(request);

        // check if this is a request for a servlet or a static resource
        // a request for a servlet begins with "/servlet/"
        if (request.getUri().startsWith("/servlet/")) {
          ServletProcessor1 processor = new ServletProcessor1();
          processor.process(request, response);
        }
        else {
          StaticResourceProcessor processor = new StaticResourceProcessor();
          processor.process(request, response);
        }

        // Close the socket
        socket.close();
        //check if the previous URI is a shutdown command
        shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
      }
      catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
      }
    }
  }

request.parse()

和第一章一樣解析uri

public void parse() {
    // Read a set of characters from the socket
    StringBuffer request = new StringBuffer(2048);
    int i;
    byte[] buffer = new byte[2048];
    try {
      i = input.read(buffer);
    }
    catch (IOException e) {
      e.printStackTrace();
      i = -1;
    }
    for (int j=0; j<i; j++) {
      request.append((char) buffer[j]);
    }
    System.out.print(request.toString());
    uri = parseUri(request.toString());
  }

//uri = parseUri(request.toString());
private String parseUri(String requestString) {
    int index1, index2;
    index1 = requestString.indexOf(' ');
    if (index1 != -1) {
      index2 = requestString.indexOf(' ', index1 + 1);
      if (index2 > index1)
        return requestString.substring(index1 + 1, index2);
    }
    return null;
  }

processor.process(request, response);

請求servlet分支,接受request,response作為引數,然後呼叫ServletProcessor1的process方法,載入對應的servlet然後,例項化該servlet並且真正地處理請求

public void process(Request request, Response response) {

    String uri = request.getUri();
    String servletName = uri.substring(uri.lastIndexOf("/") + 1);
    URLClassLoader loader = null;

    try {
      // create a URLClassLoader
      URL[] urls = new URL[1];
      URLStreamHandler streamHandler = null;
      File classPath = new File(Constants.WEB_ROOT);
      // the forming of repository is taken from the createClassLoader method in
      // org.apache.catalina.startup.ClassLoaderFactory
      String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
      // the code for forming the URL is taken from the addRepository method in
      // org.apache.catalina.loader.StandardClassLoader class.
      urls[0] = new URL(null, repository, streamHandler);
      loader = new URLClassLoader(urls);
    }
    catch (IOException e) {
      System.out.println(e.toString() );
    }
    Class myClass = null;
    try {
      //載入servlet
      myClass = loader.loadClass(servletName);
    }
    catch (ClassNotFoundException e) {
      System.out.println(e.toString());
    }

    Servlet servlet = null;

    try {
      //例項化servlet並處理請求
      servlet = (Servlet) myClass.newInstance();
      servlet.service((ServletRequest) request, (ServletResponse) response);
    }
    catch (Exception e) {
      System.out.println(e.toString());
    }
    catch (Throwable e) {
      System.out.println(e.toString());
    }

  }

processor.process(request, response);

傳送靜態資源分支,這裡只是呼叫了以下response的sendStaticResource方法

public void process(Request request, Response response) {
    try {
      response.sendStaticResource();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }

chapter 3 聯結器(Connector)

建立一個聯結器,用來增強第二章的功能,主要就是用一種更好的方式建立request和response物件

本章應用包含三個模組:聯結器模組、啟動模組和核心模組

啟動模組只包含了一個類:BootStrap

聯結器模組可分為以下5個型別:

  • 聯結器及其支援類(HttpConnector和HttpProcessor)
  • 表示Http請求的類(HttpRequest)及其支援類
  • 表示Http響應的類(HttpResponse)及其支援類
  • 外觀類(HttpRequestFacade和HttpResponseFacade)
  • 常量類(Constants)

核心模組包含兩個類,servletProcessor和StaticResourceProcessor,具體UML如下

主要類以及流程分析

啟動類BootStrap,new一個connector然後呼叫connector.start(),不難猜到connector的start另啟了一個執行緒

public static void main(String[] args) {
    HttpConnector connector = new HttpConnector();
    connector.start();
  }

HttpConnector

聯結器模組主要類,負責監聽埠,接收請求,然後將自己傳遞給HttpProcessor,並且將socket作為引數,傳遞給HttpProcessor的process方法

run()

public void run() {
    ServerSocket serverSocket = null;
    int port = 8080;
    try {
      serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
    }
    catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }
    while (!stopped) {
      // Accept the next incoming connection from the server socket
      Socket socket = null;
      try {
        socket = serverSocket.accept();
      }
      catch (Exception e) {
        continue;
      }
      // Hand this socket off to an HttpProcessor
      HttpProcessor processor = new HttpProcessor(this);
      processor.process(socket);
    }
  }

public void start() {
    Thread thread = new Thread(this);
    thread.start();
  }

HttpProcessor

負責生成resquest以及response,然後交給ServletProcessor或者StaticResourceProcessor處理

public void process(Socket socket)

public void process(Socket socket) {
    //SocketInputStream是InputStream的包裝類,主要提供了readRequestLine方法和readHeader方法
    SocketInputStream input = null;
    OutputStream output = null;
    try {
      //2048為指定的緩衝陣列大小
      input = new SocketInputStream(socket.getInputStream(), 2048);
      output = socket.getOutputStream();

      // create HttpRequest object and parse
      request = new HttpRequest(input);

      // create HttpResponse object
      response = new HttpResponse(output);
      response.setRequest(request);

      response.setHeader("Server", "Pyrmont Servlet Container");

      //解析請求行
      parseRequest(input, output);
      //解析首部行
      parseHeaders(input);
	
      //然後就可以根據請求的uri決定交給什麼processor了
      //這兩者就和第二章的一樣了
      //check if this is a request for a servlet or a static resource
      //a request for a servlet begins with "/servlet/"
      if (request.getRequestURI().startsWith("/servlet/")) {
        ServletProcessor processor = new ServletProcessor();
        processor.process(request, response);
      }
      else {
        StaticResourceProcessor processor = new StaticResourceProcessor();
        processor.process(request, response);
      }

      // Close the socket
      socket.close();
      // no shutdown for this application
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
解析請求行parseRequest

private void parseRequest(SocketInputStream input, OutputStream output)

private void parseRequest(SocketInputStream input, OutputStream output)
    throws IOException, ServletException {

    // Parse the incoming request line
    //HttpRequestLine為請求行物件,利用readRequestLine方法填充HttpProcessor的HttpRequestLine成員變數
    input.readRequestLine(requestLine);
    //經過上面的處理,這裡就能拿到方法名了
    String method =
      new String(requestLine.method, 0, requestLine.methodEnd);
    //uri暫時還拿不到,因為可能帶參,此時的uri字元陣列儲存的是帶參的uri
    String uri = null;
    String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);

    // Validate the incoming request line
    if (method.length() < 1) {
      throw new ServletException("Missing HTTP request method");
    }
    else if (requestLine.uriEnd < 1) {
      throw new ServletException("Missing HTTP request URI");
    }
    // Parse any query parameters out of the request URI
    //處理帶參uri
    int question = requestLine.indexOf("?");
    if (question >= 0) {
        //包請求引數塞進queryString,回頭需要的時候解析
      request.setQueryString(new String(requestLine.uri, question + 1,
        requestLine.uriEnd - question - 1));
      uri = new String(requestLine.uri, 0, question);
    }
    else {
      request.setQueryString(null);
      uri = new String(requestLine.uri, 0, requestLine.uriEnd);
    }

	//檢查,可略過
    // Checking for an absolute URI (with the HTTP protocol)
    if (!uri.startsWith("/")) {
      int pos = uri.indexOf("://");
      // Parsing out protocol and host name
      if (pos != -1) {
        pos = uri.indexOf('/', pos + 3);
        if (pos == -1) {
          uri = "";
        }
        else {
          uri = uri.substring(pos);
        }
      }
    }
	
    //處理jessionId
    // Parse any requested session ID out of the request URI
    String match = ";jsessionid=";
    int semicolon = uri.indexOf(match);
    if (semicolon >= 0) {
      String rest = uri.substring(semicolon + match.length());
      int semicolon2 = rest.indexOf(';');
      if (semicolon2 >= 0) {
        request.setRequestedSessionId(rest.substring(0, semicolon2));
        rest = rest.substring(semicolon2);
      }
      else {
        request.setRequestedSessionId(rest);
        rest = "";
      }
      request.setRequestedSessionURL(true);
      uri = uri.substring(0, semicolon) + rest;
    }
    else {
      request.setRequestedSessionId(null);
      request.setRequestedSessionURL(false);
    }

    // Normalize URI (using String operations at the moment)
    String normalizedUri = normalize(uri);

    // Set the corresponding request properties
    //把請求行的屬性都塞進去,至此請求行的資料解析完畢
    ((HttpRequest) request).setMethod(method);
    request.setProtocol(protocol);
    if (normalizedUri != null) {
      ((HttpRequest) request).setRequestURI(normalizedUri);
    }
    else {
      ((HttpRequest) request).setRequestURI(uri);
    }

    if (normalizedUri == null) {
      throw new ServletException("Invalid URI: " + uri + "'");
    }
  }

HttpRequestLine

HttpRequestLine為請求行物件,用字元陣列來儲存請求行的資料,程式碼一瞥

public HttpRequestLine(char[] method, int methodEnd,
                           char[] uri, int uriEnd,
                           char[] protocol, int protocolEnd) {

        this.method = method;
        this.methodEnd = methodEnd;
        this.uri = uri;
        this.uriEnd = uriEnd;
        this.protocol = protocol;
        this.protocolEnd = protocolEnd;

}

    public char[] method;
    public int methodEnd;
    public char[] uri;
    public int uriEnd;
    public char[] protocol;
    public int protocolEnd;

SocketInputStream的readRequestLine方法

public void readRequestLine(HttpRequestLine requestLine)

public void readRequestLine(HttpRequestLine requestLine)
        throws IOException {

        // Recycling check
        if (requestLine.methodEnd != 0)
            requestLine.recycle();

        // Checking for a blank line
    	//跳過空行
        int chr = 0;
        do { // Skipping CR or LF
            try {
                //呼叫read方法,
                chr = read();
            } catch (IOException e) {
                chr = -1;
            }
        } while ((chr == CR) || (chr == LF));
        if (chr == -1)
            throw new EOFException
                (sm.getString("requestStream.readline.error"));
    	//由於read方法中將pos指向了下一個位元組了,需要回退一步
        pos--;

        // Reading the method name

        int maxRead = requestLine.method.length;
        int readStart = pos;
        int readCount = 0;

        boolean space = false;

        while (!space) {
            //超過method字元陣列的最大長度了
            // if the buffer is full, extend it
            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"));
                }
            }
            // We're at the end of the internal buffer
            //如果已經讀取到inputstream讀取出來的buffer的最後一個位置了
            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;
            }
            //這裡method字元陣列是以空格節未的,並沒有在buf[pos]=SP的時候break
            requestLine.method[readCount] = (char) buf[pos];
            readCount++;
            pos++;
        }
		//以空格結尾,readCount去掉空格
        requestLine.methodEnd = readCount - 1;
		//下面就是解析uri和protocol了

    }

SocketInputStream的read、fill方法

public int read()
        throws IOException {
   		//起始值pos為0,count也為0
        if (pos >= count) {
            //修改count為本次讀入的位元組長度
            fill();
            if (pos >= count)
                return -1;
        }
    	//返回buf[pos]的那個位元組
        return buf[pos++] & 0xff;
    }

protected void fill()
        throws IOException {
        pos = 0;
        count = 0;
    	//
        int nRead = is.read(buf, 0, buf.length);
        if (nRead > 0) {
            count = nRead;
        }
    }
解析首部行parseHeaders

private void parseHeaders(SocketInputStream input)

private void parseHeaders(SocketInputStream input)
    throws IOException, ServletException {
    //迴圈解析每個請求頭
    while (true) {
      HttpHeader header = new HttpHeader();;

      // Read the next header
      //socketInputStream提供的readHeader方法,上面已經詳細解讀過readRequestLine方法了,這裡不詳細解讀,填充header
      //HttpHeader和HttpRequestLine結構也差不多
      input.readHeader(header);
      if (header.nameEnd == 0) {
        if (header.valueEnd == 0) {
          return;
        }
        else {
          throw new ServletException
            (sm.getString("httpProcessor.parseHeaders.colon"));
        }
      }

      String name = new String(header.name, 0, header.nameEnd);
      String value = new String(header.value, 0, header.valueEnd);
      request.addHeader(name, value);
      //對於cookie和content-length兩個header需要特殊處理一下
      // do something for some headers, ignore others.
      if (name.equals("cookie")) {
        Cookie cookies[] = RequestUtil.parseCookieHeader(value);
        for (int i = 0; i < cookies.length; i++) {
          if (cookies[i].getName().equals("jsessionid")) {
            // Override anything requested in the URL
            if (!request.isRequestedSessionIdFromCookie()) {
              // Accept only the first session id cookie
              request.setRequestedSessionId(cookies[i].getValue());
              request.setRequestedSessionCookie(true);
              request.setRequestedSessionURL(false);
            }
          }
          request.addCookie(cookies[i]);
        }
      }
      else if (name.equals("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 (name.equals("content-type")) {
        request.setContentType(value);
      }
    } //end while
  }
懶載入的parameter

請求引數在被呼叫之前是不會解析的,在request的getParameter、getParameterMap、getParameterNames等方法被呼叫時,首先會呼叫parseParameter方法

需要注意的一點是,ParameterMap是不支援修改的,它被鎖住了,很簡單,用一個lock變數,修改的直接return

protected void parseParameters()

protected void parseParameters() {
    if (parsed)
      return;
    ParameterMap results = parameters;
    if (results == null)
      results = new ParameterMap();
    //先開鎖
    results.setLocked(false);
    String encoding = getCharacterEncoding();
    if (encoding == null)
      encoding = "ISO-8859-1";

    //parse請求頭中的引數
    // Parse any parameters specified in the query string
    String queryString = getQueryString();
    try {
      RequestUtil.parseParameters(results, queryString, encoding);
    }
    catch (UnsupportedEncodingException e) {
      ;
    }

    //parse請求體中的引數
    // Parse any parameters specified in the input stream
    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)
      && "application/x-www-form-urlencoded".equals(contentType)) {
      try {
        int max = getContentLength();
        int len = 0;
        byte buf[] = new byte[getContentLength()];
        ServletInputStream is = getInputStream();
        while (len < max) {
          int next = is.read(buf, len, max - len);
          if (next < 0 ) {
            break;
          }
          len += next;
        }
        is.close();
        if (len < max) {
          throw new RuntimeException("Content length mismatch");
        }
        RequestUtil.parseParameters(results, buf, encoding);
      }
      catch (UnsupportedEncodingException ue) {
        ;
      }
      catch (IOException e) {
        throw new RuntimeException("Content read fail");
      }
    }

    // Store the final results
    //再把parameterMap鎖上,並且將parsed置為true避免重複parse
    results.setLocked(true);
    parsed = true;
    parameters = results;
  }

chapter 4 Tomcat預設的聯結器

總體介紹

第三章實現了簡單的聯結器,第四章來看看Tomcat預設的聯結器,雖然該聯結器已經被棄用,換成了另一個執行速率更快的聯結器——Coyote——替代,但是還是值得一看的,相較於第三章簡單的聯結器,Tomcat4的預設聯結器有以下優化:

  • 更為全面地支援Http協議,包括從0.9到1.1,也支援1.1的新特性
  • 每一個connector不再只繫結一個HttpProcessor,將會利用多執行緒支援多Http連線
  • 大量利用字元陣列,減少了代價較高的字串操作
  • 利用了池的技術,快取了HttpProcessor物件,避免每次來都需要建立,節省了物件建立的開銷

但仍然還存在著上來就直接parse所有的Header不管有沒有使用,效率比較低的問題

它的主要工作原理和第三章的聯結器基本類似,細微之處不同稍後原始碼討論

預設的聯結器UML圖如下:

首先需要注意的一點是,於第三章不同,HttpConnector來負責Request、Response的建立了,而HttpProcessor專注於parse資訊,也就是填充request和response

Http1.1的新特性

持久連線

減少了頻繁建立TCP連線帶來的系統消耗,也減少了三次握手的次數,提高了速度,在Http1.1中,預設採用持久連線,也可以顯式使用,在request首部行加上

Connection: Keep-alive
塊編碼

有了持久連線之後,一個TCP連線可能被共享,那麼多個物件的資料可能混在一起,那麼如何把它們區分開來呢?利用transfer-encoding的特殊請求頭,它用來指明位元組流會分塊傳送,對每一個塊,塊的長度後面會有一個CRLF,然後才是資料,一個事務以一個長度為0的塊標記,“0\r\n”表示事務已經結束

//e.g利用兩個塊傳送 I'am as helpess as a kitten up a tree.

//傳送內容如下
1D\r\n
I'am as helpess as a kitten u
9\r\n
p a tree.
0\r\n
狀態碼100

當瀏覽器想傳送一個比較大的請求體的時候,不知道伺服器能不能接收時,可以先發送一個帶有如下請求頭的請求

Except: 100-continue

期待服務的迴應為100-continue,收到這個迴應說明可以接收,這個時候瀏覽器才會真正地發出這個比較大的請求,避免浪費頻寬、系統資源

如果伺服器收到帶有上述請求頭的請求,並且支援的話,就傳送如下的回覆

HTTP/1.1 100 continue

這樣瀏覽器就會發送真正的請求了

主要類以及流程分析

建立ServerSocket

首先呼叫initialize方法

void initialize()

public void initialize()
    throws LifecycleException {
        if (initialized)
            throw new LifecycleException (
                sm.getString("httpConnector.alreadyInitialized"));
        this.initialized=true;
        Exception eRethrow = null;
        // Establish a server socket on the specified port
        try {
            //呼叫open方法
            serverSocket = open();
        } catch (IOException ioe) {
         	...   
        }
        if ( eRethrow != null )
            throw new LifecycleException(threadName + ".open", eRethrow);
}

ServerSocket open()

private ServerSocket open()
    throws IOException, KeyStoreException, NoSuchAlgorithmException,
           CertificateException, UnrecoverableKeyException,
           KeyManagementException
    {

        // Acquire the server socket factory for this Connector
        ServerSocketFactory factory = getFactory();
		//根據是否有限定的IP地址選擇不同的工廠方法
        // If no address is specified, open a connection on all addresses
        if (address == null) {
            log(sm.getString("httpConnector.allAddresses"));
            try {
                //不限定埠地址,也就是說監聽任何IP的請求
                return (factory.createSocket(port, acceptCount));
            } catch (BindException be) {
                throw new BindException(be.getMessage() + ":" + port);
            }
        }

        // Open a server socket on the specified address
        try {
            InetAddress is = InetAddress.getByName(address);
            log(sm.getString("httpConnector.anAddress", address));
            try {
                return (factory.createSocket(port, acceptCount, is));
            } catch (BindException be) {
                throw new BindException(be.getMessage() + ":" + address +
                                        ":" + port);
            }
        } catch (Exception e) {
            log(sm.getString("httpConnector.noAddress", address));
            try {
                return (factory.createSocket(port, acceptCount));
            } catch (BindException be) {
                throw new BindException(be.getMessage() + ":" + port);
            }
        }

    }

factory.createSocket(port, acceptCount)

//ServerSocketFactory中定義,在DefalutServerSocketFactory中實現
public ServerSocket createSocket (int port, int backlog)
    throws IOException, KeyStoreException, NoSuchAlgorithmException,
           CertificateException, UnrecoverableKeyException,
           KeyManagementException {

        return (new ServerSocket(port, backlog));

    }

至此建立了ServerSocket並與HttpConnector的serverSocket成員變數繫結

監聽請求

start()

public void start() throws LifecycleException {

        // Validate and update our current state
        if (started)
            throw new LifecycleException
                (sm.getString("httpConnector.alreadyStarted"));
        threadName = "HttpConnector[" + port + "]";
        lifecycle.fireLifecycleEvent(START_EVENT, null);
        started = true;

        // Start our background thread
    	//new了一個thread 並且呼叫了thread的start方法
        threadStart();

        // Create the specified minimum number of processors
        while (curProcessors < minProcessors) {
            if ((maxProcessors > 0) && (curProcessors >= maxProcessors))
                break;
            HttpProcessor processor = newProcessor();
            recycle(processor);
        }

    }

void run()

start之後進入run方法

public void run() {

        // Loop until we receive a shutdown command
    	//stopped成員變數
        while (!stopped) {

            // Accept the next incoming connection from the server socket
            Socket socket = null;
            try {
                //接收請求
                socket = serverSocket.accept();
                //設定超時時長
                if (connectionTimeout > 0)
                    socket.setSoTimeout(connectionTimeout);
            } catch (AccessControlException ace) {
                log("socket accept security exception: " + ace.getMessage());
                continue;
            } catch (IOException e) {
                if (started && !stopped)
                    log("accept: ", e);
                break;
            }

            // Hand this socket off to an appropriate processor
            //從成員變數processors,這個processor池中拿一個HttpProcessor出來
            //如果池中沒有,則建立一個
            HttpProcessor processor = createProcessor();
            if (processor == null) {
                try {
                    log(sm.getString("httpConnector.noProcessor"));
                    //如果不允許建立了,則直接把socket close掉,也就是直接拋棄這個請求
                    socket.close();
                } catch (IOException e) {
                    ;
                }
                continue;
            }
            //把socket交給processor
            processor.assign(socket);

            // The processor will recycle itself when it finishes

        }

        // Notify the threadStop() method that we have shut ourselves down
        synchronized (threadSync) {
            threadSync.notifyAll();
        }

    }

HttpProcessor createProcessor()

private HttpProcessor createProcessor() {

        synchronized (processors) {
            if (processors.size() > 0)
                return ((HttpProcessor) processors.pop());
            if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
                //stack中沒有空閒的的processor了,並且當前Processor數量還沒超過允許的最大Processor數量,則建立一個新的並返回
                return (newProcessor());
            } else {
                //如果maxProcessors<0 說明processor數量不受限制,可以無限制建立
                if (maxProcessors < 0) {
                    return (newProcessor());
                } else {
                    //說明curProcessors > maxProcessors 沒有空閒的processor了,也不允許建立
                    return (null);
                }
            }
        }

    }

HttpProcessor newProcessor()

private HttpProcessor newProcessor() {
		//建立processor並且繫結connector
        HttpProcessor processor = new HttpProcessor(this, curProcessors++);
        if (processor instanceof Lifecycle) {
            try {
                //注意!!! 這裡啟動了HttpProcessor執行緒
                ((Lifecycle) processor).start();
            } catch (LifecycleException e) {
                log("newProcessor", e);
                return (null);
            }
        }
        created.addElement(processor);
        return (processor);

    }

processor.assign(socket)

connector把socket交付給HttpProcessor就結束了,它並不像之前那樣還需要等待HttpProcessor的執行結果,它可以去繼續監聽其他請求了,它這裡就只負責這裡就負責喚醒了一下在run方法中由於沒有socket阻塞的processor

synchronized void assign(Socket socket) {

        // Wait for the Processor to get the previous Socket
    	//available預設值為false,代表是否有新的socket過來
        while (available) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }

        // Store the newly available Socket and notify our thread
        this.socket = socket;
        available = true;
    	//notifyAll喚醒了誰呢?喚醒了HttpProcessor的主執行緒
    	//它在newProcessor方法中啟動,然後在await方法中等待
        notifyAll();

        if ((debug >= 1) && (socket != null))
            log(" An incoming request is being assigned");

    }
處理請求

HttpProcessor

run()

public void run() {

        // Process requests until we receive a shutdown signal
    	//stopped成員變數
        while (!stopped) {

            // Wait for the next socket to be assigned
            Socket socket = await();
            if (socket == null)
                continue;

            // Process the request from this socket
            try {
                process(socket);
            } catch (Throwable t) {
                log("process.invoke", t);
            }

            // Finish up this request
            //把processor重新又壓回connector的棧內重複利用
            connector.recycle(this);

        }

        // Tell threadStop() we have shut ourselves down successfully
        synchronized (threadSync) {
            threadSync.notifyAll();
        }

    }

await()

private synchronized Socket await() {

        // Wait for the Connector to provide a new Socket
        while (!available) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
		//被喚醒之後availabe被修改成了true了,將socket賦值給區域性變數,並且將availabe修改為false
        // 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);

    }

這裡引用一下原文的解釋

為什麼這裡需要返回一個區域性變數?

這樣做的話,就可以在當前socket被處理完成之前,接收下一個socket???黑人問號,這個process方法不是單執行緒的嘛?它不是必須完成解析才能被回收嘛?怎麼同時接收兩個socket的

為什麼還需要notifyAll呢?

防止connector在執行到processor.assign()方法阻塞,此時available為true,connector一直阻塞,知道processor喚醒它

recycle(HttpProcessor processor)

void recycle(HttpProcessor processor) {

        //        if (debug >= 2)
        //            log("recycle: Recycling processor " + processor);
        processors.push(processor);

    }

處理請求的主方法

**private void process(Socket socket) **

private void process(Socket socket) {
        boolean ok = true;
        boolean finishResponse = true;
        SocketInputStream input = null;
        OutputStream output = null;

        // Construct and initialize the objects we will need
        try {
            input = new SocketInputStream(socket.getInputStream(),
                                          connector.getBufferSize());
        } catch (Exception e) {
            log("process.create", e);
            ok = false;
        }

        keepAlive = true;
		//原來宣揚了半天的持久連線新特性就是個keepAlive變數
        while (!stopped && ok && keepAlive) {

            finishResponse = true;

            try {
                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) {
                log("process.create", e);
                ok = false;
            }


            // Parse the incoming request
            try {
                if (ok) {
					//如同第三章那樣,這裡負責處理填充Request,如果想追究如何處理Http1.1的新特性,可以檢視,這裡不細說
                    parseConnection(socket);
                    parseRequest(input, output);
                    if (!request.getRequest().getProtocol()
                        .startsWith("HTTP/0"))
                        parseHeaders(input);
                    if (http11) {
                        // Sending a request acknowledge back to the client if
                        // requested.
                        ackRequest(output);
                        // If the protocol is HTTP/1.1, chunking is allowed.
                        if (connector.isChunkingAllowed())
                            response.setAllowChunking(true);
                    }

                }
            } catch (EOFException e) {
                ...
            } 

            // Ask our Container to process this request
            try {
                ((HttpServletResponse) response).setHeader
                    ("Date", FastHttpDateFormat.getCurrentDate());
                if (ok) {
                    //從processor繫結的connector中獲取contianer然後把parse好的request和response傳遞給他,處理請求
                    connector.getContainer().invoke(request, response);
                }
            } catch (ServletException e) {
                ...
            } 

            // Finish up the handling of the request
            if (finishResponse) {
                try {
                    //
                    response.finishResponse();
                } catch (IOException e) {
                    ok = false;
                } catch (Throwable e) {
                    log("process.invoke", e);
                    ok = false;
                }
                try {
                    request.finishRequest();
                } catch (IOException e) {
                    ok = false;
                } catch (Throwable e) {
                    log("process.invoke", e);
                    ok = false;
                }
                try {
                    if (output != null)
                        output.flush();
                } catch (IOException e) {
                    ok = false;
                }
            }

            // We have to check if the connection closure has been requested
            // by the application or the response stream (in case of HTTP/1.0
            // and keep-alive).
            if ( "close".equals(response.getHeader("Connection")) ) {
                keepAlive = false;
            }

            // End of request processing
            status = Constants.PROCESSOR_IDLE;

            // Recycling the request and the response objects
            //request和response物件也是需要回收的,在newProcessor的時候呼叫HttpConnector的createxxx方法建立作為processor的成員變數
            request.recycle();
            response.recycle();

        }

        try {
            shutdownInput(input);
            socket.close();
        } catch (IOException e) {
            ;
        } catch (Throwable e) {
            log("process.invoke", e);
        }
        socket = null;



    }
處理請求(Container)

核心方法invoke,也就是之前的servletProcessor做的事情,通過類載入器找到對應的servlet然後讓他處理

invoke方法

public void invoke(Request request, Response response)
    throws IOException, ServletException {

    String servletName = ( (HttpServletRequest) request).getRequestURI();
    servletName = servletName.substring(servletName.lastIndexOf("/") + 1);
    URLClassLoader loader = null;
    try {
      URL[] urls = new URL[1];
      URLStreamHandler streamHandler = null;
      File classPath = new File(WEB_ROOT);
      String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
      urls[0] = new URL(null, repository, streamHandler);
      loader = new URLClassLoader(urls);
    }
    catch (IOException e) {
      System.out.println(e.toString() );
    }
    Class myClass = null;
    try {
      myClass = loader.loadClass(servletName);
    }
    catch (ClassNotFoundException e) {
      System.out.println(e.toString());
    }

    Servlet servlet = null;

    try {
      servlet = (Servlet) myClass.newInstance();
      servlet.service((HttpServletRequest) request, (HttpServletResponse) response);
    }
    catch (Exception e) {
      System.out.println(e.toString());
    }
    catch (Throwable e) {
      System.out.println(e.toString());
    }



  }