Geeklazy 极懒 | Tomcat-源码剖析-静态资源处理

先说一个前提, 在Tomcat中, 所有的请求都是交给不同的实现了 javax.servlet.Servlet 接口的实现类来处理的.

而所有请求就包括了静态资源, 今天我们就从源码层面上看看Tomcat的静态资源处理过程:

1. DefaultServlet

DefaultServlet 就是Tomcat中负责处理静态资源的Servlet, service() 方法是请求处理入口, 可以看到, 这里所有的请求都交给了 doGet() 方法, 而 doGet() 又调用了 serveResource() 方法来处理.

public class DefaultServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        if (req.getDispatcherType() == DispatcherType.ERROR) {
            doGet(req, resp);
        } else {
            super.service(req, resp);
        }
    }

    @Override
    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
        throws IOException, ServletException {

        // Serve the requested resource, including the data content
        serveResource(request, response, true, fileEncoding);
    }
}

那接下来看看 serveResource() 方法

    protected void serveResource(HttpServletRequest request,
                                 HttpServletResponse response,
                                 boolean content,
                                 String encoding)
        throws IOException, ServletException {

        ...

        // 1. 获取静态资源的相对路径
        String path = getRelativePath(request, true);
        
        ...

        if (path.length() == 0) {
            // 根路径, 按照文件目录处理, 暂不关注
            doDirectoryRedirect(request, response);
            return;
        }

        // 2. 尝试获取网络资源
        WebResource resource = resources.getResource(path);

        
        // 一些校验和响应属性设置
        ...

        ServletOutputStream ostream = null;
        PrintWriter writer = null;

        if (serveContent) {
            // 3. 获取响应输出流
            try {
                ostream = response.getOutputStream();
            } catch (IllegalStateException e) {
                ...
            }
        }

        if (resource.isDirectory() ||
                isError ||
                ( (ranges == null || ranges.isEmpty())
                        && request.getHeader("Range") == null ) ||
                ranges == FULL ) {

            // 资源是目录或者其他特殊情况, 我们先不看
            ...
                
        } else {
            
            if ((ranges == null) || (ranges.isEmpty()))
                return;

            // Partial content response.

            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

            if (ranges.size() == 1) {

                Range range = ranges.get(0);
                response.addHeader("Content-Range", "bytes "
                                   + range.start
                                   + "-" + range.end + "/"
                                   + range.length);
                long length = range.end - range.start + 1;
                response.setContentLengthLong(length);

                if (contentType != null) {
                    if (debug > 0)
                        log("DefaultServlet.serveFile:  contentType='" +
                            contentType + "'");
                    response.setContentType(contentType);
                }

                if (serveContent) {
                    try {
                        response.setBufferSize(output);
                    } catch (IllegalStateException e) {
                        // Silent catch
                    }
                    if (ostream != null) {
                        if (!checkSendfile(request, response, resource,
                                range.end - range.start + 1, range))
                            // 4. 将资源写入输出流
                            copy(resource, ostream, range);
                    } else {
                        // we should not get here
                        throw new IllegalStateException();
                    }
                }
            } else {
                response.setContentType("multipart/byteranges; boundary="
                                        + mimeSeparation);
                if (serveContent) {
                    try {
                        response.setBufferSize(output);
                    } catch (IllegalStateException e) {
                        // Silent catch
                    }
                    if (ostream != null) {
                        // 4. 将资源写入输出流
                        copy(resource, ostream, ranges.iterator(), contentType);
                    } else {
                        // we should not get here
                        throw new IllegalStateException();
                    }
                }
            }
        }
    }

这个就是请求到达DefaultServlet后资源的获取及响应流程, 重点的方法有几个:

getRelativePath() 解析资源相对路径

resources.getResource() 获取资源

copy() 将资源写入到输出流

那么你可能会问, Tomcat怎么知道什么请求是静态资源要交给DefaultServlet处理?

2. Mapper组件

我们知道, Mapper组件的作用是在请求到达Tomcat后, 通过 map() 方法映射返回Wrapper(Servlet的包裹类) 的

详细信息可以到这篇文章:

而在MappedContext中有一个属性ContextVersion, ContextVersion中又有几个重要属性:

	protected static final class ContextVersion extends MapElement<Context> {
    	
        ... 
            
        public MappedWrapper defaultWrapper = null;
        public MappedWrapper[] exactWrappers = new MappedWrapper[0];
        public MappedWrapper[] wildcardWrappers = new MappedWrapper[0];
        public MappedWrapper[] extensionWrappers = new MappedWrapper[0];
        
        ...
    }

这里后面几个Wrapper集合, 对应的是不同的匹配规则匹配出来的Wrapper, 而第一个defaultWrapper则是对应配置为"/"的Servlet的Wrapper, 在我们不做覆盖配置的情况下, 这里就是DefaultServlet的包裹Wrapper了.

在Mapper的map()方法中, Tomcat会根据请求信息找到对应的应用下的所有上面的这些Wrapper, 并和上面的三个Wrapper集合进行比对, 如果找到能处理的Wrapper返回, 如果找不到就返回defaultWrapper, 也就是DefaultServlet来当做静态资源来处理了.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值