为了账号安全,请及时绑定邮箱和手机立即绑定

Spring Cloud zuul http请求转发原理

标签:
Spring

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();
        }
    }

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

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

routing():开始路由事项

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

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

先来分析下 preRouting:

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

同时 ZuulRunner再来调用

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

最终调用 FilterProcessorrunFilters

    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<ZuulFilter> 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<ZuulFilter> {    private final DynamicBooleanProperty filterDisabled =
            DynamicPropertyFactory.getInstance().getBooleanProperty(disablePropertyName(), false);    /**
     * to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
     * "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.
     * We also support a "static" type for static responses see  StaticResponseFilter.
     * Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)
     *
     * @return A String representing that type
     */
    abstract public String filterType();    /**
     * filterOrder() must also be defined for a filter. Filters may have the same  filterOrder if precedence is not
     * important for a filter. filterOrders do not need to be sequential.
     *
     * @return the int order of a filter
     */
    abstract public int filterOrder();    /**
     * By default ZuulFilters are static; they don't carry state. This may be overridden by overriding the isStaticFilter() property to false
     *
     * @return true by default
     */
    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 {    /**
     * a "true" return from this method means that the run() method should be invoked
     *
     * @return true if the run() method should be invoked. false will not invoke the run() method
     */
    boolean shouldFilter();    /**
     * if shouldFilter() is true, this method will be invoked. this method is the core method of a ZuulFilter
     *
     * @return Some arbitrary artifact may be returned. Current implementation ignores it.
     */
    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<String, String> headers = this.helper
                .buildZuulRequestHeaders(request);
        MultiValueMap<String, String> 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;
    }



作者:David_jim
链接:https://www.jianshu.com/p/295e51bc1518


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消