美文网首页
第三章 连接器

第三章 连接器

作者: AssassinFGO | 来源:发表于2017-04-20 11:04 被阅读0次

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

相关文章

网友评论

      本文标题:第三章 连接器

      本文链接:https://www.haomeiwen.com/subject/bygkzttx.html