SpringCloud基础(5)

来源:互联网 发布:决战武林宠物升级数据 编辑:程序博客网 时间:2024/06/10 11:33

8 Zuul处理Cookie和重定向

在实际项目中,将Spring Cloud Zuul作为API网关接入时,经常会碰到下面两个常见的问题:
会话无法保持
重定向后的HOST错误

8.1 会话保持问题

通过跟踪一个HTTP请求经过Zuul到具体服务,再到返回结果的全过程。可以发现在传递的过程中,HTTP请求头信息中的Cookie和Authorization都没有被正确传递给具体服务,所以最终导致会话状态没有得到保持的现象。
请求一进来,首先会经过Zuul的路由转发过滤器,RibbonRoutingFilter过滤器run()函数如下:

public Object run() {        RequestContext context = RequestContext.getCurrentContext();        this.helper.addIgnoredHeaders(new String[0]);        try {            RibbonCommandContext commandContext = this.buildCommandContext(context);            ClientHttpResponse response = this.forward(commandContext);            this.setResponse(response);            return response;        } catch (ZuulException var4) {            throw new ZuulRuntimeException(var4);        } catch (Exception var5) {            throw new ZuulRuntimeException(var5);        }}

可以看到,过滤函数获取到请求内容之后,通过this.buildCommandContext(context)进行处理,返回上下文内容。而this.buildCommandContext(context)函数实现如下:

protected RibbonCommandContext buildCommandContext(RequestContext context) {        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);        …        return new RibbonCommandContext(serviceId, verb, uri, retryable, headers, params,                requestEntity, this.requestCustomizers, contentLength);    }

该函数通过this.helper.buildZuulRequestHeaders(request)处理请求头信息,helper对象是
ProxyReuqestHelper类的实例,其中buildZuulRequestHeaders(request)如下:

public MultiValueMap<String, String> buildZuulRequestHeaders(      HttpServletRequest request) {   RequestContext context = RequestContext.getCurrentContext();   MultiValueMap<String, String> headers = new HttpHeaders();   Enumeration<String> headerNames = request.getHeaderNames();   if (headerNames != null) {      while (headerNames.hasMoreElements()) {         String name = headerNames.nextElement();         if (isIncludedHeader(name)) {            Enumeration<String> values = request.getHeaders(name);            while (values.hasMoreElements()) {               String value = values.nextElement();               headers.add(name, value);            }         }      }   }   Map<String, String> zuulRequestHeaders = context.getZuulRequestHeaders();   for (String header : zuulRequestHeaders.keySet()) {      headers.set(header, zuulRequestHeaders.get(header));   }   headers.set(HttpHeaders.ACCEPT_ENCODING, "gzip");   return headers;}

可以看到,构建头信息时通过isIncludeHeader函数判断当前请求的各个头信息是否在忽略的头信息中,如果是的话就不加入到转发headers中去:

public boolean isIncludedHeader(String headerName) {        String name = headerName.toLowerCase();        RequestContext ctx = RequestContext.getCurrentContext();        if (ctx.containsKey(IGNORED_HEADERS)) {            Object object = ctx.get(IGNORED_HEADERS);            if (object instanceof Collection && ((Collection<?>) object).contains(name)) {                return false;            }        }        switch (name) {        case "host":        case "connection":        case "content-length":        case "content-encoding":        case "server":        case "transfer-encoding":        case "x-application-context":            return false;        default:            return true;        }    }

需要呼略的头信息就是此处的IGNORED_HEADERS=“ignoredHeaders”,该常量的初始化位置为PreDecorationFilter的run()函数:

public class PreDecorationFilter extends ZuulFilter {    ...    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/else块。在该块中,通过判断是否存在自定义的全局敏感信息头,如不存在就通过addIgnoredHeaders方法将默认的敏感信息头添加到忽略头信息中;如果有自定义全局敏感信息头,就将其加入到呼略头信息中。而默认的敏感信息头在定义于ZuulProperties properties中:

@Data@ConfigurationProperties("zuul")public class ZuulProperties {    private Set<String> sensitiveHeaders = new LinkedHashSet<>(            Arrays.asList("Cookie", "Set-Cookie", "Authorization"));    ...}

至此会话无法保持的问题就很清楚了,就是因为没有设置全局敏感头信息,从而zuul将默认的敏感头信息添加到转发忽略头中去了,而cookie刚好包含在默认的全局敏感头信息中。解决办法就是设置自定义敏感头信息,设置方法有两种,一种是设置全局敏感头信息:
zuul.sensitive-headers=xxx
另一种是指定路由设置:
zuul.routes..sensitive-headers=xxx
zuul.routes..custom-sensitive-headers=true

8.2 重定向问题

在使用Spring Cloud Zuul时往往还会碰到以下问题,即在浏览器中通过zuul发起登录请求,该请求会被路由到某website服务,该服务在完成了登录处理后,进行重定向到某个主页或欢迎页面。此时在登录完成之后,浏览器中url的host部分会发生改变,该地址变成了具体的website服务的地址了。
出现该问题的原因是Spring Cloud Zuul没有正确处理HTTP请求头信息中的host导致。在Brixton之后的版本中,通过配置属性zuul.add-host-header=true就能让重定向操作得到正确的处理。

原文:http://blog.didispace.com/spring-cloud-zuul-cookie-redirect/

9 Zuul过滤器

前文中说过,实现ZuulFilter接口的过滤器需要实现四个方法,其中一个是filterType(),该方法返回一个字符串,代表过滤器类型,分别是:pre,route,post,error。
需要注意的是,当使用的注解不同时,开启的过滤器也是不同的。常用的注解是@EnableZuulProxy,可以理解为@EnableZuulServer的增强版。在官方文档中,@EnableZuulServer是一个“空白”的zuul,它包含以下过滤器:

pre类型的过滤器有ServletDetectionFilter,FormBodyWrapperFilter和DebuggerFilter。
ServletDetectionFilter过滤器用于检查请求是否通过Spring Dispatcher,然后通过isDispatcherServerletRequest设置布尔值。
FormBodyWrapperFilter过滤器用于解析表单数据,并为请求重新编码。
DebuggerFilter过滤器顾名思义是调试用的过滤器,可以通过设置zuul.debug.request=true或者在请求时在请求参数中加上debug=true来开启。开启之后过滤器会把RequestContext.setDebugRouting()和RequestContext.setDebugRequest()设置true。

route类型的过滤器有sendForwardFilter,该过滤器使用Servlet RequestDispathcer转发请求,转发位置存储在RequestContext.getCurrentContext().get(“forward.to”)中。

post类型过滤器有SendResponseFilter,它将zuul所代理的微服务的响应写入当前响应。

error类型过滤器有SendErrorFilter,该过滤器判断RequestContext.getThrowable()是否为空,若不为空,也就是说处理请求过程中发生了错误,此时默认转发到/error。当然也可以设置error.path属性修改默认的转发路径。

而当使用@EnableZuulProxy注解时,除上述过滤器外,Spring Cloud还会安装以下过滤器:

pre类型过滤器
PreDecorationFilter:该过滤器根据提供的RouteLocator确定路由到的地址,以及怎样去路由。该过滤器也可为后端请求设置各种代理相关的header,在前文8.1会话保持问题中,忽略头ignoredHeaders的初始化就是在该过滤器中实现的。

route类型过滤器
RibbonRoutingFilter过滤器使用Ribbon,Hystrix和可插拔的HTTP客户端发送请求。该过滤器可使用不同HTTP客户端,默认使用Apache HttpClient。该过滤器会对后端请求的header和参数进行处理。
SimpleHostRoutingFilter过滤器通过Apache HttpClient向指定的URL发送请求,URL在RequestContext.getRouteHost中。