tomcat之连接器

来源:互联网 发布:js name选择器 编辑:程序博客网 时间:2024/05/18 03:17

深入学习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(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);  }}

SocketInputStream 是org.apache.catalina.connector.http.SocketInputStream。该类提供了获取请求行(request line)和请求头(request header)的方法。通过传入一个 InputStream 对象和一个代表缓冲区大小的整数值来创建
SocketInputStream 对象。

HttpProcessor 的 process 调用其私有方法 parseRequest 来解析请求行(request line,即 http 请求的第一行)。下面是一个请求行(request line)的例子:

GET /myApp/ModernServlet?userName=tarzan&password=pwd HTTP/1.1

注意:“GET”后面和“HTTP”前面各有一个空格。
请求行的第 2 部分是 uri 加上查询字符串。在上面的例子中,uri 是:
/myApp/ModernServlet
问号后面的都是查询字符串,这里是:
userName=tarzan&password=pwd
在 servlet/jsp 编程中,参数 jsessionid 通常是嵌入到 cookie 中的,也可以将其嵌入到查询字符串中 。

请求头(request header)由 HttpHeader 对象表示。可以通过 HttpHeader 的无参构造方法建立对象,并将其作为参数传给 SocketInputStream 的 readHeader 方法,该方法会自动填充 HttpHeader 对象。parseHeader
方法内有一个循环体,不断的从 SocketInputStream 中读取 header 信息,直到读完。获取 header 的 name 和value 值可使用下米娜的语句:
String name = new String(header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd);
获取到 header 的 name 和 value 后,要将其填充到 HttpRequest 的 header 属性(hashMap 类型)中:
request.addHeader(name, value);
其中某些 header 要设置到 request 对象的属性中,如 contentLength 等。

cookie 是由浏览器作为请求头的一部分发送的,这样的请求头的名字是 cookie,它的值是一个 keyvalue
对。举例如下:
Cookie: userName=budi; password=pwd;
对 cookie 的解析是通过 org.apache.catalina.util.RequestUtil 类的 parseCookieHeader 方法完成的。该方法接受一个 cookie 头字符串,返回一个 javax.servlet.http.Cookie 类型的数组。

我们通过解析http请求的信息并存在httprequest和httpresponse中,并通过process方法传递到core模块。

创建一个HttpRequest对象
HttpRequest类在上面两节的内容上增加了三个变量和其一些方法:

protected HashMap headers = new HashMap(); protected ArrayList cookies = new ArrayList(); protected ParameterMap parameters = null;

这三个变量存放着Http请求的头部,cookies和参数。

因此,一个servlet程序员可以从javax.servlet.http.HttpServletRequest中的下列方法中取得正确的返回 值:
getCookies,getDateHeader,getHeader, getHeaderNames, getHeaders, getParameter, getPrameterMap,getParameterNames和getParameterValues。

HttpRequest

import ex03.pyrmont.connector.RequestStream;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;import javax.servlet.http.Cookie;import javax.servlet.RequestDispatcher;import javax.servlet.ServletInputStream;import java.security.Principal;import java.io.InputStream;import java.io.InputStreamReader;import java.io.IOException;import java.io.BufferedReader;import java.io.UnsupportedEncodingException;import java.net.InetAddress;import java.net.Socket;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Date;import java.util.Enumeration;import java.util.HashMap;import java.util.Locale;import java.util.Map;import org.apache.catalina.util.Enumerator;import org.apache.catalina.util.ParameterMap;import org.apache.catalina.util.RequestUtil;public class HttpRequest implements HttpServletRequest {  private String contentType;  private int contentLength;  private InetAddress inetAddress;  private InputStream input;  private String method;  private String protocol;  private String queryString;  private String requestURI;  private String serverName;  private int serverPort;  private Socket socket;  private boolean requestedSessionCookie;  private String requestedSessionId;  private boolean requestedSessionURL;  /**   * The request attributes for this request.   */  protected HashMap attributes = new HashMap();  /**   * The authorization credentials sent with this Request.   */  protected String authorization = null;  /**   * The context path for this request.   */  protected String contextPath = "";  /**   * The set of cookies associated with this Request.   */  protected ArrayList cookies = new ArrayList();  /**   * An empty collection to use for returning empty Enumerations.  Do not   * add any elements to this collection!   */  protected static ArrayList empty = new ArrayList();  /**   * The set of SimpleDateFormat formats to use in getDateHeader().   */  protected SimpleDateFormat formats[] = {    new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),    new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),    new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)  };  /**   * The HTTP headers associated with this Request, keyed by name.  The   * values are ArrayLists of the corresponding header values.   */  protected HashMap headers = new HashMap();  /**   * The parsed parameters for this request.  This is populated only if   * parameter information is requested via one of the   * <code>getParameter()</code> family of method calls.  The key is the   * parameter name, while the value is a String array of values for this   * parameter.   * <p>   * <strong>IMPLEMENTATION NOTE</strong> - Once the parameters for a   * particular request are parsed and stored here, they are not modified.   * Therefore, application level access to the parameters need not be   * synchronized.   */  protected ParameterMap parameters = null;  /**   * Have the parameters for this request been parsed yet?   */  protected boolean parsed = false;  protected String pathInfo = null;  /**   * The reader that has been returned by <code>getReader</code>, if any.   */  protected BufferedReader reader = null;  /**   * The ServletInputStream that has been returned by   * <code>getInputStream()</code>, if any.   */  protected ServletInputStream stream = null;  public HttpRequest(InputStream input) {    this.input = input;  }  public void addHeader(String name, String value) {    name = name.toLowerCase();    synchronized (headers) {      ArrayList values = (ArrayList) headers.get(name);      if (values == null) {        values = new ArrayList();        headers.put(name, values);      }      values.add(value);    }  }  /**   * Parse the parameters of this request, if it has not already occurred.   * If parameters are present in both the query string and the request   * content, they are merged.   */  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;  }  public void addCookie(Cookie cookie) {    synchronized (cookies) {      cookies.add(cookie);    }  }  /**   * Create and return a ServletInputStream to read the content   * associated with this Request.  The default implementation creates an   * instance of RequestStream associated with this request, but this can   * be overridden if necessary.   *   * @exception IOException if an input/output error occurs   */  public ServletInputStream createInputStream() throws IOException {    return (new RequestStream(this));  }  public InputStream getStream() {    return input;  }  public void setContentLength(int length) {    this.contentLength = length;  }  public void setContentType(String type) {    this.contentType = type;  }  public void setInet(InetAddress inetAddress) {    this.inetAddress = inetAddress;  }  public void setContextPath(String path) {    if (path == null)      this.contextPath = "";    else      this.contextPath = path;  }  public void setMethod(String method) {    this.method = method;  }  public void setPathInfo(String path) {    this.pathInfo = path;  }  public void setProtocol(String protocol) {    this.protocol = protocol;  }  public void setQueryString(String queryString) {    this.queryString = queryString;  }  public void setRequestURI(String requestURI) {    this.requestURI = requestURI;  }  /**   * Set the name of the server (virtual host) to process this request.   *   * @param name The server name   */  public void setServerName(String name) {    this.serverName = name;  }  /**   * Set the port number of the server to process this request.   *   * @param port The server port   */  public void setServerPort(int port) {    this.serverPort = port;  }  public void setSocket(Socket socket) {    this.socket = socket;  }  /**   * Set a flag indicating whether or not the requested session ID for this   * request came in through a cookie.  This is normally called by the   * HTTP Connector, when it parses the request headers.   *   * @param flag The new flag   */  public void setRequestedSessionCookie(boolean flag) {    this.requestedSessionCookie = flag;  }  public void setRequestedSessionId(String requestedSessionId) {    this.requestedSessionId = requestedSessionId;  }  public void setRequestedSessionURL(boolean flag) {    requestedSessionURL = flag;  }  /* implementation of the HttpServletRequest*/  public Object getAttribute(String name) {    synchronized (attributes) {      return (attributes.get(name));    }  }  public Enumeration getAttributeNames() {    synchronized (attributes) {      return (new Enumerator(attributes.keySet()));    }  }  public String getAuthType() {    return null;  }  public String getCharacterEncoding() {    return null;  }  public int getContentLength() {    return contentLength ;  }  public String getContentType() {    return contentType;  }  public String getContextPath() {    return contextPath;  }  public Cookie[] getCookies() {    synchronized (cookies) {      if (cookies.size() < 1)        return (null);      Cookie results[] = new Cookie[cookies.size()];      return ((Cookie[]) cookies.toArray(results));    }  }  public long getDateHeader(String name) {    String value = getHeader(name);    if (value == null)      return (-1L);    // Work around a bug in SimpleDateFormat in pre-JDK1.2b4    // (Bug Parade bug #4106807)    value += " ";    // Attempt to convert the date header in a variety of formats    for (int i = 0; i < formats.length; i++) {      try {        Date date = formats[i].parse(value);        return (date.getTime());      }      catch (ParseException e) {        ;      }    }    throw new IllegalArgumentException(value);  }  public String getHeader(String name) {    name = name.toLowerCase();    synchronized (headers) {      ArrayList values = (ArrayList) headers.get(name);      if (values != null)        return ((String) values.get(0));      else        return null;    }  }  public Enumeration getHeaderNames() {    synchronized (headers) {      return (new Enumerator(headers.keySet()));    }  }  public Enumeration getHeaders(String name) {    name = name.toLowerCase();    synchronized (headers) {      ArrayList values = (ArrayList) headers.get(name);      if (values != null)        return (new Enumerator(values));      else        return (new Enumerator(empty));    }  }  public ServletInputStream getInputStream() throws IOException {    if (reader != null)      throw new IllegalStateException("getInputStream has been called");    if (stream == null)      stream = createInputStream();    return (stream);  }  public int getIntHeader(String name) {    String value = getHeader(name);    if (value == null)      return (-1);    else      return (Integer.parseInt(value));  }  public Locale getLocale() {    return null;  }  public Enumeration getLocales() {    return null;  }  public String getMethod() {    return method;  }  public String getParameter(String name) {    parseParameters();    String values[] = (String[]) parameters.get(name);    if (values != null)      return (values[0]);    else      return (null);  }  public Map getParameterMap() {    parseParameters();    return (this.parameters);  }  public Enumeration getParameterNames() {    parseParameters();    return (new Enumerator(parameters.keySet()));  }  public String[] getParameterValues(String name) {    parseParameters();    String values[] = (String[]) parameters.get(name);    if (values != null)      return (values);    else      return null;  }  public String getPathInfo() {    return pathInfo;  }  public String getPathTranslated() {    return null;  }  public String getProtocol() {    return protocol;  }  public String getQueryString() {    return queryString;  }  public BufferedReader getReader() throws IOException {    if (stream != null)      throw new IllegalStateException("getInputStream has been called.");    if (reader == null) {      String encoding = getCharacterEncoding();      if (encoding == null)        encoding = "ISO-8859-1";      InputStreamReader isr =        new InputStreamReader(createInputStream(), encoding);        reader = new BufferedReader(isr);    }    return (reader);  }  public String getRealPath(String path) {    return null;  }  public String getRemoteAddr() {    return null;  }  public String getRemoteHost() {    return null;  }  public String getRemoteUser() {    return null;  }  public RequestDispatcher getRequestDispatcher(String path) {    return null;  }  public String getScheme() {   return null;  }  public String getServerName() {    return null;  }  public int getServerPort() {    return 0;  }  public String getRequestedSessionId() {    return null;  }  public String getRequestURI() {    return requestURI;  }  public StringBuffer getRequestURL() {    return null;  }  public HttpSession getSession() {    return null;  }  public HttpSession getSession(boolean create) {    return null;  }  public String getServletPath() {    return null;  }  public Principal getUserPrincipal() {    return null;  }  public boolean isRequestedSessionIdFromCookie() {    return false;  }  public boolean isRequestedSessionIdFromUrl() {    return isRequestedSessionIdFromURL();  }  public boolean isRequestedSessionIdFromURL() {    return false;  }  public boolean isRequestedSessionIdValid() {    return false;  }  public boolean isSecure() {    return false;  }  public boolean isUserInRole(String role) {    return false;  }  public void removeAttribute(String attribute) {  }  public void setAttribute(String key, Object value) {  }  /**   * Set the authorization credentials sent with this request.   *   * @param authorization The new authorization credentials   */  public void setAuthorization(String authorization) {    this.authorization = authorization;  }  public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException {  }}

HttpResponse类

import ex03.pyrmont.connector.ResponseStream;import ex03.pyrmont.connector.ResponseWriter;import ex03.pyrmont.connector.http.Constants;import java.io.OutputStream;import java.io.IOException;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.File;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.io.UnsupportedEncodingException;import java.text.SimpleDateFormat;import java.util.Locale;import java.util.ArrayList;import java.util.Date;import java.util.HashMap;import java.util.Iterator;import javax.servlet.ServletOutputStream;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletResponse;import org.apache.catalina.util.CookieTools;public class HttpResponse implements HttpServletResponse {  // the default buffer size  private static final int BUFFER_SIZE = 1024;  HttpRequest request;  OutputStream output;  PrintWriter writer;  protected byte[] buffer = new byte[BUFFER_SIZE];  protected int bufferCount = 0;  /**   * Has this response been committed yet?   */  protected boolean committed = false;  /**   * The actual number of bytes written to this Response.   */  protected int contentCount = 0;  /**   * The content length associated with this Response.   */  protected int contentLength = -1;  /**   * The content type associated with this Response.   */  protected String contentType = null;  /**   * The character encoding associated with this Response.   */  protected String encoding = null;  /**   * The set of Cookies associated with this Response.   */  protected ArrayList cookies = new ArrayList();  /**   * The HTTP headers explicitly added via addHeader(), but not including   * those to be added with setContentLength(), setContentType(), and so on.   * This collection is keyed by the header name, and the elements are   * ArrayLists containing the associated values that have been set.   */  protected HashMap headers = new HashMap();  /**   * The date format we will use for creating date headers.   */  protected final SimpleDateFormat format =    new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz",Locale.US);  /**   * The error message set by <code>sendError()</code>.   */  protected String message = getStatusMessage(HttpServletResponse.SC_OK);  /**   * The HTTP status code associated with this Response.   */  protected int status = HttpServletResponse.SC_OK;  public HttpResponse(OutputStream output) {    this.output = output;  }  /**   * call this method to send headers and response to the output   */  public void finishResponse() {    // sendHeaders();    // Flush and close the appropriate output mechanism    if (writer != null) {      writer.flush();      writer.close();    }  }  public int getContentLength() {    return contentLength;  }  public String getContentType() {    return contentType;  }  protected String getProtocol() {    return request.getProtocol();  }  /**   * 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_GATEWAY_TIMEOUT:        return ("Gateway Timeout");      case SC_GONE:        return ("Gone");      case SC_HTTP_VERSION_NOT_SUPPORTED:        return ("HTTP Version Not Supported");      case SC_INTERNAL_SERVER_ERROR:        return ("Internal Server Error");      case SC_LENGTH_REQUIRED:        return ("Length Required");      case SC_METHOD_NOT_ALLOWED:        return ("Method Not Allowed");      case SC_MOVED_PERMANENTLY:        return ("Moved Permanently");      case SC_MOVED_TEMPORARILY:        return ("Moved Temporarily");      case SC_MULTIPLE_CHOICES:        return ("Multiple Choices");      case SC_NO_CONTENT:        return ("No Content");      case SC_NON_AUTHORITATIVE_INFORMATION:        return ("Non-Authoritative Information");      case SC_NOT_ACCEPTABLE:        return ("Not Acceptable");      case SC_NOT_FOUND:        return ("Not Found");      case SC_NOT_IMPLEMENTED:        return ("Not Implemented");      case SC_NOT_MODIFIED:        return ("Not Modified");      case SC_PARTIAL_CONTENT:        return ("Partial Content");      case SC_PAYMENT_REQUIRED:        return ("Payment Required");      case SC_PRECONDITION_FAILED:        return ("Precondition Failed");      case SC_PROXY_AUTHENTICATION_REQUIRED:        return ("Proxy Authentication Required");      case SC_REQUEST_ENTITY_TOO_LARGE:        return ("Request Entity Too Large");      case SC_REQUEST_TIMEOUT:        return ("Request Timeout");      case SC_REQUEST_URI_TOO_LONG:        return ("Request URI Too Long");      case SC_REQUESTED_RANGE_NOT_SATISFIABLE:        return ("Requested Range Not Satisfiable");      case SC_RESET_CONTENT:        return ("Reset Content");      case SC_SEE_OTHER:        return ("See Other");      case SC_SERVICE_UNAVAILABLE:        return ("Service Unavailable");      case SC_SWITCHING_PROTOCOLS:        return ("Switching Protocols");      case SC_UNAUTHORIZED:        return ("Unauthorized");      case SC_UNSUPPORTED_MEDIA_TYPE:        return ("Unsupported Media Type");      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) {      Iterator items = cookies.iterator();      while (items.hasNext()) {        Cookie cookie = (Cookie) items.next();        outputWriter.print(CookieTools.getCookieHeaderName(cookie));        outputWriter.print(": ");        outputWriter.print(CookieTools.getCookieHeaderValue(cookie));        outputWriter.print("\r\n");      }    }    // 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 boolean containsHeader(String name) {    synchronized (headers) {      return (headers.get(name)!=null);    }  }  public String encodeRedirectURL(String url) {    return null;  }  public String encodeRedirectUrl(String url) {    return encodeRedirectURL(url);  }  public String encodeUrl(String url) {    return encodeURL(url);  }  public String encodeURL(String url) {    return null;  }  public void flushBuffer() throws IOException {    //committed = true;    if (bufferCount > 0) {      try {        output.write(buffer, 0, bufferCount);      }      finally {        bufferCount = 0;      }    }  }  public int getBufferSize() {    return 0;  }  public String getCharacterEncoding() {    if (encoding == null)      return ("ISO-8859-1");    else      return (encoding);  }  public Locale getLocale() {    return null;  }  public ServletOutputStream getOutputStream() throws IOException {    return null;  }  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;  }  /**   * Has the output of this response already been committed?   */  public boolean isCommitted() {    return (committed);  }  public void reset() {  }  public void resetBuffer() {  }  public void sendError(int sc) throws IOException {  }  public void sendError(int sc, String message) throws IOException {  }  public void sendRedirect(String location) throws IOException {  }  public void setBufferSize(int size) {  }  public void setContentLength(int length) {    if (isCommitted())      return;//    if (included)  //     return;     // Ignore any call from an included servlet    this.contentLength = length;  }  public void setContentType(String type) {  }  public void setDateHeader(String name, long value) {    if (isCommitted())      return;//    if (included)  //    return;     // Ignore any call from an included servlet    setHeader(name, format.format(new Date(value)));  }  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());    }  }  public void setStatus(int sc) {  }  public void setStatus(int sc, String message) {  }}

httpconnector调用httpprocessor的process方法,通过传递socket对象,连接器解析HTTP请求头部并让servlet可以获得头部, cookies, 参数名/值等等。这就是连接器的重要作用。

3.3 core模块

StaticResourceProcessor

import ex03.pyrmont.connector.http.HttpRequest;import ex03.pyrmont.connector.http.HttpResponse;import java.io.IOException;public class StaticResourceProcessor {  public void process(HttpRequest request, HttpResponse response) {    try {      response.sendStaticResource();    }    catch (IOException e) {      e.printStackTrace();    }  }}

ServletProcessor

import ex03.pyrmont.connector.http.Constants;import ex03.pyrmont.connector.http.HttpRequest;import ex03.pyrmont.connector.http.HttpResponse;import ex03.pyrmont.connector.http.HttpRequestFacade;import ex03.pyrmont.connector.http.HttpResponseFacade;import java.io.File;import java.io.IOException;import java.net.URL;import java.net.URLClassLoader;import java.net.URLStreamHandler;import javax.servlet.Servlet;public class ServletProcessor {  public void process(HttpRequest request, HttpResponse response) {    String uri = request.getRequestURI();    String servletName = uri.substring(uri.lastIndexOf("/") + 1);    URLClassLoader loader = null;    try {      // create a URLClassLoader      URL[] urls = new URL[1];      URLStreamHandler streamHandler = null;      File classPath = new File(Constants.WEB_ROOT);      String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;      urls[0] = new URL(null, repository, streamHandler);      loader = new URLClassLoader(urls);    }    catch (IOException e) {      System.out.println(e.toString() );    }    Class myClass = null;    try {      myClass = loader.loadClass(servletName);    }    catch (ClassNotFoundException e) {      System.out.println(e.toString());    }    Servlet servlet = null;    try {      servlet = (Servlet) myClass.newInstance();      HttpRequestFacade requestFacade = new HttpRequestFacade(request);      HttpResponseFacade responseFacade = new HttpResponseFacade(response);      servlet.service(requestFacade, responseFacade);      ((HttpResponse) response).finishResponse();    }    catch (Exception e) {      System.out.println(e.toString());    }    catch (Throwable e) {      System.out.println(e.toString());    }  }}
0 0
原创粉丝点击