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

Posted 华为云开发者社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了带你梳理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 运行原理和自定义方法大致梳理完毕。还有许多漏掉的和理解不到位的地方,希望大家可以提出指正。工作中偶尔抽出一点时间读一下源码,既可以提升对所用技术的理解,又可以学习欣赏这些框架的巧妙设计,还是非常有意义的。

 

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

 

以上是关于带你梳理Jetty自定义ProxyServlet实现反向代理服务的主要内容,如果未能解决你的问题,请参考以下文章

Jetty 如何创建自定义 WebSocket

一文带你梳理Clang编译步骤及命令

Android开源实战:手把手带你实现一个简单好用的搜索框(含历史搜索记录)

一步一图,带你重头梳理微服务架构!

Maven02_04_使用jetty来跑maven的web项目使用jetty的注意事项没有用Maven的时候是怎么打包的,自定义打包的名字的

Jetty配置虚拟目录,实现把web项目发布到自定义目录,指定指定上下文访问;jetty编码修改