摘要:最近要做一个将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 运行原理和自定义办法大抵梳理结束。还有许多漏掉的和了解不到位的中央,心愿大家能够提出斧正。工作中偶然抽出一点工夫读一下源码,既能够晋升对所用技术的了解,又能够学习观赏这些框架的奇妙设计,还是十分有意义的。

点击关注,第一工夫理解华为云陈腐技术~