tomcat4.x/5.x的核心组件catalina连接器的部分实现代码

catalina简介

Catalina是一个成熟的软件(也就是servlet),设计和开发的十分优雅,功能结构也是模块化的。是tomcat的核心组件。
Catalina可以划分为两个模块: 连接器(connector)和容器(container)结构如下:
catalina

这个连接器来增强之前的示例一个简单servlet容器

源码下载地址
请看:链接: https://pan.baidu.com/s/1trwFTSxiyrvPyzW6N8ugFw 提取码: jhi8

运行需要之前的jar包
由于现在大部分程序都更新了tomcat8及tomcat8+,所以按照示例中的代码编写会有很多错误。
pom文件中需要引入老版本的catalina包,才会不显示红叉

<!-- https://mvnrepository.com/artifact/tomcat/catalina -->
<dependency>
    <groupId>tomcat</groupId>
    <artifactId>catalina</artifactId>
    <version>4.1.36</version>
</dependency>

这个catalina连接器比之前servlet容器示例,主要有哪些变化呢?

  1. 启动方式,由命令行改为启动类(建立新线程执行)
package com.chl.webserver.catalina.connector.http;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 负责创建一个服务器套接字,该套接字会等待传入的HTTP请求。每启动会创建一个HttpConnector实例,该实例是独立线程。
 * @author chenhailong
 *
 */
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();
  }
}
  1. 在获取socket请求流信息时,对请求行、请求头做了较好的解析(做网关项目会有类似需求)
package com.chl.webserver.catalina.connector.http;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;

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

import com.chl.webserver.catalina.ServletProcessor;
import com.chl.webserver.catalina.StaticResourceProcessor;

/* 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(normalizedUri);
    }
    else {
      ((HttpRequest) request).setRequestURI(uri);
    }

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

  /**
   * Return a context-relative path, beginning with a "/", that represents
   * the canonical version of the specified path after ".." and "." elements
   * are resolved out.  If the specified path attempts to go outside the
   * boundaries of the current context (i.e. too many ".." path elements
   * are present), return <code>null</code> instead.
   *
   * @param path Path to be normalized
   */
  protected String normalize(String path) {
    if (path == null)
      return null;
    // Create a place for the normalized path
    String normalized = path;

    // Normalize "/%7E" and "/%7e" at the beginning to "/~"
    if (normalized.startsWith("/%7E") || normalized.startsWith("/%7e"))
      normalized = "/~" + normalized.substring(4);

    // Prevent encoding '%', '/', '.' and '\', which are special reserved
    // characters
    if ((normalized.indexOf("%25") >= 0)
      || (normalized.indexOf("%2F") >= 0)
      || (normalized.indexOf("%2E") >= 0)
      || (normalized.indexOf("%5C") >= 0)
      || (normalized.indexOf("%2f") >= 0)
      || (normalized.indexOf("%2e") >= 0)
      || (normalized.indexOf("%5c") >= 0)) {
      return null;
    }

    if (normalized.equals("/."))
      return "/";

    // Normalize the slashes and add leading slash if necessary
    if (normalized.indexOf('\\') >= 0)
      normalized = normalized.replace('\\', '/');
    if (!normalized.startsWith("/"))
      normalized = "/" + normalized;

    // Resolve occurrences of "//" in the normalized path
    while (true) {
      int index = normalized.indexOf("//");
      if (index < 0)
        break;
      normalized = normalized.substring(0, index) +
        normalized.substring(index + 1);
    }

    // Resolve occurrences of "/./" in the normalized path
    while (true) {
      int index = normalized.indexOf("/./");
      if (index < 0)
        break;
      normalized = normalized.substring(0, index) +
        normalized.substring(index + 2);
    }

    // Resolve occurrences of "/../" in the normalized path
    while (true) {
      int index = normalized.indexOf("/../");
      if (index < 0)
        break;
      if (index == 0)
        return (null);  // Trying to go outside our context
      int index2 = normalized.lastIndexOf('/', index - 1);
      normalized = normalized.substring(0, index2) +
        normalized.substring(index + 3);
    }

    // Declare occurrences of "/..." (three or more dots) to be invalid
    // (on some Windows platforms this walks the directory tree!!!)
    if (normalized.indexOf("/...") >= 0)
      return (null);

    // Return the normalized path that we have completed
    return (normalized);

  }

}

  1. 对response响应做了增强,如对接三方回调,有时需要设置响应头等信息
    (贴部分代码,完成代码请自行下载)

  /**
   * Returns a default status message for the specified HTTP status code.
   *
   * @param status The status code for which a message is desired
   */
  protected String getStatusMessage(int status) {
    switch (status) {
      case SC_OK:
        return ("OK");
      case SC_ACCEPTED:
        return ("Accepted");
      case SC_BAD_GATEWAY:
        return ("Bad Gateway");
      case SC_BAD_REQUEST:
        return ("Bad Request");
      case SC_CONFLICT:
        return ("Conflict");
      case SC_CONTINUE:
        return ("Continue");
      case SC_CREATED:
        return ("Created");
      case SC_EXPECTATION_FAILED:
        return ("Expectation Failed");
      case SC_FORBIDDEN:
        return ("Forbidden");
        ...
        ...
        
        ...
      case SC_USE_PROXY:
        return ("Use Proxy");
      case 207:       // WebDAV
        return ("Multi-Status");
      case 422:       // WebDAV
        return ("Unprocessable Entity");
      case 423:       // WebDAV
        return ("Locked");
      case 507:       // WebDAV
        return ("Insufficient Storage");
      default:
        return ("HTTP Response Status " + status);
    }
  }

  public OutputStream getStream() {
    return this.output;
  }
  /**
   * Send the HTTP response headers, if this has not already occurred.
   */
  protected void sendHeaders() throws IOException {
    if (isCommitted())
      return;
    // Prepare a suitable output writer
    OutputStreamWriter osr = null;
    try {
      osr = new OutputStreamWriter(getStream(), getCharacterEncoding());
    }
    catch (UnsupportedEncodingException e) {
      osr = new OutputStreamWriter(getStream());
    }
    final PrintWriter outputWriter = new PrintWriter(osr);
    // Send the "Status:" header
    outputWriter.print(this.getProtocol());
    outputWriter.print(" ");
    outputWriter.print(status);
    if (message != null) {
      outputWriter.print(" ");
      outputWriter.print(message);
    }
    outputWriter.print("\r\n");
    // Send the content-length and content-type headers (if any)
    if (getContentType() != null) {
      outputWriter.print("Content-Type: " + getContentType() + "\r\n");
    }
    if (getContentLength() >= 0) {
      outputWriter.print("Content-Length: " + getContentLength() + "\r\n");
    }
    // Send all specified headers (if any)
    synchronized (headers) {
      Iterator names = headers.keySet().iterator();
      while (names.hasNext()) {
        String name = (String) names.next();
        ArrayList values = (ArrayList) headers.get(name);
        Iterator items = values.iterator();
        while (items.hasNext()) {
          String value = (String) items.next();
          outputWriter.print(name);
          outputWriter.print(": ");
          outputWriter.print(value);
          outputWriter.print("\r\n");
        }
      }
    }
    // Add the session ID cookie if necessary
/*    HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
    HttpSession session = hreq.getSession(false);
    if ((session != null) && session.isNew() && (getContext() != null)
      && getContext().getCookies()) {
      Cookie cookie = new Cookie("JSESSIONID", session.getId());
      cookie.setMaxAge(-1);
      String contextPath = null;
      if (context != null)
        contextPath = context.getPath();
      if ((contextPath != null) && (contextPath.length() > 0))
        cookie.setPath(contextPath);
      else

      cookie.setPath("/");
      if (hreq.isSecure())
        cookie.setSecure(true);
      addCookie(cookie);
    }
*/
    // Send all specified cookies (if any)
    synchronized (cookies) {

    }

    // Send a terminating blank line to mark the end of the headers
    outputWriter.print("\r\n");
    outputWriter.flush();

    committed = true;
  }

  public void setRequest(HttpRequest request) {
    this.request = request;
  }

  /* This method is used to serve a static page */
  public void sendStaticResource() throws IOException {
    byte[] bytes = new byte[BUFFER_SIZE];
    FileInputStream fis = null;
    try {
      /* request.getUri has been replaced by request.getRequestURI */
      File file = new File(Constants.WEB_ROOT, request.getRequestURI());
      fis = new FileInputStream(file);
      /*
         HTTP Response = Status-Line
           *(( general-header | response-header | entity-header ) CRLF)
           CRLF
           [ message-body ]
         Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
      */
      int ch = fis.read(bytes, 0, BUFFER_SIZE);
      while (ch!=-1) {
        output.write(bytes, 0, ch);
        ch = fis.read(bytes, 0, BUFFER_SIZE);
      }
    }
    catch (FileNotFoundException e) {
      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());
    }
    finally {
      if (fis!=null)
        fis.close();
    }
  }

  public void write(int b) throws IOException {
    if (bufferCount >= buffer.length)
      flushBuffer();
    buffer[bufferCount++] = (byte) b;
    contentCount++;
  }

  public void write(byte b[]) throws IOException {
    write(b, 0, b.length);
  }

  public void write(byte b[], int off, int len) throws IOException {
    // If the whole thing fits in the buffer, just put it there
    if (len == 0)
      return;
    if (len <= (buffer.length - bufferCount)) {
      System.arraycopy(b, off, buffer, bufferCount, len);
      bufferCount += len;
      contentCount += len;
      return;
    }

    // Flush the buffer and start writing full-buffer-size chunks
    flushBuffer();
    int iterations = len / buffer.length;
    int leftoverStart = iterations * buffer.length;
    int leftoverLen = len - leftoverStart;
    for (int i = 0; i < iterations; i++)
      write(b, off + (i * buffer.length), buffer.length);

    // Write the remainder (guaranteed to fit in the buffer)
    if (leftoverLen > 0)
      write(b, off + leftoverStart, leftoverLen);
  }

  /** implementation of HttpServletResponse  */

  public void addCookie(Cookie cookie) {
    if (isCommitted())
      return;
  //  if (included)
    //        return;     // Ignore any call from an included servlet
    synchronized (cookies) {
      cookies.add(cookie);
    }
  }

  public void addDateHeader(String name, long value) {
    if (isCommitted())
      return;
//    if (included)
  //          return;     // Ignore any call from an included servlet
    addHeader(name, format.format(new Date(value)));
  }

  public void addHeader(String name, String value) {
    if (isCommitted())
      return;
//        if (included)
  //          return;     // Ignore any call from an included servlet
    synchronized (headers) {
      ArrayList values = (ArrayList) headers.get(name);
      if (values == null) {
        values = new ArrayList();
        headers.put(name, values);
      }

      values.add(value);
    }
  }

  public void addIntHeader(String name, int value) {
    if (isCommitted())
      return;
//    if (included)
  //    return;     // Ignore any call from an included servlet
    addHeader(name, "" + value);
  }

  public PrintWriter getWriter() throws IOException {
    ResponseStream newStream = new ResponseStream(this);
    newStream.setCommit(false);
    OutputStreamWriter osr =
      new OutputStreamWriter(newStream, getCharacterEncoding());
    writer = new ResponseWriter(osr);
    return writer;
  }


  public void setHeader(String name, String value) {
    if (isCommitted())
      return;
//    if (included)
  //    return;     // Ignore any call from an included servlet
    ArrayList values = new ArrayList();
    values.add(value);
    synchronized (headers) {
      headers.put(name, values);
    }
    String match = name.toLowerCase();
    if (match.equals("content-length")) {
      int contentLength = -1;
      try {
        contentLength = Integer.parseInt(value);
      }
      catch (NumberFormatException e) {
        ;
      }
      if (contentLength >= 0)
        setContentLength(contentLength);
    }
    else if (match.equals("content-type")) {
      setContentType(value);
    }
  }

  public void setIntHeader(String name, int value) {
    if (isCommitted())
      return;
    //if (included)
      //return;     // Ignore any call from an included servlet
    setHeader(name, "" + value);
  }

  public void setLocale(Locale locale) {
    if (isCommitted())
      return;
    //if (included)
      //return;     // Ignore any call from an included servlet

   // super.setLocale(locale);
    String language = locale.getLanguage();
    if ((language != null) && (language.length() > 0)) {
      String country = locale.getCountry();
      StringBuffer value = new StringBuffer(language);
      if ((country != null) && (country.length() > 0)) {
        value.append('-');
        value.append(country);
      }
      setHeader("Content-Language", value.toString());
    }
  }


  1. 增加了设计模式的使用(外观模式),隐藏了复杂的实现,方便调用
public class HttpRequestFacade implements HttpServletRequest {

  private HttpServletRequest request;

  public HttpRequestFacade(HttpRequest request) {
    this.request = request;
  }

  /* implementation of the HttpServletRequest*/
  public Object getAttribute(String name) {
    return request.getAttribute(name);
  }

  public Enumeration getAttributeNames() {
    return request.getAttributeNames();
  }
  

这一章节的内容,结构的设计比上一节多了一些。也是很受益的,设计项目代码时可以参考。

注:catalina是tomcat作者喜欢的一个小岛的名字。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值