《深入剖析Tomcat》阅读(三)
这里要介绍下Tomcat的一个重要设计方法,Catalina设计方式。
Servlet容器是一个复杂系统,但是,它有三个基本任务,对每个请求,servlet容器会为其完成以下三个操作:
1.创建一个Request对象,用可能会在调用的Servlet中使用到的信息填充该request对象,如参数、头、cookie、查询字符串、URI等。
request对象是javax.servlet.ServletRequest接口或javax.servlet.http.ServletRequest接口的一个实例。
2.创建一个调用Servlet的response对象,用来向Web客户端发送响应。response对象是javax.servlet.ServletResponse接口或javax.servlet.http.ServletResponse接口的一个实例。
3.调用Servlet的service()方法,将request对象和response对象作为参数传入。Servlet从request对象中读取信息,并通过response对象发送响应信息。
-----------------------------------------------------------------------------------------------------
Catalina是一个成熟的软件,设计和开发得十分优雅。(其实我在公司项目中发现的使用方法也是这样的,十分的优雅的软件开发方式)
Catalina使得软件开发功能结构编程模块化的,基于上文提到的servlet容器的任务,可以将Catalina划分为两个模块:连接器(connector)和容器(container)
连接器负责将一个请求与容器相关联。它的工作包括为它接收到的每一个HTTP请求创建一个Request对象和一个Response对象。然后,它将处理过程交给容器。容器从连接器中接收到Request对象和Response对象,并负责调用相应的Servlet的service()方法。
简而言这模式就是connector负责接收请求,创建解析请求需要的Request对象和Response对象,然后将请求分发到容器中处理。
(普通的服务端结构一般也是这么设计的,IO部分负责接收请求,创建解析请求的对象,然后将请求丢进线程池中进行处理)
xxxx
--------------------------------------------
现在根据模块开发Servlet容器:
1)连接器 HttpConnector和HttpProcessor 连接器及其支持类 标示HTTP请求的类(HTTPRequest)及其支持类 负责HTTP响应的类,HttpResponse以及其支持类,外观类以及常量类
2)启动模块 启动模块包括一个类,就是startup.Bootstrap 类,负责启动应用程序。
3)核心模块
启动类
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(); } }
关于这个建议具有连接器和容器功能的Http服务器
http://www.cnblogs.com/wuxinliulei/p/4967625.html
---------------------------------------------------------------------------
重点介绍这个模型当中对HTTP请求信息的parse
HttpConnector类在接收到连接请求后,将请求派发给一个HttpProcessor对象处理
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(); } }
将此次连接获取的Socket对象传入,调用HttpProcessor对象的process方法来处理Http请求
下面是HttpProcessor对象的process方法,重点关注SocketInputStream对象,SocketInputStream对象是对InputStream对象的一层包装,封装了一些对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(); } }
process方法当中调用了parseRequest方法,该方法传入SocketInputStream对象和OutputStream对象,方法对输入流当中的http请求进行了解析,
这里牵涉到几个帮助类
1:HttpRequestLine类,定义在HttpProcessor方法的成员变量当中。private HttpRequestLine requestLine = new HttpRequestLine();
该类中对http请求内容中的请求行(http请求依次分为请求行、请求头、/r/n,请求体四部分),第一行内容进行了解析 即
POST /servlet/primitServlet Http/1.1 GET /sample/hello.jsp HTTP/1.1 这样的内容
HttpRequestLine类是一个可以复用的类(PS:但是HttpProcessor在这个框架当中没有对象池的复用,所以没有卵用),对httpMethod httpUrl protocol 三部分做了处理。其中还提供了字符串匹配的方法indexOf(char[] xxx) indexOf(String xxx)
/** * HTTP request line enum type. */ final class HttpRequestLine { // -------------------------------------------------------------- Constants public static final int INITIAL_METHOD_SIZE = 8; public static final int INITIAL_URI_SIZE = 64; public static final int INITIAL_PROTOCOL_SIZE = 8; public static final int MAX_METHOD_SIZE = 1024; public static final int MAX_URI_SIZE = 32768; public static final int MAX_PROTOCOL_SIZE = 1024; // ----------------------------------------------------------- Constructors public HttpRequestLine() { this(new char[INITIAL_METHOD_SIZE], 0, new char[INITIAL_URI_SIZE], 0, new char[INITIAL_PROTOCOL_SIZE], 0); } public HttpRequestLine(char[] method, int methodEnd, char[] uri, int uriEnd, char[] protocol, int protocolEnd) { this.method = method; this.methodEnd = methodEnd; this.uri = uri; this.uriEnd = uriEnd; this.protocol = protocol; this.protocolEnd = protocolEnd; } // ----------------------------------------------------- Instance Variables public char[] method; public int methodEnd; public char[] uri; public int uriEnd; public char[] protocol; public int protocolEnd; // ------------------------------------------------------------- Properties // --------------------------------------------------------- Public Methods /** * Release all object references, and initialize instance variables, in * preparation for reuse of this object. */ public void recycle() { methodEnd = 0; uriEnd = 0; protocolEnd = 0; } /** * Test if the uri includes the given char array. */ public int indexOf(char[] buf) { return indexOf(buf, buf.length); } /** * Test if the value of the header includes the given char array. */ public int indexOf(char[] buf, int end) { char firstChar = buf[0]; int pos = 0; while (pos < uriEnd) { pos = indexOf(firstChar, pos); if (pos == -1) return -1; if ((uriEnd - pos) < end) return -1; for (int i = 0; i < end; i++) { if (uri[i + pos] != buf[i]) break; if (i == (end - 1)) return pos; } pos++; } return -1; } /** * Test if the value of the header includes the given string. */ public int indexOf(String str) { return indexOf(str.toCharArray(), str.length()); } /** * Returns the index of a character in the value. */ public int indexOf(char c, int start) { for (int i = start; i < uriEnd; i++) { if (uri[i] == c) return i; } return -1; } // --------------------------------------------------------- Object Methods public int hashCode() { // FIXME return 0; } public boolean equals(Object obj) { return false; } }
我们要注意到parseRequest方法的第一行代码 input.readRequestLine(requestLine) SocketInputStream的该方法完成了对HttpRequestLine对象的初始化,主要初始化了六个对象
public char[] method; //method对象
public int methodEnd; //method对象的结束位置
public char[] uri; //uri对象
public int uriEnd; //uri对象的结束位置
public char[] protocol; //协议对象
public int protocolEnd;//协议对象的结束位置
注意read方法完成了对count的初始化,并且将第一行内容缓存到char[] buf当中
/** * Read byte. */ public int read() throws IOException { if (pos >= count) { fill(); if (pos >= count) return -1; } return buf[pos++] & 0xff; }
/** * Fill the internal buffer using data from the undelying input stream. */ protected void fill() throws IOException { pos = 0; count = 0; int nRead = is.read(buf, 0, buf.length); if (nRead > 0) { count = nRead; } }
public void readRequestLine(HttpRequestLine requestLine) throws IOException { // Recycling check if (requestLine.methodEnd != 0) requestLine.recycle(); // Checking for a blank line int chr = 0; do { // Skipping CR or LF try { chr = read(); } catch (IOException e) { chr = -1; } } while ((chr == CR) || (chr == LF)); if (chr == -1) throw new EOFException(sm.getString("requestStream.readline.error")); pos--; // Reading the method name int maxRead = requestLine.method.length; int readStart = pos; int readCount = 0; boolean space = false; while (!space) { // if the buffer is full, extend it if (readCount >= maxRead) { if ((2 * maxRead) <= HttpRequestLine.MAX_METHOD_SIZE) { char[] newBuffer = new char[2 * maxRead]; System.arraycopy(requestLine.method, 0, newBuffer, 0, maxRead); requestLine.method = newBuffer; maxRead = requestLine.method.length; } else { throw new IOException(sm.getString("requestStream.readline.toolong")); } } // We're at the end of the internal buffer if (pos >= count) { int val = read(); if (val == -1) { throw new IOException(sm.getString("requestStream.readline.error")); } pos = 0; readStart = 0; } if (buf[pos] == SP) { space = true; } requestLine.method[readCount] = (char) buf[pos]; readCount++; pos++; } requestLine.methodEnd = readCount - 1; // Reading URI maxRead = requestLine.uri.length; readStart = pos; readCount = 0; space = false; boolean eol = false; while (!space) { // if the buffer is full, extend it if (readCount >= maxRead) { if ((2 * maxRead) <= HttpRequestLine.MAX_URI_SIZE) { char[] newBuffer = new char[2 * maxRead]; System.arraycopy(requestLine.uri, 0, newBuffer, 0, maxRead); requestLine.uri = newBuffer; maxRead = requestLine.uri.length; } else { throw new IOException(sm.getString("requestStream.readline.toolong")); } } // We're at the end of the internal buffer if (pos >= count) { int val = read(); if (val == -1) throw new IOException(sm.getString("requestStream.readline.error")); pos = 0; readStart = 0; } if (buf[pos] == SP) { space = true; } else if ((buf[pos] == CR) || (buf[pos] == LF)) { // HTTP/0.9 style request eol = true; space = true; } requestLine.uri[readCount] = (char) buf[pos]; readCount++; pos++; } //请求航URL结束 requestLine.uriEnd = readCount - 1; // Reading protocol maxRead = requestLine.protocol.length; readStart = pos; readCount = 0; while (!eol) { // if the buffer is full, extend it if (readCount >= maxRead) { if ((2 * maxRead) <= HttpRequestLine.MAX_PROTOCOL_SIZE) { char[] newBuffer = new char[2 * maxRead]; System.arraycopy(requestLine.protocol, 0, newBuffer, 0, maxRead); requestLine.protocol = newBuffer; maxRead = requestLine.protocol.length; } else { throw new IOException(sm.getString("requestStream.readline.toolong")); } } // We're at the end of the internal buffer if (pos >= count) { // Copying part (or all) of the internal buffer to the line // buffer int val = read(); if (val == -1) throw new IOException(sm.getString("requestStream.readline.error")); pos = 0; readStart = 0; } if (buf[pos] == CR) { // Skip CR. } else if (buf[pos] == LF) { eol = true; } else { requestLine.protocol[readCount] = (char) buf[pos]; readCount++; } pos++; } // HTTP/1.1 requestLine.protocolEnd = readCount; }
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 + "'"); } }
----------------------------------------------------------------------------------------------------------------------------------------
为什么采用连接器的第三章的代码没有采用第二章的简单形式直接将相关内容解析出来呢?
private String parseUri(String requestString) { int index1, index2; index1 = requestString.indexOf(' '); if (index1 != -1) { index2 = requestString.indexOf(' ', index1 + 1); if (index2 > index1) return requestString.substring(index1 + 1, index2); } return null; }
public void parse() { // Read a set of characters from the socket StringBuffer request = new StringBuffer(2048); int i; byte[] buffer = new byte[2048]; try { i = input.read(buffer); } catch (IOException e) { e.printStackTrace(); i = -1; } for (int j = 0; j < i; j++) { request.append((char) buffer[j]); } System.out.print(request.toString()); uri = parseUri(request.toString()); }
其实上面的处理方式是不严谨的,应该读到/r/n之后停止,而不是固定读取2048字节,然后处理这读出来的字节。