深入学习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 socketSocket socket = null;try {socket = serverSocket.accept();}catch (Exception e) {continue;}// Hand this socket off to an HttpProcessorHttpProcessor 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 parserequest = new HttpRequest(input);// create HttpResponse objectresponse = 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 socketsocket.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 headerinput.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 URLif (!request.isRequestedSessionIdFromCookie()) {// Accept only the first session id cookierequest.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 lineinput.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 lineif (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 URIint 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 nameif (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 URIString 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 pathString 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// charactersif ((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 necessaryif (normalized.indexOf('\\') >= 0)normalized = normalized.replace('\\', '/');if (!normalized.startsWith("/"))normalized = "/" + normalized;// Resolve occurrences of "//" in the normalized pathwhile (true) {int index = normalized.indexOf("//");if (index < 0)break;normalized = normalized.substring(0, index) +normalized.substring(index + 1);}// Resolve occurrences of "/./" in the normalized pathwhile (true) {int index = normalized.indexOf("/./");if (index < 0)break;normalized = normalized.substring(0, index) +normalized.substring(index + 2);}// Resolve occurrences of "/../" in the normalized pathwhile (true) {int index = normalized.indexOf("/../");if (index < 0)break;if (index == 0)return (null);  // Trying to go outside our contextint 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 completedreturn (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 stringString queryString = getQueryString();try {RequestUtil.parseParameters(results, queryString, encoding);}catch (UnsupportedEncodingException e) {;}// Parse any parameters specified in the input streamString 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 resultsresults.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 = "";elsethis.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 formatsfor (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));elsereturn 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));elsereturn (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);elsereturn (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]);elsereturn (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);elsereturn 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 sizeprivate 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 mechanismif (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:       // WebDAVreturn ("Multi-Status");case 422:       // WebDAVreturn ("Unprocessable Entity");case 423:       // WebDAVreturn ("Locked");case 507:       // WebDAVreturn ("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 writerOutputStreamWriter osr = null;try {osr = new OutputStreamWriter(getStream(), getCharacterEncoding());}catch (UnsupportedEncodingException e) {osr = new OutputStreamWriter(getStream());}final PrintWriter outputWriter = new PrintWriter(osr);// Send the "Status:" headeroutputWriter.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);elsecookie.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 headersoutputWriter.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 thereif (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 chunksflushBuffer();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 servletsynchronized (cookies) {cookies.add(cookie);}}public void addDateHeader(String name, long value) {if (isCommitted())return;
//    if (included)//          return;     // Ignore any call from an included servletaddHeader(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 servletsynchronized (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 servletaddHeader(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");elsereturn (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 servletthis.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 servletsetHeader(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 servletArrayList 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 servletsetHeader(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 URLClassLoaderURL[] 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());}}
}

tomcat之连接器相关推荐

  1. tomcat(3)连接器

    [0]README 0.1)本文部分内容转自"深入剖析tomcat",旨在学习 tomcat(3)连接器 的基础知识: 0.2)Catalina 中有两个主要的模块:连接器(Ser ...

  2. httpd +jk_虚拟主机+ Apache httpd服务器+ Tomcat + mod_jk连接器

    httpd +jk 在上一篇文章( Tomcat中的虚拟主机 )中,我们讨论了如何在Tomcat中设置虚拟主机. 它具有成本效益,因为仅一个公共IP即可托管多个域. 如果我们有大型组织,并且每个部门都 ...

  3. 大厂面试真题解读:Tomcat的连接器是如何设计的?

    导读 为什么要学习Tomcat的设计思路,对我们这些 crud 工程师有用吗?现代背景下,我们不仅可以学到Tomcat的架构,学会从宏观上怎么去设计一个复杂系统,怎么设计顶层模块,以及模块之间关系. ...

  4. Tomcat AJP连接器配置secretRequired=true,但是属性secret确实空或者空字符串

    Tomcat报错 27-Jan-2022 14:09:07.450 信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄[" ...

  5. tomcat Connector 连接器

    连接器的核心功能,本文去除非核心功能,留下整个程序的框架,便于理解. 1.接受连接请求 2.创建request,和response. 3.调用容器对应的Invoke方法, 首先看类的依赖结构. 1.C ...

  6. IDEA启动Tomcat AJP连接器配置secretRequired=“true“,但是属性secret确实空或者空字符串,这样的组合是无效的解决办法

    修改Tomcat安装目录=>conf=>server.xml 配置文件 ========================== Connector标签里: 加上 secret="T ...

  7. 谈谈Tomcat连接器

    " Tomcat 作为应用最广泛的 Web 容器被各大厂商所使用,从体系结构上看 Tomcat 分为连接器和容器两个部分.其中连接器负责 IO 请求转换.网络请求解析以及请求适配等工作. 图 ...

  8. tomcat(4)Tomcat的默认连接器

    [0]README 0.0)本文部分文字描述转自:"深入剖析tomcat",旨在学习 tomat(4)Tomat的默认连接器 的基础知识: 0.1)Tomcat中的连接器是一个独立 ...

  9. 《How Tomcat Works》读书笔记(三)--Connector(连接器)

    <How Tomcat Works>读书笔记(三)--Connector(连接器) 这是<How Tomcat Works>第三四章的读书笔记.主要写了Tomcat4.0默认的 ...

  10. 详解 Tomcat 的连接数与线程池

    原文出处:编程迷思 前言 在使用tomcat时,经常会遇到连接数.线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector). 在前面的文章 详解Tomcat配置文 ...

最新文章

  1. [错误收录] the pyflakes.vim plugin requires Vim to be compiled with +python
  2. HTTP请求常见状态码
  3. Nginx加入linux系统服务
  4. UA OPTI512R 傅立叶光学导论 采样定理例题
  5. 深入理解 Java 线程池:ThreadPoolExecutor
  6. 如何使用yunyang tensorflow-yolov3训练自己的数据集并使用 Intel realsense D435 传输视频流应用于实时检测?
  7. 常用netbeans快捷键
  8. leetcode刷题 162.寻找峰值
  9. WinForm窗体自适应分辨率
  10. 曲线图绘制软件_OpenFoam离心泵数值仿真及不同CFD软件间对比
  11. solr的安装使用tomcat容器
  12. java: Internal error in the mapping processor: java.lang.NullPointerException问题解决
  13. 淘淘商城系列(一)—— 淘淘商城介绍(二)
  14. 判断拐点的条件_拐点的定义
  15. 计算机无法进入操作系统怎么办,老司机教你电脑开机无法进入系统怎么办
  16. 5G还没用上,4G却越来越慢了?
  17. html怎样手机播放本地视频播放器,手机优酷怎么播放本地视频 本地文件导入方法...
  18. 135微信编辑html语言,135微信编辑器如何使用 135编辑器微信文章编辑教程
  19. 《面试集:自用》《待完善... ...》
  20. MySql生成日历表

热门文章

  1. ASP.NET 即时通信,WebSocket服务端实例
  2. oracle解一元二次方程,第 6 章 浮点运算
  3. 风笑天社会研究方法第5版笔记和课后答案
  4. 目标跟踪CVPR,ICCV,ECCV文章及代码实时更新
  5. ARM学习day6-->看门狗定时器
  6. word文档中如何将软回车替换为硬回车
  7. EDCSA OPENSSL签名验签方法
  8. 弱水三千的由来(转)
  9. 星际、魔兽3、红警完全对比
  10. python 分类变量回归_虚拟/分类变量线性回归