jetty、servlet以及spring的銜接原始碼分析

爬蜥發表於2018-10-21

Servlet

對於一個請求來講,如果只是需要一個靜態頁面,可以直接在伺服器上根據路徑訪問得到,但是如果請求的資料是一個動態頁面,即只有在執行時從後臺資料庫獲取,再拼裝東西返回,然後生成一個對應的html檔案。在Java中為了實現這個功能,使用的就是Servlet規範。

Servlet:server component,執行在伺服器上的java程式碼

Servlet容器

Servlet並不處理任何的協議和連線等等動作,它只是約定了一個種處理request-response的模式。每個功能實現Servlet介面的類都可以用來處理請求,比如加法用1個servlet,減法用一個Servlet,這樣一但多起來,就需要知道,那些請求用那個Servlet處理,對應的配置產生物也就是web.xml,另外Servlet物件的構建、連線埠的請求,處理好對應的對映關係等等都需要有一個程式來負責,這個程式稱作Servlet容器,比如Jetty,從Jetty的總體架構也就可以看出來它很好的實踐了這些

jetty、servlet以及spring的銜接原始碼分析

Connector負責連線,Handler則處理對應的請求,交給Servlet來處理

Servlet的生命週期

Servlet的生命週期是由釋出它的容器控制的,比如Jetty,當要把請求對映到一個Servlet上時,容器一般會做如下的事情:

  1. 如果Servlet不存在,就載入Servlet類,建立Servlet例項,然後呼叫Servlet的init方法
  2. 執行Servlet的service方法,傳遞request和response物件
  3. 如果容器要移除掉servlet,它就會呼叫Servlet的destroy方法

javax.servletjavax.servlet.http提供了要實現Servlet的所有介面和相關類,每一個處理Servlet的類必須實現 Servlet.java介面,它的基本實現為GenericServlet,是與協議無關的一個實現,如果要實現自己的Servlet介面,可以繼承它,僅需要實現對應的Service方法。對於處理HTTP請求則可以使用HttpServlet更方便,它根據Http請求的型別,對service方法進行了細分,這樣可以更好的去處理想要處理的請求

    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);
        }
    }
複製程式碼

每次請求總會攜帶一些東西,返回也是,Servlet針對請求抽象了ServletRequest介面和ServletResponse介面,而特定於Http協議,則分別設計了HttpServletRequest介面和HttpServletResponse,每次請求Servlet容器負責實現對應的類提交給service方法

web.xml 配置

web.xml常見配置如下

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:application-context.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:web-mvc-dispatcher.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

複製程式碼

Jetty啟動web專案中的分析可知,會依次的去執行ContextLoaderListener的contextInitialized和DispatcherServlet的init方法,這裡就是jetty容器、servlet和spring的銜接

Jetty處理網路請求

Jetty啟動web專案分析可得到,網路請求分成兩部分

  1. 等待連線建立
  2. 處理連線請求

等待連線建立

Jetty中的ServerConnector接收到請求之後呼叫accepted

private void accepted(SocketChannel channel) throws IOException
{
    channel.configureBlocking(false); //將新的連線設定成非阻塞的
    Socket socket = channel.socket();
    configure(socket);
    _manager.accept(channel);
}
複製程式碼

_manager接收到請求之後,獲取一個Selector,進而提交一個Accept,放到Deque佇列中,Accept實現了SelectorUpdate

public void accept(SelectableChannel channel, Object attachment)
{
    final ManagedSelector selector = chooseSelector();
    selector.submit(selector.new Accept(channel, attachment));
}
複製程式碼

在從Deque中取值時,會執行它的 update方法

  public void update(Selector selector)
    {
        ...
        //執行註冊
        key = channel.register(selector, 0, attachment);
        //執行run方法
        execute(this);
       ...
    }
     
    public void run()
    {
        ...
        createEndPoint(channel, key);
        _selectorManager.onAccepted(channel);
        ...
    }
複製程式碼

建立的createEndPoint過程如下

    private void createEndPoint(SelectableChannel channel, SelectionKey selectionKey) throws IOException
    {
        //注意_selectorManager本身在構造的時候是一個ServerConnectorManager
        //比如這裡是一個SocketChannelEndPoint
        EndPoint endPoint = _selectorManager.newEndPoint(channel, this, selectionKey);
        //建立連線,比如預設的Http了連線
        Connection connection = _selectorManager.newConnection(channel, endPoint, selectionKey.attachment());
        endPoint.setConnection(connection);
        //建立連線的key是一個SocketChannelEndPoint
        selectionKey.attach(endPoint);
        endPoint.onOpen();
        _selectorManager.endPointOpened(endPoint);
        //實際作用是呼叫了httpConnection的onOpen方法
        _selectorManager.connectionOpened(connection);
        if (LOG.isDebugEnabled())
            LOG.debug("Created {}", endPoint);
    }
複製程式碼

ServerConnectorManager建立的newEndPoint為SocketChannelEndPoint

  protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
    {
        SocketChannelEndPoint endpoint = new SocketChannelEndPoint(channel, selectSet, key, getScheduler());
        endpoint.setIdleTimeout(getIdleTimeout());
        return endpoint;
    }

複製程式碼

建立的連線則是根據連線工廠建立的,而預設的則是呼叫的HttpConnectionFactory

 public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment) throws IOException
{
    return getDefaultConnectionFactory().newConnection(ServerConnector.this, endpoint);
}
複製程式碼

建立一個HttpConnection物件

 public HttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint, HttpCompliance compliance, boolean recordComplianceViolations)
    {
        //1. 父類構建了一個_readCallback回撥,當成功的時候就會去執行子類的onFillable方法
        super(endPoint, connector.getExecutor());
        _config = config;
        _connector = connector;
        _bufferPool = _connector.getByteBufferPool();
        //用來構造HTTP的資訊
        _generator = newHttpGenerator();
        //裡頭構建了一個Request物件,它實現了HttpServletRequest,同時構建了Response物件,實現了HttpServletRequest
        _channel = newHttpChannel();
        _input = _channel.getRequest().getHttpInput();
        _parser = newHttpParser(compliance);
        _recordHttpComplianceViolations = recordComplianceViolations;
        if (LOG.isDebugEnabled())
            LOG.debug("New HTTP Connection {}", this);
    }
複製程式碼

可以看到Request和Response是和channel繫結的,同一個TCP連線用的就是同一個Reqeust和Response,他們會迴圈的用

連線建立完成,呼叫open方法,它實際在將回撥的_readCallback寫入SocketChannelEndPoint的_fillInterest中

處理連線請求

Jetty中的EatWhatYouKill的produce方法,即用來處理請求,它核心是隻要獲取task並執行它

Runnable task = produceTask();
複製程式碼

這裡的produceTask實際就是初始化的時候傳入的SelectorProducer的方法

 
    public Runnable produce()
    {
        while (true)
        {
            //如果_cursor中有值,也就是有連線過來了進行處理
            Runnable task = processSelected();
            if (task != null)
                return task;
            //遍歷Deque,比如連線建立的時候會放入一個selector.new Accept,並執行update方法,具體分析見面下的Accept連線建立
            processUpdates();

            updateKeys();
            //執行Selector對應的select()方法,將放回的keys放入_cursor中儲存
            if (!select())
                return null;
        }
    }
複製程式碼

當有請求過來的時候,也就是執行processSelected方法

//SelectorProducer
   private Runnable processSelected()
    {
    ...
        if (attachment instanceof Selectable)
        {
            // 當連線建立後會附上一個SocketChannelEndPoint,它實現了Selectable
            Runnable task = ((Selectable)attachment).onSelected();
            if (task != null)
                return task;
        }
    
    ...
    }
複製程式碼

對應的onSelect實現在ChannelEndPoint中

//ChannelEndPoint
 public Runnable onSelected()
    {
        int readyOps = _key.readyOps();
        ...
        //可讀
        boolean fillable = (readyOps & SelectionKey.OP_READ) != 0;
        //可寫
        boolean flushable = (readyOps & SelectionKey.OP_WRITE) != 0;
        ...
        //根據是可讀還是可寫返回對應的任務
        Runnable task= fillable 
                ? (flushable 
                        ? _runCompleteWriteFillable 
                        : _runFillable)
                : (flushable 
                        ? _runCompleteWrite 
                        : null);
        ...
        return task;
    }
複製程式碼

到這裡可以看到,EatWhatYouKill執行的實際上就是可讀或者可寫的一個channel任務。 以可讀的為例,_runFillable的實現就是從getFillInterest獲取的或吊執行它的fillable方法

 public void run()
    {
        getFillInterest().fillable();
    }
複製程式碼

這對應到建立的HttpConnection,執行它的fillable方法,即呼叫這個連線的HttpChannel來處理

    public void onFillable()
    {
            ....
            boolean suspended = !_channel.handle();
            ....
    }
複製程式碼

而處理的詳情關鍵

public boolean handle()
{
 ...
  getServer().handle(this);
  ...
 }
複製程式碼

這裡就對應處理到Server的handle實現

    public void handle(HttpChannel channel) throws IOException, ServletException
    {
        final String target=channel.getRequest().getPathInfo();
        //獲取channel上的Request
        final Request request=channel.getRequest();
        //獲取channel上的Response
        final Response response=channel.getResponse();
        ...
        if (HttpMethod.OPTIONS.is(request.getMethod()) || "*".equals(target))
        {
            if (!HttpMethod.OPTIONS.is(request.getMethod()))
                response.sendError(HttpStatus.BAD_REQUEST_400);
            handleOptions(request,response);
            if (!request.isHandled())
                handle(target, request, request, response);
        }
        else
            handle(target, request, request, response);
       ...
    }

複製程式碼

對於web專案會有ServletHanlder,對應實現為

 @Override
    public void doHandle(String target, Request baseRequest,HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException
    {
        ....
        if (chain!=null)
            chain.doFilter(req, res);
        else
            servlet_holder.handle(baseRequest,req,res);
        ...
    }
複製程式碼

這裡對應找到ServletHolder的handler方法

    public void handle(Request baseRequest,
                       ServletRequest request,
                       ServletResponse response)
        throws ServletException,
               UnavailableException,
               IOException
    {
        //獲取Servlet
        Servlet servlet = getServlet();
            if (baseRequest.isAsyncSupported() && !isAsyncSupported())
            {
                try
                {
                    baseRequest.setAsyncSupported(false,this.toString());
                    servlet.service(request,response);
                }
                finally
                {
                    baseRequest.setAsyncSupported(true,null);
                }
            }
            else
                //執行Servlet的service方法,並傳入request和response
                servlet.service(request,response);
    }
複製程式碼

原始碼參考

至此對於Spring來說已經連上了DispatcherServlet父類FrameWorkService的service方法,然後轉置DispatcherServlet的doService方法。

總結

Jetty本身去連線了客戶端,自身去實現了Servlet的規範,在每個建立的channel上,自己實現了請求request和response,經由handler,對獲取的web.xml配置中的servlet,關聯上Spring的對應servlet的init和service方法來處理請求

相關文章