1. 程式人生 > >Spring Cloud實戰小貼士 Zuul處理Cookie和重定向

Spring Cloud實戰小貼士 Zuul處理Cookie和重定向

由於我們在之前所有的入門教程中,對於HTTP請求都採用了簡單的介面實現。而實際使用過程中,我們的HTTP請求要複雜的多,比如當我們將Spring Cloud Zuul作為API閘道器接入網站類應用時,往往都會碰到下面這兩個非常常見的問題:

  • 會話無法保持
  • 重定向後的HOST錯誤

本文將幫助大家分析問題原因並給出解決這兩個常見問題的方法。

會話保持問題

通過跟蹤一個HTTP請求經過Zuul到具體服務,再到返回結果的全過程。我們很容易就能發現,在傳遞的過程中,HTTP請求頭資訊中的Cookie和Authorization都沒有被正確地傳遞給具體服務,所以最終導致會話狀態沒有得到保持的現象。

那麼這些資訊是在哪裡丟失的呢?我們從Zuul進行路由轉發的過濾器作為起點,來一探究竟。下面是RibbonRoutingFilter過濾器的實現片段:

public class RibbonRoutingFilter extends ZuulFilter {
	...
	protected ProxyRequestHelper helper;

	@Override
	public Object run() {
		RequestContext context = RequestContext.getCurrentContext();
		this.helper.addIgnoredHeaders();
try { RibbonCommandContext commandContext = buildCommandContext(context); ClientHttpResponse response = forward(commandContext); setResponse(response); return response; } ... return null; } 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); ... } }

這裡有三個重要元素:

  • 過濾器的核心邏輯run函式實現,其中呼叫了內部函式buildCommandContext來構建上下文內容
  • buildCommandContext中呼叫了helper物件的buildZuulRequestHeaders方法來處理請求頭資訊
  • helper物件是ProxyRequestHelper類的例項

接下來我們再看看ProxyRequestHelper的實現:

public class ProxyRequestHelper {

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

	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;
			}
		}
		...
	}
}

從上述原始碼中,我們可以看到構建頭資訊的方法buildZuulRequestHeaders通過isIncludedHeader函式來判斷當前請求的各個頭資訊是否在忽略的頭資訊清單中,如果是的話就不組織到此次轉發的請求中去。那麼這些需要忽略的頭資訊是在哪裡初始化的呢?在PRE階段的PreDecorationFilter過濾器中,我們可以找到答案:

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塊,通過呼叫ProxyRequestHelperaddIgnoredHeaders方法來新增需要忽略的資訊到請求上下文中,供後續ROUTE階段的過濾器使用。這裡的if/else塊分別用來處理全域性設定的敏感頭資訊和指定路由設定的敏感頭資訊。而全域性的敏感頭資訊定義於ZuulProperties中:

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

所以解決該問題的思路也很簡單,我們只需要通過設定sensitiveHeaders即可,設定方法分為兩種:

  • 全域性設定:
    • zuul.sensitive-headers=
  • 指定路由設定:
    • zuul.routes.<routeName>.sensitive-headers=
    • zuul.routes.<routeName>.custom-sensitive-headers=true

重定向問題

在使用Spring Cloud Zuul對接Web網站的時候,處理完了會話控制問題之後。往往我們還會碰到如下圖所示的問題,我們在瀏覽器中通過Zuul發起了登入請求,該請求會被路由到某WebSite服務,該服務在完成了登入處理之後,會進行重定向到某個主頁或歡迎頁面。此時,仔細的開發者會發現,在登入完成之後,我們瀏覽器中URL的HOST部分發生的改變,該地址變成了具體WebSite服務的地址了。這就是在這一節,我們將分析和解決的重定向問題!

出現該問題的根源是Spring Cloud Zuul沒有正確的處理HTTP請求頭資訊中的Host導致。在Brixton版本中,Spring Cloud Zuul的PreDecorationFilter過濾器實現時完全沒有考慮這一問題,它更多的定位於REST API的閘道器。所以如果要在Brixton版本中增加這一特性就相對較為複雜,不過好在Camden版本之後,Spring Cloud Netflix 1.2.x版本的Zuul增強了該功能,我們只需要通過配置屬性zuul.add-host-header=true就能讓原本有問題的重定向操作得到正確的處理。關於更多Host頭資訊的處理,讀者可以參考本文之前的分析思路,可以通過檢視PreDecorationFilter過濾器的原始碼來詳細更多實現細節。

原文由程式猿DD-翟永超創作,原文地址:http://blog.didispace.com/spring-cloud-zuul-cookie-redirect/

再分享一下我老師大神的人工智慧教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智慧的隊伍中來!https://www.cnblogs.com/captainbed