SpringCloud基础(5)

目录:
SpringCloud基础(0)
SpringCloud基础(1)
SpringCloud基础(2)
SpringCloud基础(3)
SpringCloud基础(4)
SpringCloud基础(5)

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中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值