tomcat之连接器

本文深入探讨了Tomcat的连接器,包括Catalina架构、StringManager类的作用以及连接器模块的划分。连接器负责接收HTTP请求,创建request和response对象,将流程传递给容器处理servlet。同时介绍了如何模仿Tomcat实现一个简单的连接器,将其分为startup、connector和core三个模块,讲解了各模块的功能和实现细节。
摘要由CSDN通过智能技术生成
深入学习Java Web服务器系列三

一个简单的连接器

下面我们来学习tomcat中的连接器。
首先我们先了解一下Catalina的结构图。

1. Catalina架构图

catalina 就是Tomcat服务器使用Servlet容器的名字。
Tomcat的核心可以分为3个部分:

  • Web容器—处理静态页面;
  • catalina —处理servlet;
  • JSP容器 — jsp页面翻译成一般的servlet

我们可以把catalina看成是两个主要模块组成的,连接器(connector)和容器(container)。

这里写图片描述

连接器是用来“连接”容器里边的请求的。它的工作是为接收到每一个HTTP请求构造一个request和response对象。然后它把流程传递给容器。容器从连接器接收到requset和response对象之后调用servlet的service方法用于响应。

在本系列的前一篇博文中,一个简单的servlet容器,我们把创建request和response对象的功能直接交给了我们的容器使用,而本篇博文,我们将写一个可以创建更好的请求和响应对象的连接器,用来改善之前的程序。

2. StringManager类(Tomcat5)

在Tomcat中,错误信息对于系统管理员和servlet程序员都是有用的。例 如,Tomcat记录错误信息,让系统管理员可以定位发生的任何异常。对servlet程序员来说,Tomcat会在抛出的任何一个 javax.servlet.ServletException中发送一个错误信息,这样程序员可以知道他/她的servlet究竟发送什么错误了。

Tomcat所采用的方法是在一个属性文件里边存储错误信息,这样,可以容易的修改这些信息。不过,Tomcat中有数以百计的类。把所有类使用的错误信 息存储到一个大的属性文件里边将会容易产生维护的噩梦。为了避免这一情况,Tomcat为每个包都分配一个属性文件。例如,在包 org.apache.catalina.connector里边的属性文件包含了该包所有的类抛出的所有错误信息。每个属性文件都会被一个 org.apache.catalina.util.StringManager类的实例所处理。当Tomcat运行时,将会有许多 StringManager实例,每个实例会读取包对应的一个属性文件。此外,由于Tomcat的受欢迎程度,提供多种语言的错误信息也是有意义的。

当包里边的一个类需要查找放在该包属性文件的一个错误信息时,它首先会获得一个StringManager实例。不过,相同包里边的许多类可能也需要 StringManager,为每个对象创建一个StringManager实例是一种资源浪费。因此,StringManager类被设计成一个StringManager实例可以被包里边的所有类共享,这里,StringManager被设计成了单例模式的。我们通过传递一个包名来调用它的公共静态方法 getManager来获得一个实例。每个实例存储在一个以包名为键(key)的Hashtable中。

private static Hashtable managers = new Hashtable();
public synchronized static StringManager getManager(String packageName)
{
    StringManager mgr = (StringManager)managers.get(packageName);
    if (mgr == null)
    {
        mgr = new StringManager(packageName);
        managers.put(packageName, mgr);
    }
    return mgr;
}

我们将在这篇博文中的程序中使用这种思想。

3. 模块划分

下面我们自己仿照tomcat来实现一个自己的连接器,我们将把本篇博文中的程序分成三个模块,connector, startup和core。

startup模块只有一个类,Bootstrap,用来启动应用的。

connector模块的类可以分为五组:
- 连接器和它的支撑类(HttpConnector和HttpProcessor)
- 指代HTTP请求的类(HttpRequest)和它的辅助类
- 指代HTTP响应的类(HttpResponse)和它的辅助类。
- Facade类(HttpRequestFacade和HttpResponseFacade)
- Constant类

core模块由两个类组成:ServletProcessor和StaticResourceProcessor。

程序的uml图如下图所示:

这里写图片描述

3.1 startup模块

startup模块中只有一个启动类。

Bootstrap类
Bootstrap类中的main方法实例化HttpConnector类并调用它的start方法

import ex03.pyrmont.connector.http.HttpConnector;

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

HttpConnector类的定义见下面模块。

3.2 connector模块

HttpConnector类

HttpConnector类指代一个连接器,职责是创建一个服务器套接字用来等待前来的HTTP请求。
HttpConnector类实现了java.lang.Runnable,所以它能被它自己的线程专用。当你启动应用程序,一个HttpConnector的实例被创建,并且它的run方法被执行。
一个HttpConnector主要完成下面的事情:

  • 等待HTTP请求
  • 为每个请求创建个HttpProcessor实例
  • 调用HttpProcessor的process方法
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class HttpConnector implements Runnable {
   

  boolean stopped;
  private String scheme = "http";

  public String getScheme() {
    return scheme;
  }

  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类
HttpProcessor类的process方法接受前来的HTTP请求的套接字,会做下面的事情

  • 创建一个HttpRequest对象
  • 创建一个HttpResponse对象
  • 解析HTTP请求的第一行和头部,并放到HttpRequest对象
  • 解析HttpRequest和HttpResponse对象到一个ServletProcessor或者
    StaticResourceProcessor
import ex03.pyrmont.ServletProcessor;
import ex03.pyrmont.StaticResourceProcessor;

import java.net.Socket;
import java.io.OutputStream;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;

import org.apache.catalina.util.RequestUtil;
import org.apache.catalina.util.StringManager;

/* this class used to be called HttpServer */
public class HttpProcessor {
   

  public HttpProcessor(HttpConnector connector) {
    this.connector = connector;
  }
  /**
   * The HttpConnector with which this processor is associated.
   */
  private HttpConnector connector = null;
  private HttpRequest request;
  private HttpRequestLine requestLine = new HttpRequestLine();
  private HttpResponse response;

  protected String method = null;
  protected String queryString = null;

  /**
   * The string manager for this package.
   */
  protected StringManager sm =
    StringManager.getManager("ex03.pyrmont.connector.http");

  public void process(Socket socket) {
    SocketInputStream input = null;
    OutputStream output = null;
    try {
      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);

      //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();
    }
  }

  /**
   * This method is the simplified version of the similar method in
   * org.apache.catalina.connector.http.HttpProcessor.
   * However, this method only parses some "easy" headers, such as
   * "cookie", "content-length", and "content-type", and ignore other headers.
   * @param input The input stream connected to our socket
   *
   * @exception IOException if an input/output error occurs
   * @exception ServletException if a parsing error occurs
   */
  private void parseHeaders(SocketInputStream input)
    throws IOException, ServletException {
    while (true) {
      HttpHeader header = new HttpHeader();;

      // Read the next header
      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);
      // 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
  }


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

    // Parse the incoming request line
    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
    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
    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);
    }


    // 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);
        }
      }
    }

    // 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(normalizedU
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值