1. 程式人生 > 其它 >tomcat請求處理分析(六)servlet的處理過程

tomcat請求處理分析(六)servlet的處理過程

1.1.1.1  servlet的解析過程

servlet的解析分為兩步實現,第一個是匹配到對應的Wrapper,第二個是載入對應的servlet並進行資料,這些資料是怎麼到介面的,response.getWrite()獲取對應的流,然後寫入這個流中,這個流中就有上文的outputBuffer。

匹配到對應Wrapper

   在上文中我們曾經走到過了doRun方法,現在就直接從這裡開始

執行順序如下:

NioEndpoint(run)==>下步呼叫doRun

NioEndpoint(doRun)==>下步呼叫state = handler.process(ka,status);

handler例項物件Http11ConnectionHandler其繼承AbstractConnectionHandler

AbstractConnectionHandler(process) ==》下步呼叫 state = processor.process(wrapper);

processor例項物件Http11NioProcessor 其繼承AbstractHttp11Processor

AbstractHttp11Processor(process)  ==》下步呼叫getAdapter().service(request, response);

CoyoteAdapter.service(request,response)這個方法就已經接近核心處理了,程式碼如下:

在第一處標紅的地方,對請求進行了解析,並且匹配到對應的主機和context和wrapper

在第二處標紅的地方是載入servlet並進行呼叫處理

在第三處標紅的地方是重新整理流,響應到介面

@SuppressWarnings("deprecation")
@Override
publicvoid service(org.apache.coyote.Request req,
                    org.apache.coyote.Responseres)
    throws Exception {
    Request request = (Request)req.getNote(ADAPTER_NOTES);
    Response response =(Response) res.getNote(ADAPTER_NOTES);

    if (request == null) {

        //建立一個request物件
        request = connector.createRequest();
        request.setCoyoteRequest(req);
        response = connector.createResponse();
        response.setCoyoteResponse(res);

        // Link objects
        request.setResponse(response);
        response.setRequest(request);

        // Set as notes
        req.setNote(ADAPTER_NOTES,request);
        res.setNote(ADAPTER_NOTES,response);

        // Set query string encoding
        req.getParameters().setQueryStringEncoding
            (connector.getURIEncoding());

    }

    if (connector.getXpoweredBy()){
        response.addHeader("X-Powered-By",POWERED_BY);
    }

    boolean comet =false;
    boolean async = false;
    boolean postParseSuccess = false;

    try {
        //設定執行執行緒執行緒名
        req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());
        //對uri進行解碼,主要是解析報文,如果不合法返回響應碼400
        postParseSuccess = postParseRequest(req,request, res, response);

        if (postParseSuccess) {
            //設定是否支援非同步
            request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());

            //不斷呼叫管道載入對應的servlet進行呼叫,其中傳遞了response引數,所以可以放入流資料
            connector.getService().getContainer().getPipeline().getFirst().invoke(request,response);

            if (request.isComet()) {
                if (!response.isClosed()&& !response.isError()) {
                    comet = true;
                    res.action(ActionCode.COMET_BEGIN, null);
                    if (request.getAvailable()|| (request.getContentLength() >0 &&(!request.isParametersParsed()))) {
                       event(req, res,SocketStatus.OPEN_READ);
                    }
                } else {
                          request.setFilterChain(null);
                }
            }
        }
        //如果是非同步請求
        if (request.isAsync()){
            async = true;
            ReadListener readListener= req.getReadListener();
            if (readListener != null&&request.isFinished()) {
                ClassLoader oldCL =null;
                try {
                    oldCL =request.getContext().bind(false, null);
                    if (req.sendAllDataReadEvent()){
                       req.getReadListener().onAllDataRead();
                    }
                } finally {
                   request.getContext().unbind(false,oldCL);
                }
            }
            Throwable throwable =
                    (Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
            if (!request.isAsyncCompleting()&& throwable !=null) {
               request.getAsyncContextInternal().setErrorState(throwable, true);
            }
        } else if (!comet) {
            //如果為同步請求,Flush並關閉輸入輸出流
            request.finishRequest();
            response.finishResponse();
        }
    } catch (IOExceptione) {
        // Ignore
    } finally{
        AtomicBoolean error = new AtomicBoolean(false);
        res.action(ActionCode.IS_ERROR,error);

        if (request.isAsyncCompleting()&& error.get()) {
            res.action(ActionCode.ASYNC_POST_PROCESS,  null);
            async = false;
        }

        if (!async&& !comet) {
            if (postParseSuccess){
                        request.getMappingData().context.logAccess(
                        request, response,
                        System.currentTimeMillis()- req.getStartTime(),
                        false);
            }
        }

       req.getRequestProcessor().setWorkerThreadName(null);

        if (!comet &&!async) {
            request.recycle();
            response.recycle();
        } else{
            request.clearEncoders();
            response.clearEncoders();
        }
    }
}
1.1.1.1.1     構造對應request的MappingData屬性
postParseRequest:CoyoteAdapter(org.apache.catalina.connector)
connector.getService().getMapper().map(serverName,decodedURI,version, request.getMappingData());
 
下面是request部分構造程式碼
protected final MappingDatamappingData =new MappingData();
public MappingDatagetMappingData() {
    return mappingData;
}

   根據呼叫方法,我們可以知道其傳入的引數有request例項成員物件mappingData的引用型別,所以下面的匹配的Context以及Wrapper所到的mappingData都是當前request的屬性

============================================

map: Mapper (org.apache.catalina.mapper)

internalMap(host.getCharChunk(),uri.getCharChunk(), version, mappingData);

internalMap:758,Mapper (org.apache.catalina.mapper)

在這裡面匹配到了對應的虛擬主機,存放到了mappingData中去,以及Context主要採用了二分查詢獲取遊標進行匹配

然後其呼叫internalMapWrapper(contextVersion,uri, mappingData);匹配到了Wrapper存放到mappingData,其匹配規則如下

/**
 * a: 對全新的路徑進行精準匹配
 * b: 對全新的路徑進行萬用字元匹配
 * c: 根據全新的路徑,進行查詢是否存在相應的檔案,如果存在相應的檔案,則需要將該檔案返回。在回前我們需要進一步確認,這個檔案是不是講檔案內容原始碼返回,還是像jsp檔案一樣,進行一定的處理然後再返回,所以又要確認下檔案的副檔名是怎樣的
 *   c1: 嘗試尋找能夠處理該副檔名的servlet,即進行副檔名匹配,如果找到,則使用對應的servlet
 *   c2: 如果沒找到,則預設使用defaultWrapper,即DefaultServlet(它只會將檔案內容原始碼返回,不做任何處理)
 * d: 對全新的路徑進行副檔名匹配(與c的目的不同,c的主要目的是想返回一個檔案的內容,在返回內容前涉及到副檔名匹配,所以4c的前提是存在對應路徑的檔案)

 * 案例1: a.html,a、b沒有匹配到,到c的時候,找到了該檔案,然後又嘗試副檔名匹配,來決定是走c1還是c2,由於.html還沒有對應的servlet來處理,就使用了預設的DefaultServlet
 * 案例2: a.jsp,同上,在走到c的時候,找到了處理.jsp對應的servlet,所以走了c1
 * 案例3: a.action,如果根目錄下有a.action檔案,則走到c1的時候,進行副檔名匹配,匹配到了SecondServlet,即走了c1,使用SecondServlet來處理請求;如果根目錄下沒有a.action檔案,則走到了d,進行副檔名匹配,同樣匹配到了SecondServlet,即走了d,同樣使用SecondServlet來處理請求
 * 案例4: first/abc,執行b的時候,就匹配到了FirstServlet,所以使用FirstServlet來處理請求
 * */
private final void internalMapWrapper(ContextVersioncontextVersion,
                                      CharChunkpath,
                                      MappingDatamappingData)throws IOException {

  。。。。。。。
        if (found) {
            mappingData.wrapperPath.setString(wrappers[pos].name);
            if (path.getLength() >length) {
                mappingData.pathInfo.setChars
                    (path.getBuffer(),
                     path.getOffset()+ length,
                     path.getLength()- length);
            }
            mappingData.requestPath.setChars
                (path.getBuffer(), path.getOffset(),path.getLength());
            mappingData.wrapper=wrappers[pos].object;
            mappingData.jspWildCard=wrappers[pos].jspWildCard;
        }
    }
}
1.1.1.1.2     載入servlet並呼叫

   一起執行順序來看一下一個servlet如何進行載入

invoke:98,StandardEngineValve (org.apache.catalina.core)

程式碼如下:

/**
 * 基於請求的服務名選擇合適的虛擬主機進行請求處理
 *
 * 如果不能匹配到對應主機,返回對應的http錯誤
 *
 * @param request 執行請求
 * @param response Response tobe produced
 *
 */
@Override
publicfinal void invoke(Request request,Response response)
    throws IOException,ServletException{

    //根據請求找到對應的host
    Host host =request.getHost();
    if (host == null) {
        response.sendError
            (HttpServletResponse.SC_BAD_REQUEST,
             sm.getString("standardEngine.noHost",
                          request.getServerName()));
        return;
    }
    //設定當前請求是否支援非同步
    if (request.isAsyncSupported()){
       request.setAsyncSupported(host.getPipeline().isAsyncSupported());
    }

    //org.apache.catalina.valves.AccessLogValve[localhost]
   //org.apache.catalina.valves.ErrorReportValve[localhost]
   //org.apache.catalina.core.StandardHostValve[localhost]
    /**
     * 呼叫host的第一個valve
     *
     * 其執行原理是獲取根據管道獲取第一個閥門AccessLogValve呼叫其invoke方法
     *
     * AccessLogValve的invoke第一行呼叫getNext().invoke()呼叫了ErrorReportValve
     *
     * 同理呼叫了StandardHostValve的invoke方法所以實際呼叫的最先的是invoke方法
     * */
    host.getPipeline().getFirst().invoke(request,response);
}

invoke:143,StandardHostValve (org.apache.catalina.core)

  下步呼叫context.getPipeline().getFirst().invoke(request,response);

invoke:504,AuthenticatorBase(org.apache.catalina.authenticator)

   下步呼叫getNext().invoke(request, response);

/**
 *
 * 基於URI的request獲取對應的Wrapper如果沒有匹配到返回一個HTTP錯誤
 *
 * 在這個方法中做的事情主要是獲取wrapper然後進行對應管道的閥門進行呼叫
 * @param request Request tobe processed
 * @param response Response tobe produced
 *
 * @exception IOException if aninput/output error occurred
 * @exception ServletException ifa servlet error occurred
 */
@Override
publicfinal void invoke(Request request,Response response)
    throws IOException,ServletException{

    // Disallow any directaccess to resources under WEB-INF or META-INF
    MessageBytesrequestPathMB = request.getRequestPathMB();
    if ((requestPathMB.startsWithIgnoreCase("/META-INF/",0))
            ||(requestPathMB.equalsIgnoreCase("/META-INF"))
            ||(requestPathMB.startsWithIgnoreCase("/WEB-INF/",0))
            ||(requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
       response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    //獲取當前request利用的wrapper
    Wrapper wrapper =request.getWrapper();
    if (wrapper == null||wrapper.isUnavailable()) {
       response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return;
    }

    // Acknowledge the request
    try {
        response.sendAcknowledgement();
    } catch(IOExceptionioe) {
        container.getLogger().error(sm.getString(
                "standardContextValve.acknowledgeException"),ioe);
        request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,ioe);
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        return;
    }

    if (request.isAsyncSupported()){
       request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
    }
    
   wrapper.getPipeline().getFirst().invoke(request,response);
}

invoke:98,StandardWrapperValve (org.apache.catalina.core)

其主要操作如下:獲取到對應的StandardWrapper,然後分配一個servlet,具體在loadServlet中進行例項話,再分配由於是成員變數,只有第一次呼叫的時候才會進行分配,之後直接返回第一次的例項化對一下物件,具體看allocate方法

public final void invoke(Request request,Responseresponse)
    throws IOException,ServletException{

 
   //獲取到對應的StandardWrapper
    StandardWrapper wrapper= (StandardWrapper) getContainer();
    //每個請求開始servlet都是為空
    Servlet servlet = null;
    Context context =(Context) wrapper.getParent();
    try {
        if (!unavailable){
            servlet = wrapper.allocate();
        }
    }        

    MessageBytes requestPathMB =request.getRequestPathMB();
    DispatcherTypedispatcherType = DispatcherType.REQUEST;
    if (request.getDispatcherType()==DispatcherType.ASYNC)dispatcherType = DispatcherType.ASYNC;
    request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType);
    request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
            requestPathMB);
    // 建立過濾器例項
    ApplicationFilterChain filterChain =
            ApplicationFilterFactory.createFilterChain(request,wrapper, servlet);

  
        if ((servlet !=null) &&(filterChain !=null)) {
          if (context.getSwallowOutput()) {
                try {
                    SystemLogHandler.startCapture();
                    if (request.isAsyncDispatching()){
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else if(comet) {
                       filterChain.doFilterEvent(request.getEvent());
                    } else{
                       filterChain.doFilter(request.getRequest(),
                                response.getResponse());
                    }
                } finally {
                    String log =SystemLogHandler.stopCapture();
                    if (log != null&&log.length() > 0) {
                       context.getLogger().info(log);
                    }
                }
            } else {
                if (request.isAsyncDispatching()){
                   request.getAsyncContextInternal().doInternalDispatch();
                } else if(comet) {
                   filterChain.doFilterEvent(request.getEvent());
                } else{
                    filterChain.doFilter
                       (request.getRequest(), response.getResponse());
                }
            }

        }
    }

    //釋放過濾器
    if (filterChain!=null) {
        if (request.isComet()){
            // If this is a Cometrequest, then the same chain will be used for the
            // processing of allsubsequent events.
            filterChain.reuse();
        } else{
            filterChain.release();
        }
    }

    // Deallocate theallocated servlet instance
    try {
        if (servlet !=null) {
            wrapper.deallocate(servlet);
        }
    } catch (Throwablee) {
        ExceptionUtils.handleThrowable(e);
        container.getLogger().error(sm.getString("standardWrapper.deallocateException",
                         wrapper.getName()),e);
        if (throwable == null) {
            throwable = e;
            exception(request,response, e);
        }
    }


    try {
        if ((servlet !=null) &&
            (wrapper.getAvailable() ==Long.MAX_VALUE)) {
            wrapper.unload();
        }
    } catch (Throwablee) {
        ExceptionUtils.handleThrowable(e);
        container.getLogger().error(sm.getString("standardWrapper.unloadException",
                         wrapper.getName()),e);
        if (throwable == null) {
            throwable = e;
            exception(request,response, e);
        }
    }
    long t2=System.currentTimeMillis();

    long time=t2-t1;
    processingTime += time;
    if( time > maxTime)maxTime=time;
    if( time < minTime)minTime=time;
}

   servlet的呼叫

   按照這個順序執行完所有過濾器就會執行對應的servlet,這是因為在建立過濾器

ApplicationFilterChain filterChain =             ApplicationFilterFactory.createFilterChain(request,wrapper, servlet);    的時候,將servlet給注入進去了,當過濾器執行完了,會執行呼叫servlet的service, 由於自己寫的servlet是會繼承HttpServlet的,所以將呼叫其service方法

   呼叫如下:

internalDoFilter:,ApplicationFilterChain

方法如下:下面展示了兩個service ,同在HttpServlet只是方法的引數有所不同,載入過程先呼叫一個,然後第一個再呼叫第二個,根據請求方法呼叫自己對應的Servlet中的doGet等一些列方法

protected void service(HttpServletRequest req,HttpServletResponseresp)
    throws ServletException,IOException{

    //獲取對應的方法
    String method = req.getMethod();

    /**
     * 根據請求method呼叫對應方法
     * GET ==>doGet(req, resp)
     * head ==> doHead(req, resp)
     * post ==>doPost(req,resp)
     *
     * */
    if (method.equals(METHOD_GET)) {
        long lastModified= getLastModified(req);
        if (lastModified == -1) {
            // servlet doesn't supportif-modified-since, no reason
            // to go through furtherexpensive logic
            doGet(req,resp);
        } else{
            long ifModifiedSince;
            try {
                ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            } catch(IllegalArgumentExceptioniae) {
                // Invalid date header -proceed as if none was set
                ifModifiedSince = -1;
            }
            if (ifModifiedSince< (lastModified /1000 * 1000)) {
                // If the servlet mod timeis later, call doGet()
                // Round down to thenearest 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 NOservlet supports whatever
        // method was requested, anywhereon this server.
        //

        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = newObject[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg,errArgs);

        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED,errMsg);
    }
}

上面已經講述了一個servlet呼叫的過程,他的資訊是如何返回掉流中,我們的看一下response,getWrite方法

  可以看出這個流最終將outputBuffer給封裝,其write方法

  所以是寫到上文封裝的流中,最後一併解析到頁面,可以參照請求到響應流。