第三章 連接器

Catalina有兩個主要的模塊 Connector 和 Container,在本章的程序中所要建立的連接器是 Tomcat 4 中的默認連接器的簡化版

3.1 StringManage

Tomcat 將存儲錯誤消息的 properties 文件劃分到不同的包中,每個 properties 文件都是使用 org.apache.catalina.util.StringManager 類的一個實例進行處理。同時 StringManager 是單例模式的,避免空間的浪費。

3.2 應用程序

本章的應用程序包含三個模塊:

  • 啟動模塊:負責啟動應用程序。
  • 連接器模塊:
    • 連接器及其支持類(HttpConnector 和 HttpProcessor);
    • HttpRequest 類和 HttpResponse 類及其支持類;
    • 外觀類(HttpRequesFaced 類和 HttpResponseFaced);
    • 常量類。
  • 核心模塊:
    • ServletProcessor : 處理 servlet 請求(類似 Wrapper);
    • StaticResourceProcessor : 處理靜態資源請求。
本章應用程序的 UML 類圖

3.2.1 啟動應用程序

  public final class Bootstrap {
    public static void main(String[] args) {
      //創建 HttpConnector 實例
      HttpConnector connector = new HttpConnector();
      //啟動 Connector
      connector.start();
    }
  }

3.2.2 HttpConnector 類

Connector 不知道 servlet 接受 Request 和 Response 的具體類型,所以使用 HttpServletRequest 和 HttpResponse 進行傳參。同時解析 HTTP 請求對參數是懶加載的,在這些參數被 servlet 實例真正調用前是不會進行解析的。
Tomcat 的默認連接器和本章程序的 Connector 都使用 SocketInputStream 獲取字節流。SocketInputStream 是 InputStream 的包裝類,提供了兩個重要的方法:

  • readRequestLine:獲取請求行,包括 URI、請求方法和 HTTP 版本信息。
  • readHead: 獲取請求頭,每次調用 readHead 方法都會返回一個鍵值對,可以通過遍歷讀取所有的請求頭信息。
public class HttpConnector implements Runnable {
  boolean stopped;
  private String scheme = "http";
  public void start() {
    //啟動一個新的線程,調用自身的 run 方法
    Thread thread = new Thread(this);
    thread.start();
  }
  public void run() {
    //為減少文章篇幅省略 try catch 語句塊
    //創建 ServerSocket 
    ServerSocket serverSocket = null;
    int port = 8080;
      serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
  
    while (!stopped) {
      // 等待連接請求
      Socket socket = null;
      socket = serverSocket.accept();
      // 為當前請求創建一個 HttpProcessor 
      HttpProcessor processor = new HttpProcessor(this);
      //調用 process 方法處理請求
      processor.process(socket);
    }
  }
HttpProcessor 類

HttpProcessor 的 process 方法對每個傳入的 HTTP 請求,要完成4個操作:

  • 創建一個 HttpRequest 對象;
  • 創建一個 HttpResponse 對象;
  • 解析 HTTP 請求的第一行內容和請求頭信息,填充 HttpRequest 對象;
  • 將 HttpRequest 和 HttpResponse 傳遞給 ServletProcessor 或 Static-ResourceProcessor 的 process 方法。
public void process(Socket socket) {
      SocketInputStream input= new SocketInputStream(socket.getInputStream(), 2048);
      OutputStream output = socket.getOutputStream();

      //創建 HttpRequest 和 HttpResponse 對象
      request = new HttpRequest(input);
      response = new HttpResponse(output);
      response.setRequest(request);
      //向客戶端發送響應頭信息
      response.setHeader("Server", "Pyrmont Servlet Container");
      //解析請求行
      parseRequest(input, output);
      //解析請求頭
      parseHeaders(input);

      //根據請求的 URI 模式判斷處理請求的類
      if (request.getRequestURI().startsWith("/servlet/")) {
        ServletProcessor processor = new ServletProcessor();
        processor.process(request, response);
      }
      else {
        StaticResourceProcessor processor = new StaticResourceProcessor();
        processor.process(request, response);
      }  
  }

parseRequest 方法解析請求行:
處理 URI 字符串 --> 絕對路徑檢查 --> 會話標識符檢查 --> URI 修正 --> 設置 request 屬性

private void parseRequest(SocketInputStream input, OutputStream output) {
    // requestLine 是 HttpRequestLine 的實例
    //使用 SocketInputStream 中的信息填充 requestLine,并從 requestLine 中獲取請求行信息
    input.readRequestLine(requestLine);
    String method =
      new String(requestLine.method, 0, requestLine.methodEnd);
    String uri = null;
    String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);
    // Validate the incoming request line
    //驗證輸入的 request line 是否合法, 略
    ...

    //處理 URI 后的查詢字符串
    int question = requestLine.indexOf("?");
    if (question >= 0) {
      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);
    }
    //檢查是否是絕對路徑
    if (!uri.startsWith("/")) {
      int pos = uri.indexOf("://");
      // 獲取 protocol 的類型
      if (pos != -1) {
        pos = uri.indexOf('/', pos + 3);
        if (pos == -1) {
          uri = "";
        }
        else {
          uri = uri.substring(pos);
        }
      }
    }

    // 檢查查詢字符串是否還攜帶會話標識符
    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);
    }

    //對 URI 進行修正轉化為合法的 URI,如將 "\" 替換成 "/"
    String normalizedUri = normalize(uri);

    // 屬性 request 的屬性
    ((HttpRequest) request).setMethod(method);
    request.setProtocol(protocol);
    if (normalizedUri != null) {
      ((HttpRequest) request).setRequestURI(normalizedUri);
    }
    else {
      ((HttpRequest) request).setRequestURI(uri);
    }
  }

parseHeaders 方法解析請求頭:
獲取下一個 header --> 如果 header name 和 value 為空則退出循環 --> 從 header 中獲取 key/value 并放入 request --> 對 cookie 和 content-type 做處理 --> 循環

private void parseHeaders(SocketInputStream input) {
    while (true) {
      HttpHeader header = new HttpHeader();
      // 獲取下一個 header
      input.readHeader(header);
      if (header.nameEnd == 0) {
        if (header.valueEnd == 0) {
          return;
        }
        else {
          throw new ServletException ();
        }
      }
      
      // 從 header 中獲取請求頭的 key/value,并存入 request 中
      String name = new String(header.name, 0, header.nameEnd);
      String value = new String(header.value, 0, header.valueEnd);
      request.addHeader(name, value);
      // 對一些特別的 header 做處理
      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;
        n = Integer.parseInt(value);
        request.setContentLength(n);
      }
      else if (name.equals("content-type")) {
        request.setContentType(value);
      }
    } 
  }

獲取參數:

  • 在獲取參數前會調用 HttpRequest 的 parseParameter 方法解析請求參數。參數只需解析一次也只會解析一次。
  • 參數存儲在特殊的 hashMap 中: ParameterMap
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 any parameters specified in the query string
    String queryString = getQueryString();
    try {
      RequestUtil.parseParameters(results, queryString, encoding);
    }
    catch (UnsupportedEncodingException e) {
      ;
    }

    // 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
    results.setLocked(true);
    parsed = true;
    parameters = results;
  }

4
4
4
4
4
4
4
4
4
4

4

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容