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中。
- SpringCloud基础(5)
- SpringCloud基础(1)
- SpringCloud基础(0)
- SpringCloud基础(2)
- SpringCloud基础(3)
- SpringCloud基础(4)
- 探索SpringCloud一(基础概念)
- springCloud基础配置
- SpringCloud零基础上手(三)——服务注册
- SpringCloud笔记(1):微服务介绍,SpringBoot基础
- 【SpringCloud】(一):SpringCloud入门程序
- springcloud常见问题(四)
- springcloud常见问题(三)
- springcloud入门(1)
- Springcloud学习(一)
- SpringCloud--断路器(Hystrix)
- SpringCloud:断路器(Hystrix)
- Netflix(奈飞)--SpringCloud
- IntelliJ IDEA报class is never used
- 玩转windbg软件调试视频教程发布-windbg入门教程
- [bzoj4597][Shoi2016]随机序列 线段树
- 浅谈AP聚类算法-matlab
- 开博首篇
- SpringCloud基础(5)
- 归并排序
- 挑战程序竞赛系列(91):3.6凸包(2)
- Thread类的sleep()方法和对象的wait()方法都能使线程暂停执行,他们有什么区别?
- 《红楼梦》的方位观念
- OSG可绘制体Drawable
- EBS中调用fnd_profile.value(GL_ACCESS_SET_ID)取不到值的情况
- 什么是图灵测试
- [caioj]KMP总结(?)