栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

浅谈Spring Cloud zuul http请求转发原理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

浅谈Spring Cloud zuul http请求转发原理

spring cloud 网关,依赖于netflix 下的zuul 组件

zuul 的流程是,自定义 了ZuulServletFilter和zuulServlet两种方式,让开发者可以去实现,并调用

先来看下ZuulServletFilter的实现片段

 @Override
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    try {
      init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
      try {
 preRouting();
      } catch (ZuulException e) {
 error(e);
 postRouting();
 return;
      }
      
      // only forward onto to the chain if a zuul response is not being sent
      if (!RequestContext.getCurrentContext().sendZuulResponse()) {
 filterChain.doFilter(servletRequest, servletResponse);
 return;
      }
      
      try {
 routing();
      } catch (ZuulException e) {
 error(e);
 postRouting();
 return;
      }
      try {
 postRouting();
      } catch (ZuulException e) {
 error(e);
 return;
      }
    } catch (Throwable e) {
      error(new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_FROM_FILTER_" + e.getClass().getName()));
    } finally {
      RequestContext.getCurrentContext().unset();
    }
  }

从上面的代码可以看到,比较关心的是preRouting、routing,postRouting三个方法 ,这三个方法会调用 注册为ZuulFilter的子类,首先来看下这三个方法

preRouting: 是路由前会做一些内容

routing():开始路由事项

postRouting:路由结束,不管是否有错误都会经过该方法

那这三个方法是怎么和ZuulFilter联系在一起的呢?

先来分析下 preRouting:

 void postRouting() throws ZuulException {
    zuulRunner.postRoute();
  }

同时 ZuulRunner再来调用

  public void postRoute() throws ZuulException {
    FilterProcessor.getInstance().postRoute();
  }

最终调用 FilterProcessor 的 runFilters

  public void preRoute() throws ZuulException {
    try {
      runFilters("pre");
    } catch (ZuulException e) {
      throw e;
    } catch (Throwable e) {
      throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
    }
  }

看到了runFilters 是通过 filterType(pre ,route ,post )来过滤出已经注册的 ZuulFilter:

 public Object runFilters(String sType) throws Throwable {
    if (RequestContext.getCurrentContext().debugRouting()) {
      Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
    }
    boolean bResult = false;
    //通过sType获取 zuulFilter的列表
    List list = FilterLoader.getInstance().getFiltersByType(sType);
    if (list != null) {
      for (int i = 0; i < list.size(); i++) {
 ZuulFilter zuulFilter = list.get(i);
 Object result = processZuulFilter(zuulFilter);
 if (result != null && result instanceof Boolean) {
   bResult |= ((Boolean) result);
 }
      }
    }
    return bResult;
  }

再来看下 ZuulFilter的定义

public abstract class ZuulFilter implements IZuulFilter, Comparable {

  private final DynamicBooleanProperty filterDisabled =
      DynamicPropertyFactory.getInstance().getBooleanProperty(disablePropertyName(), false);

  
  abstract public String filterType();

  
  abstract public int filterOrder();

  
  public boolean isStaticFilter() {
    return true;
  }

只列出了一部分字段,但可以看到filterType和filterOrder两个字段,这两个分别是指定filter是什么类型,排序

这两个决定了实现的ZuulFilter会在什么阶段被执行,按什么顺序执行

当选择好已经注册的ZuulFilter后,会调用ZuulFilter的runFilter

 public ZuulFilterResult runFilter() {
    ZuulFilterResult zr = new ZuulFilterResult();
    if (!isFilterDisabled()) {
      if (shouldFilter()) {
 Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
 try {
   Object res = run();
   zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
 } catch (Throwable e) {
   t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
   zr = new ZuulFilterResult(ExecutionStatus.FAILED);
   zr.setException(e);
 } finally {
   t.stopAndLog();
 }
      } else {
 zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
      }
    }
    return zr;
  }

其中run 是一个ZuulFilter的一个抽象方法

public interface IZuulFilter {
  
  boolean shouldFilter();

  
  Object run();
}  

所以,实现ZuulFilter的子类要重写 run方法,我们来看下 其中一个阶段的实现 PreDecorationFilter 这个类是Spring Cloud封装的在使用Zuul 作为转发的代码服务器时进行封装的对象,目的是为了决定当前的要转发的请求是按ServiceId,Http请求,还是forward来作转发

@Override
  public Object run() {
    RequestContext ctx = RequestContext.getCurrentContext();
    final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
    Route route = this.routeLocator.getMatchingRoute(requestURI);
    if (route != null) {
      String location = route.getLocation();
      if (location != null) {
 ctx.put("requestURI", route.getPath());
 ctx.put("proxy", route.getId());
 if (!route.isCustomSensitiveHeaders()) {
   this.proxyRequestHelper
.addIgnoredHeaders(this.properties.getSensitiveHeaders().toArray(new String[0]));
 }
 else {
   this.proxyRequestHelper.addIgnoredHeaders(route.getSensitiveHeaders().toArray(new String[0]));
 }

 if (route.getRetryable() != null) {
   ctx.put("retryable", route.getRetryable());
 }
 // 如果配置的转发地址是http开头,会设置 RouteHost
 if (location.startsWith("http:") || location.startsWith("https:")) {
   ctx.setRouteHost(getUrl(location));
   ctx.addOriginResponseHeader("X-Zuul-Service", location);
 }
  // 如果配置的转发地址forward,则会设置forward.to
 else if (location.startsWith("forward:")) {
   ctx.set("forward.to",
StringUtils.cleanPath(location.substring("forward:".length()) + route.getPath()));
   ctx.setRouteHost(null);
   return null;
 }
 else {
    // 否则以serviceId进行转发
   // set serviceId for use in filters.route.RibbonRequest
   ctx.set("serviceId", location);
   ctx.setRouteHost(null);
   ctx.addOriginResponseHeader("X-Zuul-ServiceId", location);
 }
 if (this.properties.isAddProxyHeaders()) {
   addProxyHeaders(ctx, route);
   String xforwardedfor = ctx.getRequest().getHeader("X-Forwarded-For");
   String remoteAddr = ctx.getRequest().getRemoteAddr();
   if (xforwardedfor == null) {
     xforwardedfor = remoteAddr;
   }
   else if (!xforwardedfor.contains(remoteAddr)) { // Prevent duplicates
     xforwardedfor += ", " + remoteAddr;
   }
   ctx.addZuulRequestHeader("X-Forwarded-For", xforwardedfor);
 }
 if (this.properties.isAddHostHeader()) {
   ctx.addZuulRequestHeader("Host", toHostHeader(ctx.getRequest()));
 }
      }
    }
    else {
      log.warn("No route found for uri: " + requestURI);

      String fallBackUri = requestURI;
      String fallbackPrefix = this.dispatcherServletPath; // default fallback
    // servlet is
    // DispatcherServlet

      if (RequestUtils.isZuulServletRequest()) {
 // remove the Zuul servletPath from the requestUri
 log.debug("zuulServletPath=" + this.properties.getServletPath());
 fallBackUri = fallBackUri.replaceFirst(this.properties.getServletPath(), "");
 log.debug("Replaced Zuul servlet path:" + fallBackUri);
      }
      else {
 // remove the DispatcherServlet servletPath from the requestUri
 log.debug("dispatcherServletPath=" + this.dispatcherServletPath);
 fallBackUri = fallBackUri.replaceFirst(this.dispatcherServletPath, "");
 log.debug("Replaced DispatcherServlet servlet path:" + fallBackUri);
      }
      if (!fallBackUri.startsWith("/")) {
 fallBackUri = "/" + fallBackUri;
      }
      String forwardURI = fallbackPrefix + fallBackUri;
      forwardURI = forwardURI.replaceAll("//", "/");
      ctx.set("forward.to", forwardURI);
    }
    return null;
  }

这个前置处理,是为了后面决定以哪种ZuulFilter来处理当前的请求 ,如 SimpleHostRoutingFilter,这个的filterType是post ,当 ``PreDecorationFilter设置了requestContext中的 RouteHost,如 SimpleHostRoutingFilter中的判断

  @Override
  public boolean shouldFilter() {
    return RequestContext.getCurrentContext().getRouteHost() != null
 && RequestContext.getCurrentContext().sendZuulResponse();
  }

在 SimpleHostRoutingFilter中的run中,真正实现地址转发的内容,其实质是调用 httpClient进行请求

@Override
  public Object run() {
    RequestContext context = RequestContext.getCurrentContext();
    HttpServletRequest request = context.getRequest();
    MultiValueMap headers = this.helper
 .buildZuulRequestHeaders(request);
    MultiValueMap params = this.helper
 .buildZuulRequestQueryParams(request);
    String verb = getVerb(request);
    InputStream requestEntity = getRequestBody(request);
    if (request.getContentLength() < 0) {
      context.setChunkedRequestBody();
    }

    String uri = this.helper.buildZuulRequestURI(request);
    this.helper.addIgnoredHeaders();

    try {
      HttpResponse response = forward(this.httpClient, verb, uri, request, headers,
   params, requestEntity);
      setResponse(response);
    }
    catch (Exception ex) {
      context.set(ERROR_STATUS_CODE, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
      context.set("error.exception", ex);
    }
    return null;
  }

最后如果是成功能,会调用 注册 为post的ZuulFilter ,目前有两个 SendErrorFilter 和 SendResponseFilter 这两个了,一个是处理错误,一个是处理成功的结果

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/140181.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号