第三章 連接器

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

3.1 StringManage

Tomcat 將存儲(chǔ)錯(cuò)誤消息的 properties 文件劃分到不同的包中,每個(gè) properties 文件都是使用 org.apache.catalina.util.StringManager 類的一個(gè)實(shí)例進(jìn)行處理鼎俘。同時(shí) StringManager 是單例模式的,避免空間的浪費(fèi)怔揩。

3.2 應(yīng)用程序

本章的應(yīng)用程序包含三個(gè)模塊:

  • 啟動(dòng)模塊:負(fù)責(zé)啟動(dòng)應(yīng)用程序捉邢。
  • 連接器模塊:
    • 連接器及其支持類(HttpConnector 和 HttpProcessor);
    • HttpRequest 類和 HttpResponse 類及其支持類商膊;
    • 外觀類(HttpRequesFaced 類和 HttpResponseFaced)伏伐;
    • 常量類。
  • 核心模塊:
    • ServletProcessor : 處理 servlet 請(qǐng)求(類似 Wrapper)晕拆;
    • StaticResourceProcessor : 處理靜態(tài)資源請(qǐng)求藐翎。
本章應(yīng)用程序的 UML 類圖

3.2.1 啟動(dòng)應(yīng)用程序

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

3.2.2 HttpConnector 類

Connector 不知道 servlet 接受 Request 和 Response 的具體類型,所以使用 HttpServletRequest 和 HttpResponse 進(jìn)行傳參。同時(shí)解析 HTTP 請(qǐng)求對(duì)參數(shù)是懶加載的吝镣,在這些參數(shù)被 servlet 實(shí)例真正調(diào)用前是不會(huì)進(jìn)行解析的堤器。
Tomcat 的默認(rèn)連接器和本章程序的 Connector 都使用 SocketInputStream 獲取字節(jié)流。SocketInputStream 是 InputStream 的包裝類末贾,提供了兩個(gè)重要的方法:

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

HttpProcessor 的 process 方法對(duì)每個(gè)傳入的 HTTP 請(qǐng)求,要完成4個(gè)操作:

  • 創(chuàng)建一個(gè) HttpRequest 對(duì)象昼扛;
  • 創(chuàng)建一個(gè) HttpResponse 對(duì)象寸齐;
  • 解析 HTTP 請(qǐng)求的第一行內(nèi)容和請(qǐng)求頭信息,填充 HttpRequest 對(duì)象抄谐;
  • 將 HttpRequest 和 HttpResponse 傳遞給 ServletProcessor 或 Static-ResourceProcessor 的 process 方法渺鹦。
public void process(Socket socket) {
      SocketInputStream input= new SocketInputStream(socket.getInputStream(), 2048);
      OutputStream output = socket.getOutputStream();

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

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

parseRequest 方法解析請(qǐng)求行:
處理 URI 字符串 --> 絕對(duì)路徑檢查 --> 會(huì)話標(biāo)識(shí)符檢查 --> URI 修正 --> 設(shè)置 request 屬性

private void parseRequest(SocketInputStream input, OutputStream output) {
    // requestLine 是 HttpRequestLine 的實(shí)例
    //使用 SocketInputStream 中的信息填充 requestLine,并從 requestLine 中獲取請(qǐng)求行信息
    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
    //驗(yàn)證輸入的 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);
    }
    //檢查是否是絕對(duì)路徑
    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);
        }
      }
    }

    // 檢查查詢字符串是否還攜帶會(huì)話標(biāo)識(shí)符
    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);
    }

    //對(duì) URI 進(jìn)行修正轉(zhuǎn)化為合法的 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 方法解析請(qǐng)求頭:
獲取下一個(gè) header --> 如果 header name 和 value 為空則退出循環(huán) --> 從 header 中獲取 key/value 并放入 request --> 對(duì) cookie 和 content-type 做處理 --> 循環(huán)

private void parseHeaders(SocketInputStream input) {
    while (true) {
      HttpHeader header = new HttpHeader();
      // 獲取下一個(gè) header
      input.readHeader(header);
      if (header.nameEnd == 0) {
        if (header.valueEnd == 0) {
          return;
        }
        else {
          throw new ServletException ();
        }
      }
      
      // 從 header 中獲取請(qǐng)求頭的 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);
      // 對(duì)一些特別的 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);
      }
    } 
  }

獲取參數(shù):

  • 在獲取參數(shù)前會(huì)調(diào)用 HttpRequest 的 parseParameter 方法解析請(qǐng)求參數(shù)浦箱。參數(shù)只需解析一次也只會(huì)解析一次吸耿。
  • 參數(shù)存儲(chǔ)在特殊的 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市酷窥,隨后出現(xiàn)的幾起案子咽安,更是在濱河造成了極大的恐慌,老刑警劉巖蓬推,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妆棒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡沸伏,警方通過查閱死者的電腦和手機(jī)糕珊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毅糟,“玉大人红选,你說我怎么就攤上這事×籼兀” “怎么了纠脾?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵玛瘸,是天一觀的道長。 經(jīng)常有香客問我苟蹈,道長糊渊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任慧脱,我火速辦了婚禮渺绒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘菱鸥。我一直安慰自己宗兼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布氮采。 她就那樣靜靜地躺著殷绍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鹊漠。 梳的紋絲不亂的頭發(fā)上主到,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音躯概,去河邊找鬼登钥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛娶靡,可吹牛的內(nèi)容都是我干的牧牢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼姿锭,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼塔鳍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起艾凯,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤献幔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后趾诗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蹬蚁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年恃泪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片犀斋。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贝乎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出叽粹,到底是詐尸還是另有隱情览效,我是刑警寧澤却舀,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站锤灿,受9級(jí)特大地震影響挽拔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜但校,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一螃诅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧状囱,春花似錦术裸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至叨粘,卻和暖如春猾编,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宣鄙。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工袍镀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冻晤。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓苇羡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鼻弧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子设江,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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