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方法

  所以是写到上文封装的流中,最后一并解析到页面,可以参照请求到响应流。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏一个会写诗的程序员的博客

《Spring Boot 2.0 极简教程》附录 I : Spring 5.0 新特性《Spring Boot 2.0 极简教程》附录 I : Spring 5.0 新特性

因为Spring Boot 2.0 是基于Spring Framework 5.0而开发的,所以我们这里对 Spring 5.0的新功能特性做一个简单的介绍。 ...

813
来自专栏Java架构沉思录

Mybatis的SqlSession是如何运行的

SqlSession是Mybatis最重要的构建之一,可以简单的认为Mybatis一系列的配置目的是生成类似JDBC生成的Connection对象的SqlSes...

762
来自专栏androidBlog

Android 二次封装网络加载框架

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/de...

251
来自专栏美团技术团队

Spring MVC注解故障追踪记

Spring MVC是美团点评很多团队使用的Web框架。在基于Spring MVC的项目里,注解的使用几乎遍布在项目中的各个模块,有Java提供的注解,如:@O...

3647
来自专栏Java成神之路

Java企业微信开发_08_素材管理之下载微信临时素材到本地服务器

请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&med...

632
来自专栏ASP.NET MVC5 后台权限管理系统

构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(8)-MVC与EasyUI DataGrid 分页

前言 为了符合后面更新后的重构系统,文章于2016-11-1日重写 EasyUI Datagrid在加载的时候会提交一些分页的信息到后台,我们需要根据这些信...

2407
来自专栏李家的小酒馆

Quarzt定时调度任务

简介 Quarzt是一个项目中定时执行任务的开源项目,Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J...

1830
来自专栏Java编程技术

基于Java注解和模块化生成树形业务文档的实践

一个新人快速掌握一个新系统业务逻辑的最好的工具是什么,是看代码?是debug?是看uc?是看demo?答案应该都不是,因为看代码和debug一来太耗时,二来系统...

471
来自专栏智能大石头

XCode新增数据转换功能(导数据)

用法: DAL.AddConnStr("xxgk", "Data Source=192.168.1.21;Initial Catalog=信息公开;user i...

1826
来自专栏yukong的小专栏

​【SpringBoot2.0系列06】SpringBoot之多数据源动态切换数据源

【SpringBoot2.0系列02】SpringBoot之使用Thymeleaf视图模板

1636

扫描关注云+社区