深入剖析Tomcat(How Tomcat works)读书笔记
参考资料:
[1]. 深入剖析Tomcat(How Tomcat works)书籍代码下载地址
注意:这一章的内容是基于前一章的内容。
- 第三章:连接器
StringManager类将错误消息存储在LocalStrings_xxx.properties文件中,xxx是各个国家的简写,每个包下面每个国家各有一个,随便找一个中文的来看下,如下面所示。
jasper.error.emptybodycontent.nonempty=根据 TLD,[{0}] 标签必须为空,但不是
jsp.error.action.isnottagfile=[{0}]行为只能用于标签文件
jsp.error.attempt_to_clear_flushed_buffer=错误:尝试清空已刷新的缓冲区
jsp.error.attribute.deferredmix=不能在同一属性值中同时使用 ${} 和 #{} EL 表达式
jsp.error.attribute.noequal=期望的符号是等号
jsp.error.attribute.noescape=属性值[{0}]引用[{1}],在值内使用时必须被转义。
jsp.error.attribute.nowhitespace=JSP 规范要求一个属性名字前有空格
jsp.error.bad.scratch.dir=你指定的 scratchDir:[{0}] 不可用。
jsp.error.bad_attribute=属性[{0}]无效为tag[{1}] 通过TLD
...
StringManager的用法
public class UpgradeServletOutputStream extends ServletOutputStream {
private static final StringManager sm =
StringManager.getManager(UpgradeServletOutputStream.class);
@Override
public final boolean isReady() {
if (listener == null) {
throw new IllegalStateException(
sm.getString("upgrade.sos.canWrite.ise"));
}
...
}
}
我们就来看看StringManager内部的实现,上面讲了StringManager每个包一个,上面虽然输入的参数是类,但是最后还是会得到它的包名再去获取对应的StringManager,接下来我们来看看getManager的真正实现。
输入的是包名和地区的名字,StringManger实际上放在Map中Map中,第一层Map是包名->Map,第二层Map是地区名->StringManager。removeEldestEntry
那段读下上面的英文即可知,是为了保证第二层Map的容量固定。最后如果找不到,那么根据包名和地区新创建一个StringManager。
/**
* Get the StringManager for a particular package and Locale. If a manager
* for a package/Locale combination already exists, it will be reused, else
* a new StringManager will be created and returned.
*
* @param packageName The package name
* @param locale The Locale
*
* @return The instance associated with the given package and Locale
*/
public static final synchronized StringManager getManager(
String packageName, Locale locale) {
Map<Locale,StringManager> map = managers.get(packageName);
if (map == null) {
/*
* Don't want the HashMap to be expanded beyond LOCALE_CACHE_SIZE.
* Expansion occurs when size() exceeds capacity. Therefore keep
* size at or below capacity.
* removeEldestEntry() executes after insertion therefore the test
* for removal needs to use one less than the maximum desired size
*
*/
map = new LinkedHashMap<Locale,StringManager>(LOCALE_CACHE_SIZE, 1, true) {
private static final long serialVersionUID = 1L;
@Override
protected boolean removeEldestEntry(
Map.Entry<Locale,StringManager> eldest) {
if (size() > (LOCALE_CACHE_SIZE - 1)) {
return true;
}
return false;
}
};
managers.put(packageName, map);
}
StringManager mgr = map.get(locale);
if (mgr == null) {
mgr = new StringManager(packageName, locale);
map.put(locale, mgr);
}
return mgr;
}
接下来我们来看看StringManager的创建,
/**
* Creates a new StringManager for a given package. This is a
* private method and all access to it is arbitrated by the
* static getManager method call so that only one StringManager
* per package will be created.
*
* @param packageName Name of package to create StringManager for.
*/
private StringManager(String packageName, Locale locale) {
String bundleName = packageName + ".LocalStrings";
ResourceBundle bnd = null;
try {
// The ROOT Locale uses English. If English is requested, force the
// use of the ROOT Locale else incorrect results may be obtained if
// the system default locale is not English and translations are
// available for the system default locale.
if (locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) {
locale = Locale.ROOT;
}
bnd = ResourceBundle.getBundle(bundleName, locale);
} catch (MissingResourceException ex) {
// Try from the current loader (that's the case for trusted apps)
// Should only be required if using a TC5 style classloader structure
// where common != shared != server
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl != null) {
try {
bnd = ResourceBundle.getBundle(bundleName, locale, cl);
} catch (MissingResourceException ex2) {
// Ignore
}
}
}
bundle = bnd;
// Get the actual locale, which may be different from the requested one
if (bundle != null) {
Locale bundleLocale = bundle.getLocale();
if (bundleLocale.equals(Locale.ROOT)) {
this.locale = Locale.ENGLISH;
} else {
this.locale = bundleLocale;
}
} else {
this.locale = null;
}
}
上一节中自己创建了Request,继承自RequestServlet,这节创建了HttpRequest,继承自HttpRequestServlet。上一节HttpServer负责等待http 请求,并创建 request 和 response 对象。本章中,等待 http 请求的工作由 HttpConnector 完成,创建 request 和 response 对象的工作由 HttpProcessor 完成,HttpProcessor解析请求行和请求头的信息,HttpConnector在用户需要的时候解析请求体或者查询字符串中的参数。
HttpRequest包含很多请求头的很多信息,但是他们不一定会被用到,所以用到的时候才会进行加载。
函数从HttpConnector开始,HttpConnector创建一个新的线程,然后建立ServerSocket等待连接,之后交给HttpProcessor处理。
Bootstrap.java
public final class Bootstrap {
public static void main(String[] args) {
HttpConnector connector = new HttpConnector();
connector.start();
}
}
HttpConnector.java
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.proccess创建HttpRequest,HttpResponse,然后解析请求头信息,分派到Servlet。
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();
}
}
parseRequest(input, output);
的代码如下所示。input.readRequestLine(requestLine);
是将请求行的请求方法,URI,协议都给提取出来,放在HttpRequestLine类中。input的类型是SocketInputStream,它代理了InputStream类型,增加了简单解析请求行readRequestLine和简单解析请求头readHeader两个方法,然后HttpProcessor再此基础上分别在parseRequest和parseHeaders方法进一步进行信息的提取和规整化等等。下面parseRequest再得到请求行的各个信息后,再分别进行检验是否为空,对URI的处理比较浓墨重彩,因为URI可以携带很多信息,包括请求参数,jsessionid,绝对路径相对路径的处理,错误路径的矫正等等。
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 + "'");
}
}
下面我们仔细看下SocketInputStream.readRequestLine
,它将请求行的信息放到了它的参数HttpRequestLine类型变量中,下面截取它读取请求行的请求方法,在这之前已经过滤掉一些特殊的字符的干扰,接下来不断地读取输入直到遇到空格,HttpRequestLine给请求方法字段预留了空间,如果不够,需要进行翻倍拓展。请求行其他的信息提取也是类似的。
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;
在自定义的HttpRequest中会用ParameterMap类型变量存放着请求的参数,请求的参数可能存在URI中,也可能存在请求体中,这里只有用到的时候才会用parseParameters进行加载,且只加载一次。ParameterMap继承自HashMap,增加了一个lock变量,但是貌似起不到同步的作用??
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;
}
HttpResponse.getWriter反映了写出时的流程,ResponseWriter(实际上是PrintWriter,将字符串转化为字节流)->OutputStreamWriter(编码)->ResponseStream...写出到socket
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;
}