Catalina有两个主要的模块:connector和container,connector接收http请求,发送给container进行处理。container必须创建HttpServletRequest和HttpServletResponse的实例,然后传递给被调用的servlet的service方法。在这篇文章的应用中,connector解析HTTP请求头,并允许servlet获取headers, cookies, parameter names/values。
本篇的应用由三个模块组成:connector, startup, 和core.
startup模块仅包含一个类:BootStrap,它是应用的入口
connector模块分为5个类别:
- connector 和它的支持(supporting )类(HttpConnector 和HttpProcessor )
- 代表HTTP 请求的类(HttpRequest )及 其支持类
- 代表HTTP 响应的类(HttpResponse )及其支持类
- 门面(Facade )类(HttpRequestFacade 和HttpResponseFacade )
- Constant 类
在这章的应用中,监听HTTP请求的任务交给了HttpConnector类,创建http请求和响应的任务交给了HttpProcessor类。HttpRequest类代表一个请求,HttpRespons代表一个响应。HttpRequest必须实现javax.servlet.http.HttpServletRequest接口。一个HttpRequest对象将会被转换成(cast)HttpServletRequest的实例然后传递给被请求的servlet的service方法。因此,每个HttpRequest的实例必须拥有合适的成员,被分配给HttpRequest的值有:URI, query string, parameters, cookies and 其他的 headers
SocketInputStream类包含两个重要的方法:readRequestLine和readHeader。readRequestLine返回请求字符串的第一行,readHeader用来获取名值对。
本篇的应用包含如下的结构:
Starting the Application
The Connector
Creating an HttpRequest Object
Creating an HttpResponse Object
Static resource processor and servlet processor
Running the Application
Starting the Application
ex03.pyrmont.startup.Bootstrap类为起点类,源代码如下:
package ex03.pyrmont.startup;
import ex03.pyrmont.connector.http.HttpConnector;
public final class Bootstrap {
public static void main(String[] args) {
HttpConnector connector = new HttpConnector();
connector.start();
}
}
HttpConnector类的main方法实例化一个HttpConnector,然后调用它的start方法,启动一个线程
HttpConnector类的代码如下:
package ex03.pyrmont.connector.http; 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的processor方法: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(); } }
创建HttpRequest对象httpRequest类实现了javax.servlet.http.HttpServletRequest接口,类图如下:
HttpRequest的代码如下:
package ex03.pyrmont.connector.http; /** this class copies methods from org.apache.catalina.connector.HttpRequestBase * and org.apache.catalina.connector.http.HttpRequestImpl. * The HttpRequestImpl class employs a pool of HttpHeader objects for performance * These two classes will be explained in Chapter 4. */ 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 { } public String getLocalAddr() { // TODO Auto-generated method stub return null; } public String getLocalName() { // TODO Auto-generated method stub return null; } public int getLocalPort() { // TODO Auto-generated method stub return 0; } public int getRemotePort() { // TODO Auto-generated method stub return 0; } }
HttpRequest类包含如下子内容:
- 读取套接字的输入流
- 解析请求行
- 解析headers
- 解析cookies
- 获取请求参数