How Tomcat Works - Connector
在前两章实现的WebServer还有很多问题,比如:
1)最后一个out.print("xxx")没有生效。
2)没有解析请求头,请求方法,协议,uri,参数等,而这些内容在servlet里面是需要用到的。
在这一章中,增加了一个新的servlet:ModerServlet:
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ModernServlet extends HttpServlet {
public void init(ServletConfig config) {
System.out.println("ModernServlet -- init");
}
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>Modern Servlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h2>Headers</h2");
Enumeration headers = request.getHeaderNames();
while (headers.hasMoreElements()) {
String header = (String) headers.nextElement();
out.println("<br>" + header + " : " + request.getHeader(header));
}
out.println("<br><h2>Method</h2");
out.println("<br>" + request.getMethod());
out.println("<br><h2>Parameters</h2");
Enumeration parameters = request.getParameterNames();
while (parameters.hasMoreElements()) {
String parameter = (String) parameters.nextElement();
out.println("<br>" + parameter + " : " + request.getParameter(parameter));
}
out.println("<br><h2>Query String</h2");
out.println("<br>" + request.getQueryString());
out.println("<br><h2>Request URI</h2");
out.println("<br>" + request.getRequestURI());
out.println("</body>");
out.println("</html>");
}
}
这个Servlet和之前两章的PrimitiveServlet不同的是,这个Servlet是继承了HttpServlet而不是实现了Servlet接口。HttpServlet又继承了GenericServlet,实现了Servlet, ServletConfig这两个接口。所以ModerServlet也实现了Servlet接口,而且除此之外还复用了基类的功能。
WebServer的启动和业务逻辑分成了三个类:Bootstrap,HttpProcessor和HttpConnector。HttpConnector实现了Runnable接口,这样就可以把自身的实例通过线程去去执行:
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 start() {
Thread thread = new Thread(this);
thread.start();
}
@Override
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) {
Socket socket = null;
try {
socket = serverSocket.accept();
} catch (IOException e) {
continue;
}
HttpProcessor processor = new HttpProcessor(this);
processor.process(socket);
}
}
}
这部分逻辑和前两章类似,除了新增了一个HttpProcessor,这个类是处理http请求的关键部分。首先来看process函数:
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();
}
}
和HttpServer不一样的是:
1)input类型又InputStream变成了SocketInputStream,这个类tomcat4里面对InputStream的封装类,在现在的tomcat源代码里面这个类已经被移除了。这里直接把这个类的源代码和它依赖的HttpRequestLine和HttpHeader拷贝进来了。同时SocketInputStream这个类还使用了org.apache.catalina.util.RequestUtil 和org.apache.catalina.util.StringManager这两个不包含在源代码里的类,可以去这里下载http://archive.apache.org/dist/tomcat/tomcat-4/v4.1.40/bin/ tomcat的zip版本,在server/lib下找到catalina.jar并加到classpath里面。
2)增加了解析request和header的逻辑:
response.setHeader("Server", "Pyrmont Servlet Container");
parseRequest(input, output);
parseHeaders(input)
parseRequest就是解析Http请求的第一部分,比如:
GET /myApp/ModernServlet?userName=tarzan&password=pwd HTTP/1.1
然后把解析到的结果保存到HttpRequest对象中:
private void parseRequest(SocketInputStream input, OutputStream output)
throws IOException, ServletException {
// Parse the incoming request line
input.readRequestLine(requestLine);
// http请求方法,这里是GET
String method =
new String(requestLine.method, 0, requestLine.methodEnd);
String uri = null;
// http请求协议,这里是http/1.1
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");
}
// 解析问号后面的部分,比如/myApp/ModernServlet?userName=tarzan&password=pwd 解析之后uri成了/myApp/ModernServlet
// ,userName=tarzan&password=pwd保存在HttpRequest的queryString中
// 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);
}
// 如果uri是一个绝对路径,比如http://baidu.com/index.html?abc=xyz则把http://baidu.com这一部分去掉
// 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);
}
}
}
// 从uri里解析jsessionid,如果有的话
// 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);
}
// 规范化uri的内容
// 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 + "'");
}
}
parseHeaders通过一个while循环,每次通过SocketInputStream读入一个header项:
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
}
如果header项的是cookie,使用RequestUtil.parseCookieHeader去解析cookie的值,如果cookie的值中包含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.isRequestedSessionIdFromCookie()返回false的时候才保存jsessionid的值到sessionid,但是什么时候会返回false,我们可以看下这个接口的声明:
/**
*
* Checks whether the requested session ID came in as a cookie.
*
* @return <code>true</code> if the session ID
* came in as a
* cookie; otherwise, <code>false</code>
*
*
* @see #getSession
*
*/
public boolean isRequestedSessionIdFromCookie();
看起来是说sessionid是否已经从cookie中拿到了,再配合评论// Accept only the first session id cookie 所以我们知道就是一个标记位,在request.setRequestedSessionCookie(true);就是设成了true就是说只接受第一个sessionid。我们也可以去看下tommcat的源码:
/**
* Return <code>true</code> if the session identifier included in this
* request came from a cookie.
*/
@Override
public boolean isRequestedSessionIdFromCookie() {
if (requestedSessionId == null) {
return false;
}
return requestedSessionCookie;
}
然后我们可以看到请求参数的解析是在HttpRequest的parseParameters里面做的:
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;
}
最关键的部分就是通过RequestUtil.parseParameters 解析queryString并把结果保存到results。而且这个函数通过parsed这个变量来保证只会被执行一次。
最后我们访问http://localhost:8080/servlet/PrimitiveServlet,发现浏览器打印了:
Hello, Roses are red Violets are blue
说明上一章的那个最后的out.print("xxx")没有生效的问题解决了。但是访问http://localhost:8080/servlet/ModernServlet?abc=xyz,因为第一章说的问题,火狐浏览器打印了:
<html> <head> <title>Modern Servlet</title> </head> <body> <h2>Headers</h2 <br>accept-language : en-US,en;q=0.5 <br>host : localhost:8080 <br>upgrade-insecure-requests : 1 <br>connection : keep-alive <br>accept-encoding : gzip, deflate <br>user-agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:52.0) Gecko/20100101 Firefox/52.0 <br>accept : text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 <br><h2>Method</h2 <br>GET <br><h2>Parameters</h2 <br>abc : xyz <br><h2>Query String</h2 <br>abc=xyz <br><h2>Request URI</h2 <br>/servlet/ModernServlet </body> </html>
如果需要达到想要的效果,可以通过IE浏览器访问。

浙公网安备 33010602011771号