Tomcat源碼分析 -- 1

Tomcat源碼分析 -- 1

sschrodinger

2018/12/10


資源



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處理
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市类垫,隨后出現(xiàn)的幾起案子司光,更是在濱河造成了極大的恐慌,老刑警劉巖悉患,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件残家,死亡現(xiàn)場離奇詭異,居然都是意外死亡售躁,警方通過查閱死者的電腦和手機坞淮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陪捷,“玉大人回窘,你說我怎么就攤上這事∈行洌” “怎么了啡直?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵烁涌,是天一觀的道長。 經(jīng)常有香客問我酒觅,道長撮执,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任舷丹,我火速辦了婚禮抒钱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颜凯。我一直安慰自己谋币,他們只是感情好,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布症概。 她就那樣靜靜地躺著瑞信,像睡著了一般。 火紅的嫁衣襯著肌膚如雪穴豫。 梳的紋絲不亂的頭發(fā)上凡简,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機與錄音精肃,去河邊找鬼秤涩。 笑死,一個胖子當著我的面吹牛司抱,可吹牛的內(nèi)容都是我干的筐眷。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼习柠,長吁一口氣:“原來是場噩夢啊……” “哼匀谣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起资溃,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤武翎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后溶锭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宝恶,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年趴捅,在試婚紗的時候發(fā)現(xiàn)自己被綠了垫毙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡拱绑,死狀恐怖综芥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情猎拨,我是刑警寧澤膀藐,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布征峦,位于F島的核電站,受9級特大地震影響消请,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜类腮,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一臊泰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蚜枢,春花似錦缸逃、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至筷凤,卻和暖如春昭殉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背藐守。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工挪丢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卢厂。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓乾蓬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親慎恒。 傳聞我的和親對象是個殘疾皇子任内,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351