Catalina有两个主要的模块 Connector 和 Container,在本章的程序中所要建立的连接器是 Tomcat 4 中的默认连接器的简化版
3.1 StringManage
Tomcat 将存储错误消息的 properties 文件划分到不同的包中,每个 properties 文件都是使用 org.apache.catalina.util.StringManager 类的一个实例进行处理。同时 StringManager 是单例模式的,避免空间的浪费。
3.2 应用程序
本章的应用程序包含三个模块:
- 启动模块:负责启动应用程序。
- 连接器模块:
- 连接器及其支持类(HttpConnector 和 HttpProcessor);
- HttpRequest 类和 HttpResponse 类及其支持类;
- 外观类(HttpRequesFaced 类和 HttpResponseFaced);
- 常量类。
- 核心模块:
- ServletProcessor : 处理 servlet 请求(类似 Wrapper);
- StaticResourceProcessor : 处理静态资源请求。
3.2.1 启动应用程序
public final class Bootstrap {
public static void main(String[] args) {
//创建 HttpConnector 实例
HttpConnector connector = new HttpConnector();
//启动 Connector
connector.start();
}
}
3.2.2 HttpConnector 类
Connector 不知道 servlet 接受 Request 和 Response 的具体类型,所以使用 HttpServletRequest 和 HttpResponse 进行传参。同时解析 HTTP 请求对参数是懒加载的,在这些参数被 servlet 实例真正调用前是不会进行解析的。
Tomcat 的默认连接器和本章程序的 Connector 都使用 SocketInputStream 获取字节流。SocketInputStream 是 InputStream 的包装类,提供了两个重要的方法:
- readRequestLine:获取请求行,包括 URI、请求方法和 HTTP 版本信息。
- readHead: 获取请求头,每次调用 readHead 方法都会返回一个键值对,可以通过遍历读取所有的请求头信息。
public class HttpConnector implements Runnable {
boolean stopped;
private String scheme = "http";
public void start() {
//启动一个新的线程,调用自身的 run 方法
Thread thread = new Thread(this);
thread.start();
}
public void run() {
//为减少文章篇幅省略 try catch 语句块
//创建 ServerSocket
ServerSocket serverSocket = null;
int port = 8080;
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
while (!stopped) {
// 等待连接请求
Socket socket = null;
socket = serverSocket.accept();
// 为当前请求创建一个 HttpProcessor
HttpProcessor processor = new HttpProcessor(this);
//调用 process 方法处理请求
processor.process(socket);
}
}
HttpProcessor 类
HttpProcessor 的 process 方法对每个传入的 HTTP 请求,要完成4个操作:
- 创建一个 HttpRequest 对象;
- 创建一个 HttpResponse 对象;
- 解析 HTTP 请求的第一行内容和请求头信息,填充 HttpRequest 对象;
- 将 HttpRequest 和 HttpResponse 传递给 ServletProcessor 或 Static-ResourceProcessor 的 process 方法。
public void process(Socket socket) {
SocketInputStream input= new SocketInputStream(socket.getInputStream(), 2048);
OutputStream output = socket.getOutputStream();
//创建 HttpRequest 和 HttpResponse 对象
request = new HttpRequest(input);
response = new HttpResponse(output);
response.setRequest(request);
//向客户端发送响应头信息
response.setHeader("Server", "Pyrmont Servlet Container");
//解析请求行
parseRequest(input, output);
//解析请求头
parseHeaders(input);
//根据请求的 URI 模式判断处理请求的类
if (request.getRequestURI().startsWith("/servlet/")) {
ServletProcessor processor = new ServletProcessor();
processor.process(request, response);
}
else {
StaticResourceProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}
}
parseRequest 方法解析请求行:
处理 URI 字符串 --> 绝对路径检查 --> 会话标识符检查 --> URI 修正 --> 设置 request 属性
private void parseRequest(SocketInputStream input, OutputStream output) {
// requestLine 是 HttpRequestLine 的实例
//使用 SocketInputStream 中的信息填充 requestLine,并从 requestLine 中获取请求行信息
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
//验证输入的 request line 是否合法, 略
...
//处理 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);
}
//检查是否是绝对路径
if (!uri.startsWith("/")) {
int pos = uri.indexOf("://");
// 获取 protocol 的类型
if (pos != -1) {
pos = uri.indexOf('/', pos + 3);
if (pos == -1) {
uri = "";
}
else {
uri = uri.substring(pos);
}
}
}
// 检查查询字符串是否还携带会话标识符
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);
}
//对 URI 进行修正转化为合法的 URI,如将 "\" 替换成 "/"
String normalizedUri = normalize(uri);
// 属性 request 的属性
((HttpRequest) request).setMethod(method);
request.setProtocol(protocol);
if (normalizedUri != null) {
((HttpRequest) request).setRequestURI(normalizedUri);
}
else {
((HttpRequest) request).setRequestURI(uri);
}
}
parseHeaders 方法解析请求头:
获取下一个 header --> 如果 header name 和 value 为空则退出循环 --> 从 header 中获取 key/value 并放入 request --> 对 cookie 和 content-type 做处理 --> 循环
private void parseHeaders(SocketInputStream input) {
while (true) {
HttpHeader header = new HttpHeader();
// 获取下一个 header
input.readHeader(header);
if (header.nameEnd == 0) {
if (header.valueEnd == 0) {
return;
}
else {
throw new ServletException ();
}
}
// 从 header 中获取请求头的 key/value,并存入 request 中
String name = new String(header.name, 0, header.nameEnd);
String value = new String(header.value, 0, header.valueEnd);
request.addHeader(name, value);
// 对一些特别的 header 做处理
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;
n = Integer.parseInt(value);
request.setContentLength(n);
}
else if (name.equals("content-type")) {
request.setContentType(value);
}
}
}
获取参数:
- 在获取参数前会调用 HttpRequest 的 parseParameter 方法解析请求参数。参数只需解析一次也只会解析一次。
- 参数存储在特殊的 hashMap 中: ParameterMap
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;
}
4
4
4
4
4
4
4
4
4
4
4