Tomcat源碼分析 -- 1
sschrodinger
2018/12/10
資源
- 《深入剖析 Tomcat》 - 基于Tomcat 4.x
- Tomcat 8.5.x 源碼
HTTP 協(xié)議
HTTP 協(xié)議建立在 TCP 連接之上指巡,提供安全可靠的數(shù)據(jù)傳輸方案凯力。
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 協(xié)議的所有換行符都為/r/n,即標準的 linux 換行符
HTTP 請求的第一行規(guī)定了傳輸方法,URI和協(xié)議/版本,稱為請求行哼鬓,一般有如下的形式:
POST /example/default.jsp HTTP/1.1
接下來到實體之前規(guī)定了請求頭,表明了這個 HTTP 請求的屬性肥橙,最后是實體魄宏,表明了傳送的內(nèi)容。
note
- 在實體和請求頭之間一定有一個換行符存筏,以表明請求頭的結(jié)束宠互。
HTTP 響應(yīng)
HTTP 響應(yīng)也同樣包括了三個部分,分別是響應(yīng)行椭坚,響應(yīng)頭和響應(yīng)實體予跌。具體格式如下:
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 響應(yīng)的第一行規(guī)定了協(xié)議/版本和狀態(tài),稱為響應(yīng)行善茎,一般有如下的形式:
HTTP/1.1 200 OK
note
- 類似的券册,在響應(yīng)實體和響應(yīng)頭之間一定有一個換行符,以表明響應(yīng)頭的結(jié)束垂涯。
servlet 編程用戶接口(servlet 框架)
在 servlet 編程中烁焙,用戶主要會使用三個接口來編寫 web 應(yīng)用程序「福回想一下骄蝇,我們在 web 編程時,主要是實現(xiàn)一個 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;
}
此接口一共提供了五個函數(shù)需要我們實現(xiàn)九火。
note
- init 函數(shù)只會調(diào)用一次,這個函數(shù)可以作為數(shù)據(jù)庫等的初始化函數(shù)册招。
- service 函數(shù)必須在init 函數(shù)調(diào)用之后調(diào)用岔激,這個是我們改寫的主要函數(shù),實現(xiàn)了具體的功能是掰,包括來了一個請求之后如何返回一個響應(yīng)虑鼎。
- destroy 函數(shù)調(diào)用之后,就不能調(diào)用 service 函數(shù)了键痛,規(guī)定如何銷毀一個 servlet炫彩。
在實現(xiàn)中,有輔助類 ServletRequest 和 ServletResponse散休,ServletRequest 提供了大量的 get 方法媒楼,以此獲得 request 的屬性乐尊,ServletResponse 提供了大量的 set 方法戚丸,用來設(shè)置 response 的屬性。
可以直接使用實現(xiàn)了 Servlet 接口的類 (HttpServlet) 來完成 web 服務(wù)器的編寫。定義如下:
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 服務(wù)限府,可以不管
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 協(xié)議中夺颤,HttpServlet 實現(xiàn)了 新版的全部七個 Http 請求方法,包括 get胁勺、post世澜、head、put署穗、delete寥裂、options、trace案疲。
note
- 在 HttpServlet 的實現(xiàn)中封恰,使用了繼承自 ServletResponse/ServletRequest 的 HttpServletResponse/HttpServletRequest,增加了 getMethod 等獲得 Http 請求方法等屬性的方法褐啡。
- 實現(xiàn)了 Servlet 接口的函數(shù)為
public void service(ServletRequest req, ServletResponse res)
诺舔,這個函數(shù)只是檢查 request 和 response 參數(shù)是不是 Http* 類型,最終的操作是交給另一個同名函數(shù)protected void service(HttpServletRequest req, HttpServletResponse resp)
來做备畦。protected void service(HttpServletRequest req, HttpServletResponse resp)
根據(jù) http 請求的方法不同低飒,將不同的 http 請求分發(fā)給不同的 doFunction 來做。所以在實際中是需要重寫需要的 doFunction 方法懂盐,比如說重寫 doGet 以接收并處理使用 get 方式傳來的數(shù)據(jù)褥赊。
一個最簡單的 hello world 服務(wù)器端如下所示:
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(){}
}
服務(wù)器端處理流程
如下是一個極簡的服務(wù)器處理框架:
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程序
//動態(tài)加載 servlet
Class clazz = Class.forName("xxx");
Servlet servlet = (HttpServlet)clazz.newInstance();
//處理 clazz 的service 方法
servlet.service(request, response);
} else {
//靜態(tài)頁面允粤,直接發(fā)送
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
}
Java 的反射機制很好的將用戶接口和服務(wù)器連接起來崭倘。
總結(jié)服務(wù)器的工作流程如下:
graph LR
創(chuàng)建ServerSocket-->監(jiān)聽ServerSocket
監(jiān)聽ServerSocket-->當有新連接時創(chuàng)建Socket
當有新連接時創(chuàng)建Socket-->填充Request和Response
填充Request和Response-->如果是servlet則動態(tài)加載servlet處理