Tomcat源码分析 -- 1
Tomcat源码分析 -- 1
sschrodinger
2018/12/10
资源
- 《深入剖析 Tomcat》 - 基于Tomcat 4.x
- Tomcat 8.5.x 源码
HTTP 协议
HTTP 协议建立在 TCP 连接之上,提供安全可靠的数据传输方案。
HTTP 请求
HTTP 请求由三部分组成,分别是请求行,请求头和实体,具体格式如下:
method URI protocol/version
Accept:text/plain;text/html
Accept-language:en-gb
Conection:Keep-Alive
Host:localHost
Content-length:33
lastName=Franks&firstName=Michael
note
- HTTP 协议的所有换行符都为/r/n,即标准的 linux 换行符
HTTP 请求的第一行规定了传输方法,URI和协议/版本,称为请求行,一般有如下的形式:
POST /example/default.jsp HTTP/1.1
接下来到实体之前规定了请求头,表明了这个 HTTP 请求的属性,最后是实体,表明了传送的内容。
note
- 在实体和请求头之间一定有一个换行符,以表明请求头的结束。
HTTP 响应
HTTP 响应也同样包括了三个部分,分别是响应行,响应头和响应实体。具体格式如下:
protocol/version statusCode description
Date:Mon, 5 Jan 2004 13:13:33 GMT
content-length:112
<html>
<head>
<title>HTTP response example</title>
</head>
<body>
Welcome to my world
</body>
</html>
HTTP 响应的第一行规定了协议/版本和状态,称为响应行,一般有如下的形式:
HTTP/1.1 200 OK
note
- 类似的,在响应实体和响应头之间一定有一个换行符,以表明响应头的结束。
servlet 编程用户接口(servlet 框架)
在 servlet 编程中,用户主要会使用三个接口来编写 web 应用程序。回想一下,我们在 web 编程时,主要是实现一个 Servlet 的接口,接口定义如下:
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public interface Servlet {
@Override
public void destroy();
@Override
public ServletConfig getServletConfig();
@Override
public String getServletInfo();
@Override
public void init(ServletConfig arg0) throws ServletException;
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException;
}
此接口一共提供了五个函数需要我们实现。
note
- init 函数只会调用一次,这个函数可以作为数据库等的初始化函数。
- service 函数必须在init 函数调用之后调用,这个是我们改写的主要函数,实现了具体的功能,包括来了一个请求之后如何返回一个响应。
- destroy 函数调用之后,就不能调用 service 函数了,规定如何销毁一个 servlet。
在实现中,有辅助类 ServletRequest 和 ServletResponse,ServletRequest 提供了大量的 get 方法,以此获得 request 的属性,ServletResponse 提供了大量的 set 方法,用来设置 response 的属性。
可以直接使用实现了 Servlet 接口的类 (HttpServlet) 来完成 web 服务器的编写。定义如下:
package javax.servlet.http;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.servlet.*;
public abstract class HttpServlet extends GenericServlet
{
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String LSTRING_FILE =
"javax.servlet.http.LocalStrings";
//ResourceBundle处理国际化语言
private static ResourceBundle lStrings =
ResourceBundle.getBundle(LSTRING_FILE);
public HttpServlet() { }
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
} else {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
}
}
protected long getLastModified(HttpServletRequest req) {
return -1;
}
protected void doHead(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
//NoBodyResponse 专门为 doHead 服务,可以不管
NoBodyResponse response = new NoBodyResponse(resp);
doGet(req, response);
response.setContentLength();
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
// similar with doGet
}
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
// similar with doGet
}
protected void doDelete(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException
{
// similar with doGet
}
private Method[] getAllDeclaredMethods(Class<? extends HttpServlet> c) {
Class<?> clazz = c;
Method[] allMethods = null;
while (!clazz.equals(HttpServlet.class)) {
Method[] thisMethods = clazz.getDeclaredMethods();
if (allMethods != null && allMethods.length > 0) {
Method[] subClassMethods = allMethods;
allMethods =
new Method[thisMethods.length + subClassMethods.length];
System.arraycopy(thisMethods, 0, allMethods, 0,
thisMethods.length);
System.arraycopy(subClassMethods, 0, allMethods, thisMethods.length,
subClassMethods.length);
} else {
allMethods = thisMethods;
}
clazz = clazz.getSuperclass();
}
return ((allMethods != null) ? allMethods : new Method[0]);
}
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
Method[] methods = getAllDeclaredMethods(this.getClass());
boolean ALLOW_GET = false;
boolean ALLOW_HEAD = false;
boolean ALLOW_POST = false;
boolean ALLOW_PUT = false;
boolean ALLOW_DELETE = false;
boolean ALLOW_TRACE = true;
boolean ALLOW_OPTIONS = true;
for (int i=0; i<methods.length; i++) {
String methodName = methods[i].getName();
if (methodName.equals("doGet")) {
ALLOW_GET = true;
ALLOW_HEAD = true;
} else if (methodName.equals("doPost")) {
ALLOW_POST = true;
} else if (methodName.equals("doPut")) {
ALLOW_PUT = true;
} else if (methodName.equals("doDelete")) {
ALLOW_DELETE = true;
}
}
// we know "allow" is not null as ALLOW_OPTIONS = true
// when this method is invoked
StringBuilder allow = new StringBuilder();
if (ALLOW_GET) {
allow.append(METHOD_GET);
}
if (ALLOW_HEAD) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_HEAD);
}
if (ALLOW_POST) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_POST);
}
if (ALLOW_PUT) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_PUT);
}
if (ALLOW_DELETE) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_DELETE);
}
if (ALLOW_TRACE) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_TRACE);
}
if (ALLOW_OPTIONS) {
if (allow.length() > 0) {
allow.append(", ");
}
allow.append(METHOD_OPTIONS);
}
resp.setHeader("Allow", allow.toString());
}
protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
int responseLength;
String CRLF = "\r\n";
StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI())
.append(" ").append(req.getProtocol());
Enumeration<String> reqHeaderEnum = req.getHeaderNames();
while( reqHeaderEnum.hasMoreElements() ) {
String headerName = reqHeaderEnum.nextElement();
buffer.append(CRLF).append(headerName).append(": ")
.append(req.getHeader(headerName));
}
buffer.append(CRLF);
responseLength = buffer.length();
resp.setContentType("message/http");
resp.setContentLength(responseLength);
ServletOutputStream out = resp.getOutputStream();
out.print(buffer.toString());
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
private void maybeSetLastModified(HttpServletResponse resp,
long lastModified) {
if (resp.containsHeader(HEADER_LASTMOD))
return;
if (lastModified >= 0)
resp.setDateHeader(HEADER_LASTMOD, lastModified);
}
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException
{
HttpServletRequest request;
HttpServletResponse response;
if (!(req instanceof HttpServletRequest &&
res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
service(request, response);
}
}
在 HTTP/1.1 协议中,HttpServlet 实现了 新版的全部七个 Http 请求方法,包括 get、post、head、put、delete、options、trace。
note
- 在 HttpServlet 的实现中,使用了继承自 ServletResponse/ServletRequest 的 HttpServletResponse/HttpServletRequest,增加了 getMethod 等获得 Http 请求方法等属性的方法。
- 实现了 Servlet 接口的函数为
public void service(ServletRequest req, ServletResponse res)
,这个函数只是检查 request 和 response 参数是不是 Http* 类型,最终的操作是交给另一个同名函数protected void service(HttpServletRequest req, HttpServletResponse resp)
来做。protected void service(HttpServletRequest req, HttpServletResponse resp)
根据 http 请求的方法不同,将不同的 http 请求分发给不同的 doFunction 来做。所以在实际中是需要重写需要的 doFunction 方法,比如说重写 doGet 以接收并处理使用 get 方式传来的数据。
一个最简单的 hello world 服务器端如下所示:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class HelloWorld extends HttpServlet {
private String message;
public void init() throws ServletException
{
message = "Hello World";
}
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<h1>" + message + "</h1>");
}
public void destroy(){}
}
服务器端处理流程
如下是一个极简的服务器处理框架:
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class HttpServer {
public static void main(String[] args) {
HttpServer httpServer = new HttpServer();
httpServer.await();
}
public void await() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(Constants.PORT, 1, InetAddress.getByName("127.0.0.1"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.exit(1);
}
while (true) {
try (Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
Request request = new Request(inputStream);
Response response = new Response(outputStream);
if (request.getUri().startsWith("/servlet/")) {
//如果请求的url为servlet程序
//动态加载 servlet
Class clazz = Class.forName("xxx");
Servlet servlet = (HttpServlet)clazz.newInstance();
//处理 clazz 的service 方法
servlet.service(request, response);
} else {
//静态页面,直接发送
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
}
Java 的反射机制很好的将用户接口和服务器连接起来。
总结服务器的工作流程如下:
graph LR
创建ServerSocket-->监听ServerSocket
监听ServerSocket-->当有新连接时创建Socket
当有新连接时创建Socket-->填充Request和Response
填充Request和Response-->如果是servlet则动态加载servlet处理