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()); } }}
- Tomcat之连接器
- tomcat之连接器
- tomcat连接器
- 浅析Tomcat之Coyote连接器架构分析
- Tomcat源码阅读之初始化连接器组件
- 浅析Tomcat之Coyote连接器架构分析
- Tomcat源码解析之连接器解析请求
- Tomcat优化之连接器运行模式优化
- Tomcat优化之连接器其他参数优化
- TOMCAT内核之旅--连接器(Connector)--学习心得(三)
- 深入理解 Tomcat(八)源码剖析之连接器
- Tomcat连接器:Coyote框架
- Tomcat的默认连接器
- Tomcat连接器:Coyote框架
- Tomcat连接器:Coyote框架
- Tomcat配置加密连接器
- Tomcat连接器:Coyote框架
- tomcat(3)连接器
- ASP.NET MVC 5 (五)c#的lambda表达式、LinQ和Async异步处理
- java.lang.OutOfMemoryError:GC overhead limit exceeded
- 小鑫の日常系列故事(七)——小纸条 (sdut oj)
- Objective-C NSDictionary
- React Native Awesome(汇聚知识,分享精华)
- tomcat之连接器
- Python八皇后问题的学习体会与分析-递归的运用
- Ubuntu及RHEL双Linux操作系统安装教程
- CF558A
- java正则表达式练习
- JVM学习(一)
- AndroidStudio下通过JNI实现java和c互调
- 一个在JSP页面输出“HelloWorld”的Spring MVC实例
- 历届试题 错误票据