继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Spring Cloud zuul http请求转发原理

幕布斯6054654
关注TA
已关注
手记 1135
粉丝 218
获赞 1009

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


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP