• 带你梳理Jetty自定义ProxyServlet实现反向代理服务


    摘要:最近要做一个将K8s中的某组件UI通过反向代理映射到自定义规则的链接地址上,提供给用户访问的需求。所以顺便研究了一下Jetty的ProxyServlet。

    本文分享自华为云社区《Jetty自定义ProxyServlet实现反向代理服务(含源码分析)》,作者: 小焱 。

    一、背景概述

    最近要做一个将K8s中的某组件UI通过反向代理映射到自定义规则的链接地址上,提供给用户访问的需求。所以顺便研究了一下Jetty的ProxyServlet。在这里做一下分析,如果有理解不到位的还希望可以补充指正。

    二、Jetty 的基本架构

    Jetty 是一个Servlet 引擎,它的架构比较简单,也是一个可扩展性和非常灵活的应用服务器,它有一个基本数据模型,这个数据模型就是 Handler,所有可以被扩展的组件都可以作为一个 Handler,添加到 Server 中,Jetty 就是帮你管理这些 Handler。

    整个 Jetty 的核心组件由 Server 和 Connector 两个组件构成,整个 Server 组件是基于 Handler 容器工作的,Jetty 中另外一个比不可少的组件是 Connector,它负责接受客户端的连接请求,并将请求分配给一个处理队列去执行。Jetty 中还有一些可有可无的组件,我们可以在它上做扩展。如 JMX,我们可以定义一些 Mbean 把它加到 Server 中,当 Server 启动的时候,这些 Bean 就会一起工作。

    整个 Jetty 的核心是围绕着 Server 类来构建,Server 类继承了 Handler,关联了 Connector 和 Container。Container 是管理 Mbean 的容器。Jetty 的 Server 的扩展主要是实现一个个 Handler 并将 Handler 加到 Server 中,Server 中提供了调用这些 Handler 的访问规则。整个 Jetty 的所有组件的生命周期管理是基于观察者模板设计,实现LifeCycle。

    三、Handler 的体系结构

    Jetty 主要是基于 Handler 来设计的,Handler 的体系结构影响着整个 Jetty 的方方面面。下面总结了一下 Handler 的种类及作用:

    Jetty 主要提供了两种 Handler 类型,一种是 HandlerWrapper,它可以将一个 Handler 委托给另外一个类去执行,如我们要将一个 Handler 加到 Jetty 中,那么就必须将这个 Handler 委托给 Server 去调用。配合 ScopeHandler 类我们可以拦截 Handler 的执行,在调用 Handler 之前或之后,可以做一些另外的事情,类似于 Tomcat 中的 Valve;另外一个 Handler 类型是 HandlerCollection,这个 Handler 类可以将多个 Handler 组装在一起,构成一个 Handler 链,方便我们做扩展。

    四、编码设计

    这里我提供一个设计框架,具体内容可以根据需要自定义。

    public class RestApi {
        private static final Logging LOGGER = Logging.getLogging(RestApi.class.getName());
    ​
        private Server server;
    ​
        /**
         * 启动方法,需要在程序启动时调用该方法
         */
        public void start() {
            try {
                ContextHandlerCollection collection = new ContextHandlerCollection();
                WebAppContext appContext = new WebAppContext();
                appContext.setContextPath("/");
                // 设置资源文件地址,可略
                appContext.setResourceBase("/opt/appHome/myDemo/webapp");
                // 设置web.xml,可在里面进行一些Servlet配置,可略
                appContext.setDescriptor("/opt/appHome/myDemo/webapp/WEB-INF/web.xml"); 
                appContext.setParentLoaderPriority(true);
                collection.addHandler(appContext);
                addProxyHandler(collection);
    ​
                server = new Server(8080);
                server.setHandler(collection);
                server.start();
            } catch (Throwable t) {
                LOGGER.error("Start RESTful API server failed", t);
            }
        }
     
        private static void addProxyHandler(ContextHandlerCollection collection) {
            ProxyServlet proxyServlet = new WebProxyServlet();      // 添加自定义ProxyServlet
    ​
            ServletHolder holder = new ServletHolder(proxyServlet);
    ​
            holder.setInitParameter("idleTimeout", 120000);         // 设置空闲释放时间
            holder.setInitParameter("timeout", 300000);             // 设置超时时间
            holder.setInitParameter("maxConnections", 256);         // 设置最大连接数
    ​
            ServletContextHandler contextHandler = new ServletContextHandler();
            contextHandler.addServlet(holder, "/proxy/*");
            contextHandler.setContextPath("/demo");
            collection.addHandler(contextHandler);
        }
    }

    自定义 ProxyServlet,在此列出一部分常用的重写方法,还有很多方法可以查询文档自行重写

    public class WebProxyServlet extends ProxyServlet {
        private static final Logging LOGGING = Logging.getLogging(WebProxyServlet.class);
    ​
        /**
         * 自定义目标地址重写方法
         */
        @Override
        protected String rewriteTarget(HttpServletRequest request) { }      
     
        /**
         * 自定义重写错误处理方法
         */
        @Override
        protected void onProxyRewriteFailed(HttpServletRequest clientRequest, HttpServletResponse clientResponse) { }
     
        /**
         * 自定义response错误处理方法
         */
        @Override
        protected void onProxyResponseFailure(
            HttpServletRequest clientRequest,
            HttpServletResponse proxyResponse,
            Response serverResponse,
            Throwable failure) { }
     
        /**
         * 自定义response头filter
         */
        @Override
        protected String filterServerResponseHeader(
            HttpServletRequest clientRequest,
            Response serverResponse,
            String headerName,
            String headerValue) { }
     
        /**
         * 自定义头XForwarded设置
         */
        @Override
        protected void addXForwardedHeaders(HttpServletRequest clientRequest, Request proxyRequest) { }
    }

    五、请求处理过程

    下面通过跟踪源码初步梳理了一下,从 request 请求进入到返回 response 的整个流程

    六、源码分析

    1、Request 转发部分

    当 Jetty 接收到一个请求时,Jetty 就把这个请求交给在 Server 中注册的 ContextHandlerCollection 去执行,查看 Service 的 handle 方法源码

     public void handle(HttpChannel channel) throws IOException, ServletException {
            String target = channel.getRequest().getPathInfo();
            Request request = channel.getRequest();
            Response response = channel.getResponse();
            if (LOG.isDebugEnabled()) {
                LOG.debug("{} {} {} on {}", new Object[]{request.getDispatcherType(), request.getMethod(), target, channel});
            }
    ​
            if (!HttpMethod.OPTIONS.is(request.getMethod()) && !"*".equals(target)) {
                this.handle(target, request, request, response);
            } else if (!HttpMethod.OPTIONS.is(request.getMethod())) {
                request.setHandled(true);
                response.sendError(400);
            } else {
                this.handleOptions(request, response);
                if (!request.isHandled()) {
                    this.handle(target, request, request, response);
                }
            }
    ​
            if (LOG.isDebugEnabled()) {
                LOG.debug("handled={} async={} committed={} on {}", new Object[]{request.isHandled(), request.isAsyncStarted(), 
                                                                                 response.isCommitted(), channel});
            }
        }

    这里调用的 this.handle(target, request, request, response) 方法其实是父类 HandlerWrapper 的 handle 方法

     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
            throws IOException, ServletException {
            Handler handler = this._handler;
            if (handler != null) {
                handler.handle(target, baseRequest, request, response);
            }
        }

    创建 server 时曾调用过 server.setHandler(collection) ,所以这里就调用到了 ContextHandlerCollection 的 handle 方法

      public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
            throws IOException, ServletException {
            ContextHandlerCollection.Mapping mapping = (ContextHandlerCollection.Mapping)this._handlers.get();
            if (mapping != null) {
                Handler[] handlers = mapping.getHandlers();
                if (handlers != null && handlers.length != 0) {
                    if (handlers.length == 1) {
                        handlers[0].handle(target, baseRequest, request, response);
                    } else {
                        HttpChannelState async = baseRequest.getHttpChannelState();
                        if (async.isAsync()) {
                            ContextHandler context = async.getContextHandler();
                            if (context != null) {
                                Handler branch = (Handler)mapping._contextBranches.get(context);
                                if (branch == null) {
                                    context.handle(target, baseRequest, request, response);
                                } else {
                                    branch.handle(target, baseRequest, request, response);
                                }
    ​
                                return;
                            }
                        }
    ​
                        int limit;
                        if (target.startsWith("/")) {
                            Trie<Entry<String, ContextHandlerCollection.Branch[]>> pathBranches = mapping._pathBranches;
                            if (pathBranches == null) {
                                return;
                            }
    ​
                            int l;
                            for(limit = target.length() - 1; limit >= 0; limit = l - 2) {
                                Entry<String, ContextHandlerCollection.Branch[]> branches = 
                                    (Entry)pathBranches.getBest(target, 1, limit);
                                if (branches == null) {
                                    break;
                                }
    ​
                                l = ((String)branches.getKey()).length();
                                if (l == 1 || target.length() == l || target.charAt(l) == '/') {
                                    ContextHandlerCollection.Branch[] var12 = 
                                        (ContextHandlerCollection.Branch[])branches.getValue();
                                    int var13 = var12.length;
    ​
                                    for(int var14 = 0; var14 < var13; ++var14) {
                                        ContextHandlerCollection.Branch branch = var12[var14];
                                        branch.getHandler().handle(target, baseRequest, request, response);
                                        if (baseRequest.isHandled()) {
                                            return;
                                        }
                                    }
                                }
                            }
                        } else {
                            Handler[] var17 = handlers;
                            limit = handlers.length;
    ​
                            for(int var19 = 0; var19 < limit; ++var19) {
                                Handler handler = var17[var19];
                                handler.handle(target, baseRequest, request, response);
                                if (baseRequest.isHandled()) {
                                    return;
                                }
                            }
                        }
                    }
                }
            }
        }

    从上面的源码可以看出 ContextHandlerCollection 的 handle 方法继续调用了 collection.addHandler 设置进来 ServletContextHandler 的 handle 方法,通过跟踪,可以找到其实这里调用了父类 ScopedHandler 的 handle --> doScope --> nextScope

      public final void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
            throws IOException, ServletException {
            if (this.isStarted()) {
                if (this._outerScope == null) {
                    this.doScope(target, baseRequest, request, response);
                } else {
                    this.doHandle(target, baseRequest, request, response);
                }
            }
        }
    ​
        public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
            throws IOException, ServletException {
            this.nextScope(target, baseRequest, request, response);
        }
    ​
        public final void nextScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
            throws IOException, ServletException {
            if (this._nextScope != null) {
                this._nextScope.doScope(target, baseRequest, request, response);
            } else if (this._outerScope != null) {
                this._outerScope.doHandle(target, baseRequest, request, response);
            } else {
                this.doHandle(target, baseRequest, request, response);
            }
        }

    查看 ServletContextHandler 可以找到主要注册了以下三个 handler,均为 ScopedHandler 的子类,也就是 nextScope 方法中的 this._nextScope

    protected SessionHandler _sessionHandler;
    protected SecurityHandler _securityHandler;
    protected ServletHandler _servletHandler;

    SessionHandler 是对 ServletHandler 进行了一层包装(装饰器模式),用于一些session的预处理什么的,而SecurityHandler从名字分析是做一些安全相关的,这两个具体就不分析了,直接来看 ServletHandler 的 doScope 方法

       public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
            throws IOException, ServletException {
            String old_servlet_path = baseRequest.getServletPath();
            String old_path_info = baseRequest.getPathInfo();
            DispatcherType type = baseRequest.getDispatcherType();
            ServletHolder servletHolder = null;
            Scope oldScope = null;
            MappedResource<ServletHolder> mapping = this.getMappedServlet(target);
            if (mapping != null) {
                servletHolder = (ServletHolder)mapping.getResource();
                if (mapping.getPathSpec() != null) {
                    PathSpec pathSpec = mapping.getPathSpec();
                    String servletPath = pathSpec.getPathMatch(target);
                    String pathInfo = pathSpec.getPathInfo(target);
                    if (DispatcherType.INCLUDE.equals(type)) {
                        baseRequest.setAttribute("javax.servlet.include.servlet_path", servletPath);
                        baseRequest.setAttribute("javax.servlet.include.path_info", pathInfo);
                    } else {
                        baseRequest.setServletPath(servletPath);
                        baseRequest.setPathInfo(pathInfo);
                    }
                }
            }
    ​
            if (LOG.isDebugEnabled()) {
                LOG.debug("servlet {}|{}|{} -> {}", 
                          new Object[]{baseRequest.getContextPath(), 
                                       baseRequest.getServletPath(), 
                                       baseRequest.getPathInfo(), 
                                       servletHolder});
            }
    ​
            try {
                oldScope = baseRequest.getUserIdentityScope();
                baseRequest.setUserIdentityScope(servletHolder);
                this.nextScope(target, baseRequest, request, response);
            } finally {
                if (oldScope != null) {
                    baseRequest.setUserIdentityScope(oldScope);
                }
    ​
                if (!DispatcherType.INCLUDE.equals(type)) {
                    baseRequest.setServletPath(old_servlet_path);
                    baseRequest.setPathInfo(old_path_info);
                }
            }
        }

    这里对 baseRequest 做了一些设置,将注册进来的 ServletHolder set 进了 baseRequest,之后又继续调用了 this.nextScope(target, baseRequest, request, response) ,根据上面的 nextScope 方法,所有 scope 执行完,则执行 doHandle 方法,继续跳过 SessionHandler 和 SecurityHandler,来看下ServletHandler 的 doHandle 方法

     public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
            throws IOException, ServletException {
            ServletHolder servletHolder = (ServletHolder)baseRequest.getUserIdentityScope();
            FilterChain chain = null;
            if (servletHolder != null && this._filterMappings != null && this._filterMappings.length > 0) {
                chain = this.getFilterChain(baseRequest, target.startsWith("/") ? target : null, servletHolder);
            }
    ​
            if (LOG.isDebugEnabled()) {
                LOG.debug("chain={}", new Object[]{chain});
            }
    ​
            try {
                if (servletHolder == null) {
                    this.notFound(baseRequest, request, response);
                } else {
                    ServletRequest req = request;
                    if (request instanceof ServletRequestHttpWrapper) {
                        req = ((ServletRequestHttpWrapper)request).getRequest();
                    }
    ​
                    ServletResponse res = response;
                    if (response instanceof ServletResponseHttpWrapper) {
                        res = ((ServletResponseHttpWrapper)response).getResponse();
                    }
    ​
                    servletHolder.prepare(baseRequest, (ServletRequest)req, (ServletResponse)res);
                    if (chain != null) {
                        chain.doFilter((ServletRequest)req, (ServletResponse)res);
                    } else {
                        servletHolder.handle(baseRequest, (ServletRequest)req, (ServletResponse)res);
                    }
                }
            } finally {
                if (servletHolder != null) {
                    baseRequest.setHandled(true);
                }
            }
        }

    doHandle 主要是取出注册的 FilterChain ServletHolder,如果存在 Filter,先执行 chain.doFilter 方法,否则执行 servletHolder.handle 我没有设置 filter 所有就直接看 ServletHolder 的 handle 方法了

     public void handle(Request baseRequest, ServletRequest request, ServletResponse response) 
            throws ServletException, UnavailableException, IOException {
            try {
                Servlet servlet = this.getServletInstance();
                if (servlet == null) {
                    throw new UnavailableException("Servlet Not Initialized");
                }
    ​
                servlet.service(request, response);
            } catch (UnavailableException var5) {
                this.makeUnavailable(var5).service(request, response);
            }
        }

    这里调用了 ServletHolder 中 Servlet 的 service 方法,也就是走到了我们自定义类 WebProxyServlet 类,因为没有重写,所以这里调用的是 ProxyServlet 的 service 方法

     protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            int requestId = this.getRequestId(request);
            String rewrittenTarget = this.rewriteTarget(request);
            if (this._log.isDebugEnabled()) {
                StringBuffer uri = request.getRequestURL();
                if (request.getQueryString() != null) {
                    uri.append("?").append(request.getQueryString());
                }
    ​
                if (this._log.isDebugEnabled()) {
                    this._log.debug("{} rewriting: {} -> {}", new Object[]{requestId, uri, rewrittenTarget});
                }
            }
    ​
            if (rewrittenTarget == null) {
                this.onProxyRewriteFailed(request, response);
            } else {
                Request proxyRequest = this.newProxyRequest(request, rewrittenTarget);
                this.copyRequestHeaders(request, proxyRequest);
                this.addProxyHeaders(request, proxyRequest);
                AsyncContext asyncContext = request.startAsync();
                asyncContext.setTimeout(0L);
                proxyRequest.timeout(this.getTimeout(), TimeUnit.MILLISECONDS);
                if (this.hasContent(request)) {
                    if (this.expects100Continue(request)) {
                        DeferredContentProvider deferred = new DeferredContentProvider(new ByteBuffer[0]);
                        proxyRequest.content(deferred);
                        proxyRequest.attribute(CONTINUE_ACTION_ATTRIBUTE, () -> {
                            try {
                                ContentProvider provider = this.proxyRequestContent(request, response, proxyRequest);
                                (new ProxyServlet.DelegatingContentProvider(request, proxyRequest, 
                                                                            response, provider, deferred)).iterate();
                            } catch (Throwable var6) {
                                this.onClientRequestFailure(request, proxyRequest, response, var6);
                            }
    ​
                        });
                    } else {
                        proxyRequest.content(this.proxyRequestContent(request, response, proxyRequest));
                    }
                }
                this.sendProxyRequest(request, response, proxyRequest);
            }
        }

    至此调用到了我们重写的最关键的方法 rewriteTarget 此方法可以自定义逻辑将 request 的地址解析,返回要代理到的目标地址,使用目标地址组成proxyRequest 最后调用 sendProxyRequest 实现代理转发。

    2、Response 接收部分

    如果继续跟 sendProxyRequest 会看到创建了一个 ProxyResponseListener,这里具体就不详细跟踪了,主要讲一下流程,有兴趣的可以自行动手看一下。Response 返回会通过反射机制触发 onHeader 方法 ProxyServlet 重写了该方法并跳转到了 onServerResponseHeaders 方法

      public void onHeaders(Response proxyResponse) {
            ProxyServlet.this.onServerResponseHeaders(this.request, this.response, proxyResponse);
        }

    这个方法是设置 Response 的 header 内容的,其中有获取 headerValue 调用了 this.filterServerResponseHeader 方法,我们也可以通过重写此方法自定义返回体的 headerValue。

    七、总结

    到这里 Jetty 的 ProxyServlet 运行原理和自定义方法大致梳理完毕。还有许多漏掉的和理解不到位的地方,希望大家可以提出指正。工作中偶尔抽出一点时间读一下源码,既可以提升对所用技术的理解,又可以学习欣赏这些框架的巧妙设计,还是非常有意义的。

     

    点击关注,第一时间了解华为云新鲜技术~

  • 相关阅读:
    结构-行为-样式-有趣的函数
    结构-行为-样式-angularJs笔记
    Js-Html 前端系列--页面撑开头尾
    Java 实现下载
    Js-Html 前端系列--Ajax
    Js-Html 前端系列--checkbox
    Nutz中过滤特殊字符
    NUTZ中处理系统未捕获异常
    Thymeleaf 笔记
    Json 使用小结
  • 原文地址:https://www.cnblogs.com/huaweiyun/p/15179114.html
Copyright © 2020-2023  润新知