spring cloud連載第三篇補充之Zuul

因爲Zuul的內容較多因此單獨列出一篇來說。全是乾貨,若是學到東西的,動動小手給點個推薦^_^  謝謝!

1. Router and Filter: Zuul(路由和過濾:Zuul)

路由是微服務架構不缺乏的一部分。例如「/」可能映射到web服務,「/api/users」映射到用戶管理服務,而「/api/shop」映射到採購服務。Zuul是Netflix中的一個基於JVM的路由器,也是一個服務端負載均衡器。html

zuul有下列用途:java

  • Authentication(權限驗證)
  • Insights
  • Stress Testing(壓力測試)
  • Canary Testing(金絲雀測試)
  • Dynamic Routing(動態路由)
  • Service Migration(服務遷移)
  • Load Shedding(負載削減)
  • Security(安全機制)
  • Static Response handling(靜態響應處理)
  • Active/Active traffic management(流量管理)

 

注意:git

1)zuul.max.host.connections已經被zuul.host.maxTotalConnections(默認值200)和zuul.host.maxPerRouteConnections(默認值20)代替了。github

2)Hystrix對全部理由的默認隔離模式是SEMAPHORE,能夠經過zuul.ribbonIsolationStrategy改成THREAD。web

1.1 How to Include Zuul(依賴)

1         <dependency>
2             <groupId>org.springframework.cloud</groupId>
3             <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
4         </dependency>

1.2 Embedded Zuul Reverse Proxy(反向代理)

Spring Cloud建立了一個內置Zuul代理來簡化開發,好比有一個UI應用想要使用代理調用後端的一個或者多個服務。這能夠避免爲後臺每一個服務都配置CORS和權限系統。正則表達式

在spring boot的入口類上使用@EnableZuulProxy註解來開啓代理。代理使用Ribbon經過服務發現來定位後端服務實例。而且全部請求在 hystrix command中執行。因此當斷路器打開時,代理將不會重試鏈接後端服務。spring

注意:Zuul starter不包含服務發現客戶端,因此想要使用服務發現功能,須要提供一個服務發現客戶端(好比Eureka)。後端

爲了防止自動添加服務,能夠設置zuul.ignored-services參數來避免。若是一個服務匹配到一個忽略表達式,而且又在路由映射中明確指定了,那麼它就不會被忽略。例如(application.yml):api

1 zuul:
2   ignoredServices: '*'
3   routes:
4     users: /myusers/**

你能夠單獨指定路徑和service ID,例如(application.yml):瀏覽器

1  zuul:
2   routes:
3     users:
4       path: /myusers/**
5       serviceId: users_service

其中path是一個ant風格的表達式,因此/myusers/*僅僅匹配一層目錄,而/myusers/**能夠匹配任意多層級目錄。

其中後端服務的位置既可使用serviceId也可使用url(物理位置)指定。以下(application.yml):

1  zuul:
2   routes:
3     users:
4       path: /myusers/**
5       url: http://example.com/users_service

這些簡單的url路由不會做爲HystrixCommand執行,也不會使用Ribbon負載均衡。若是要使用的話,能夠指定一個服務器列表的serviceId,以下:

application.yml.

 1 zuul:
 2   routes:
 3     echo:
 4       path: /myusers/**
 5       serviceId: myusers-service
 6       stripPrefix: true
 7 
 8 hystrix:
 9   command:
10     myusers-service:
11       execution:
12         isolation:
13           thread:
14             timeoutInMilliseconds: ...
15 
16 myusers-service:
17   ribbon:
18     NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList
19     listOfServers: http://example1.com,http://example2.com
20     ConnectTimeout: 1000
21     ReadTimeout: 3000
22     MaxTotalHttpConnections: 500
23     MaxConnectionsPerHost: 100

另外一個方法是指定一個服務路由而且爲serviceId配置Ribbon客戶端(這麼作須要在Ribbon中禁用Eureka),以下:

application.yml.

 1 zuul:
 2   routes:
 3     users:
 4       path: /myusers/**
 5       serviceId: users
 6 
 7 ribbon:
 8   eureka:
 9     enabled: false
10 
11 users:
12   ribbon:
13     listOfServers: example.com,google.com

可使用正則表達式來配置路由規則。以下:

ApplicationConfiguration.java.

1 @Bean
2 public PatternServiceRouteMapper serviceRouteMapper() {
3     return new PatternServiceRouteMapper(
4         "(?<name>^.+)-(?<version>v.+$)",
5         "${version}/${name}");
6 }

在上面的例子中若是serviceId爲myusers-v1那麼它將被映射到/v1/myusers/**。若是有一個serviceId不匹配,那麼將會使用默認規則。例如,在上面的例子中一個serviceId爲myusers的服務將會映射到"/myusers/**"。

給全部映射添加前綴可使用zuul.prefix。默認狀況下,請求被轉發前將會去除掉其中的代理前綴(可使用zuul.stripPrefix=false來改變默認行爲)。也能夠在單獨的一個路由中關閉去除服務指定前綴的行爲。以下:

application.yml. 

1  zuul:
2   routes:
3     users:
4       path: /myusers/**
5       stripPrefix: false

注意:zuul.stripPrefix只是針對zuul.prefix,對路由中的路徑不起做用。

在上面的例子中/myusers/101請求將會轉發爲/myusers/101到users服務中。

zuul.routes是綁定在ZuulProperties對象上。在這個類裏能夠看到retryable屬性,將它設置爲true,能夠在請求失敗時使用Ribbon客戶端重試。

默認狀況下,X-Forwarded-Host將會添加到轉發的請求頭上,能夠設置zuul.addProxyHeaders = false來關閉它。默認狀況下,路徑中的前綴將會被跳過,轉發的請求頭中將會有一個X-Forwarded-Prefix頭(例如上面例子中的/myusers)。

注意:若是你但願你配置的路由是有序的話,那你應該使用yml配置文件,由於使用properties文件時會丟失排序。

1.3 Zuul Http Client(Zuul HTTP客戶端)

Zuul的默認HTTP客戶端是Apache HTTP客戶端而不是已通過時的Ribbon的RestClient。若是要使用RestClient或者okhttp3.OkHttpClient,能夠設置ribbon.restclient.enabled=true 或者 ribbon.okhttp.enabled=true。

若是你想自定義Apache HTTP client 或者 OK HTTP client,那麼須要提供一個ClosableHttpClient 或者 OkHttpClient類型的bean。

1.4 Cookies and Sensitive Headers(cookies和敏感頭部)

能夠在路由配置中指定要忽略的頭部,以下:

application.yml.

1 zuul:
2   routes:
3     users:
4       path: /myusers/**
5       sensitiveHeaders: Cookie,Set-Cookie,Authorization
6       url: https://downstream

注意:上面的例子是sensitiveHeaders的默認值,這是在Spring Cloud Netflix 1.1版本新增的功能。(在1.0中,不能設置頭部而且全部cookie雙向流動)。

sensitiveHeaders是一個黑名單,默認不爲空。所以要讓Zuul發送全部頭部的話,須要明確指定sensitiveHeaders爲空。以下:

application.yml. 

1  zuul:
2   routes:
3     users:
4       path: /myusers/**
5       sensitiveHeaders:
6       url: https://downstream

你也能夠經過zuul.sensitiveHeader進行全局設置。若是在一個路由上設置sensitiveHeaders的話將會覆蓋全局設置。

1.5 Ignored Headers(忽略頭部)

除了路由敏感頭部之外,你還能夠設置zuul.ignoredHeaders成那些在與下游服務交互時應該剔除的值。默認,Spring Security不在classpath上時,這些值是空的。不然他們被Spring Security初始化爲一些常見的「安全」頭部。

這種狀況下,下游的服務也可能設置這些頭部,可是咱們想要的是代理中的值。若是Spring Security在classpath上時,爲了避免剔除這些安全頭部,能夠設置zuul.ignoreSecurityHeaders=false。

1.6 Management Endpoints(管理端點)

若是你同時使用@EnableZuulProxy 和Spring Boot Actuator,那麼將會開啓兩個額外的端點:

  • Routes
  • Filters

1.6.1 Routes Endpoint(路由端點)

get請求/routes:

GET /routes. 

1 {
2   /stores/**: "http://localhost:8081"
3 }

若是想要獲得路由的詳細信息,在請求上添加?format=details查詢字段。

GET /routes/details

 1 {
 2   "/stores/**": {
 3     "id": "stores",
 4     "fullPath": "/stores/**",
 5     "location": "http://localhost:8081",
 6     "path": "/**",
 7     "prefix": "/stores",
 8     "retryable": false,
 9     "customSensitiveHeaders": false,
10     "prefixStripped": true
11   }
12 }

/routes的POST方法將會強制刷新路由信息。

能夠經過endpoints.routes.enabled=false來禁用這個端點。

注意:路由會自動根據服務目錄的改動來更新,可是POST請求/routes是一種當即強制更新的方法。

 

1.6.2 Filters Endpoint(過濾器端點)

/filters的GET請求將會返回過濾器類型列表。

1.7 Strangulation Patterns and Local Forwards(壓縮模式和本地轉發)

當遷移一個老的應用或者API時,須要慢慢把它的訪問端點替換成新的實現。Zuul會是一個頗有用的代理,由於你可使用它處理全部來自客戶端老的端點的流量而且重定向一些請求到新的實現上。以下:

application.yml. 

 1  zuul:
 2   routes:
 3     first:
 4       path: /first/**
 5       url: http://first.example.com
 6     second:
 7       path: /second/**
 8       url: forward:/second
 9     third:
10       path: /third/**
11       url: forward:/3rd
12     legacy:
13       path: /**
14       url: http://legacy.example.com

其中,forward:開頭的url將會轉發到本地。

 

1.8 Uploading Files through Zuul(經過Zuul上傳文件)

若是你使用@EnableZuulProxy,那麼能夠經過代理路徑來上傳一些小文件,對於大文件有一個能夠繞過Spring DispatcherServlet的路徑「/zuul/*」,換句話說,若是zuul.routes.customers=/customers/*,那麼能夠

經過發送POST請求到/zuul/customers/*。servlet路徑是經過zuul.servletPath外部化的。若是代理路由使用Ribbon,尤爲大文件須要提升超時時間。以下:

application.yml. 

1 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
2 ribbon:
3   ConnectTimeout: 3000
4   ReadTimeout: 60000

1.9 Query String Encoding(查詢字段編碼)

當處理請求時,查詢字段將會被解碼,這樣就能夠在Zuul過濾器中修改他們。而後在過濾器中再從新編碼後發送請求給後端。若是使用Javascrip的encodeURIComponent()方法,那麼結果可能會和原始輸入不一樣。這在大多數狀況下不會有問題,可是一些web服務器對於複雜查詢字段的編碼要求仍是很挑剔的。

爲了強制查詢字符串的原始編碼,能夠向ZuulProperties傳遞一個特殊的標誌,以便將查詢字符串做爲HttpServletRequest::getQueryString方法使用。

application.yml. 

1  zuul:
2   forceOriginalQueryStringEncoding: true

注意:這個特殊的標誌只對SimpleHostRoutingFilter有效,另外能夠經過RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)來覆蓋查詢字符串。

1.10 Plain Embedded Zuul(純內置Zuul)

若是使用@EnableZuulServer (而不是@EnableZuulProxy),能夠啓動一個Zuul服務器可是沒有任何代理。任何ZuulFilter類型的bean會自動安裝,可是沒有任何代理過濾器會被自動添加。

這種狀況下,Zuul服務器的路由依然能夠經過"zuul.routes.*"來配置,可是沒有服務發現和代理。所以,"serviceId" 和 "url"會被忽略掉。在下面的例子中全部"/api/**"中的路徑都會被映射到Zuul的過濾器鏈。

application.yml. 

1  zuul:
2   routes:
3     api: /api/**

1.11 Disable Zuul Filters(禁用Zuul過濾器)

在代理和服務器模式,spring cloud Zuul都會默認註冊一些ZuulFilter。能夠經過zuul.<SimpleClassName>.<filterType>.disable=true來禁用指定的過濾器。

按照慣例,包名中filters的後面就是filterType。例如,若是要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter,能夠設置zuul.SendResponseFilter.post.disable=true。

1.12 Providing Hystrix Fallbacks For Routes(爲路由提供Hystrix降級服務)

當Zuul的路由迴路出現問題時,能夠經過一個FallbackProvider類型的bean來提供降級服務。在這個bean中,須要指定路由的ID,而且提供一個ClientHttpResponse。下面的例子提供了一個相對簡單的FallbackProvider的實現。

 1 class MyFallbackProvider implements FallbackProvider {
 2 
 3     @Override
 4     public String getRoute() {
 5         return "customers";
 6     }
 7 
 8     @Override
 9     public ClientHttpResponse fallbackResponse(String route, final Throwable cause) {
10         if (cause instanceof HystrixTimeoutException) {
11             return response(HttpStatus.GATEWAY_TIMEOUT);
12         } else {
13             return response(HttpStatus.INTERNAL_SERVER_ERROR);
14         }
15     }
16 
17     private ClientHttpResponse response(final HttpStatus status) {
18         return new ClientHttpResponse() {
19             @Override
20             public HttpStatus getStatusCode() throws IOException {
21                 return status;
22             }
23 
24             @Override
25             public int getRawStatusCode() throws IOException {
26                 return status.value();
27             }
28 
29             @Override
30             public String getStatusText() throws IOException {
31                 return status.getReasonPhrase();
32             }
33 
34             @Override
35             public void close() {
36             }
37 
38             @Override
39             public InputStream getBody() throws IOException {
40                 return new ByteArrayInputStream("fallback".getBytes());
41             }
42 
43             @Override
44             public HttpHeaders getHeaders() {
45                 HttpHeaders headers = new HttpHeaders();
46                 headers.setContentType(MediaType.APPLICATION_JSON);
47                 return headers;
48             }
49         };
50     }
51 }

下面的例子是對應上面例子的路由配置:

1 zuul:
2   routes:
3     customers: /customers/**

若是要對全部路由提供一個默認降級服務,能夠建立一個FallbackProvider類型的bean,而後在getRoute方法中返回「*」或者null。以下:

 1 class MyFallbackProvider implements FallbackProvider {
 2     @Override
 3     public String getRoute() {
 4         return "*";
 5     }
 6 
 7     @Override
 8     public ClientHttpResponse fallbackResponse(String route, Throwable throwable) {
 9         return new ClientHttpResponse() {
10             @Override
11             public HttpStatus getStatusCode() throws IOException {
12                 return HttpStatus.OK;
13             }
14 
15             @Override
16             public int getRawStatusCode() throws IOException {
17                 return 200;
18             }
19 
20             @Override
21             public String getStatusText() throws IOException {
22                 return "OK";
23             }
24 
25             @Override
26             public void close() {
27 
28             }
29 
30             @Override
31             public InputStream getBody() throws IOException {
32                 return new ByteArrayInputStream("fallback".getBytes());
33             }
34 
35             @Override
36             public HttpHeaders getHeaders() {
37                 HttpHeaders headers = new HttpHeaders();
38                 headers.setContentType(MediaType.APPLICATION_JSON);
39                 return headers;
40             }
41         };
42     }
43 }

1.13 Zuul Timeouts(Zuul的超時時間)

若是想要爲經過Zuul代理的請求設置socket超時時間和讀取超時時間,你有兩個選項,基於配置:

1)若是Zuul使用服務發現,則配置ribbon.ReadTimeout和ribbon.SocketTimeout;

2)若是路由是經過URL指定的,那麼須要配置zuul.host.connect-timeout-millis和zuul.host.socket-timeout-millis

1.14 Rewriting the Location header(重寫頭部Location字段)

若是Zuul在一個web應用前面,那麼你須要重寫Location頭部當你的web應用經過HTTP狀態碼3XX重定向。不然,瀏覽器會重定向到web應用的URL而不是Zuul的URL。

能夠經過配置一個LocationRewriteFilter類型的Zuul過濾器來重寫Location頭部到Zuul的URL。它還恢復了刪除的全局前綴和特定於路由的前綴。以下:

 1 import org.springframework.cloud.netflix.zuul.filters.post.LocationRewriteFilter;
 2 ...
 3 
 4 @Configuration
 5 @EnableZuulProxy
 6 public class ZuulConfig {
 7     @Bean
 8     public LocationRewriteFilter locationRewriteFilter() {
 9         return new LocationRewriteFilter();
10     }
11 }

注意:要很是當心使用這個過濾器,由於它會做用於全部響應碼爲3XX的Location頭部,這可能在某些場合不適合。好比要重定向到一個外部地址。

1.15 Metrics(度量)

Zuul在Actuator metrics端點下提供metrics,當路由請求出現失敗時。能夠經過/actuator/metrics端點查看。metrics名稱的格式爲ZUUL::EXCEPTION:errorCause:statusCode。

1.16 Zuul Developer Guide(Zuul開發指南)

1.16.1 The Zuul Servlet

Zuul的實現是一個Servlet。一般狀況下,Zuul是嵌入到Spring分發機制中的。Spring MVC會掌控路由。這種狀況下,Zuul會緩存請求。若是有一種狀況是穿過Zuul可是不要緩存(例如大文件的上傳),這時可使用一種獨立於Spring分發器的外部Servlet。默認狀況,這個Servlet的地址是/zuul。也能夠經過zuul.servlet-path屬性來修改。

1.16.2 Zuul RequestContext

Zuul使用RequestContext在不一樣的過濾器中傳遞信息。它的數據保存在特定於每一個請求的ThreadLocal中.它存儲的信息有:路由請求到何處,錯誤,HttpServletRequest 和 HttpServletResponse。

RequestContext繼承ConcurrentHashMap,因此它能夠存儲任何信息。FilterConstants保存了那些被過濾器使用的key。

1.16.3 @EnableZuulProxy vs. @EnableZuulServer

Spring Cloud Netflix安裝了一系列過濾器,安裝了哪些過濾器依賴於你使用哪一種註解來開啓Zuul的。@EnableZuulProxy是@EnableZuulServer的超集。換句話說,@EnableZuulProxy包含了@EnableZuulServer中的過濾器。

在「proxy」模式中的額外過濾器開啓了路由功能。因此若是想要一個「空白」的Zuul,就使用@EnableZuulServer。

1.16.4 @EnableZuulServer Filters

@EnableZuulServer建立一個SimpleRouteLocator(它從Spring Boot配置文件中加載路由定義)。

安裝的過濾器(做爲普通的spring bean):

1)Pre filters:

ServletDetectionFilter:檢測請求是不是經過Spring Dispatcher,設置FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY的布爾值。

FormBodyWrapperFilter:解析表單數據,而且爲下游請求從新編碼這些數據。

DebugFilter:若是請求參數中設置了debug,則RequestContext.setDebugRouting()和RequestContext.setDebugRequest()都設置爲true。

2)Route filters:

SendForwardFilter:使用RequestDispatcher轉發請求。轉發地址存儲在RequestContext的FilterConstants.FORWARD_TO_KEY屬性中。

3)Post filters:

SendResponseFilter:將代理請求的響應寫入到當前的響應中。

4)Error filters:

SendErrorFilter:若是RequestContext.getThrowable()不是null,則轉發到/error(默認)。也能夠error.path設置轉發路徑。

 

1.16.5 @EnableZuulProxy Filters

建立一個 DiscoveryClientRouteLocator(從DiscoveryClient(例如Eureka)和配置文件中加載路由定義)。爲每一個服務發現客戶端中的serviceId都會建立一個路由。當有新服務添加時,路由就會刷新。

除了上面的過濾器外,還有額外的過濾器:

1)Pre filters:

PreDecorationFilter:根據提供的RouteLocator來決定如何路由,而且路由到何處。而且爲下游服務設置了一些代理相關的頭部。

2)Route filters:

RibbonRoutingFilter:使用Ribbon、Hystrix和可插入的HTTP客戶端發送請求。服務ID存儲在RequestContext的FilterConstants.SERVICE_ID_KEY鍵中。

這個過濾器可使用不一樣的HTTP客戶端:

1️⃣Apache HttpClient:默認客戶端

2️⃣Squareup OkHttpClient v3:添加com.squareup.okhttp3:okhttp依賴,而且設置ribbon.okhttp.enabled=true。

3️⃣Netflix Ribbon HTTP client:設置ribbon.restclient.enabled=true。可是這個客戶端有一些限制,它不支持PATCH方法,可是有內建的重試機制。

SimpleHostRoutingFilter:經過Apache HttpClient向預約的url發送請求。URL在RequestContext.getRouteHost()中。

1.16.6 Custom Zuul Filter Examples(自定義Zuul過濾器示例)

下面大多數的例子都包括在Sample Zuul Filters項目中。這個項目中還包含了一些若是修改請求或者響應的消息體的例子。

How to Write a Pre Filter

Pre filters爲在RequestContext中設置數據,給下游的過濾器使用。主要用途就設置一些route過濾器須要的信息。以下:

 1 public class QueryParamPreFilter extends ZuulFilter {
 2     @Override
 3     public int filterOrder() {
 4         return PRE_DECORATION_FILTER_ORDER - 1; // run before PreDecoration
 5     }
 6 
 7     @Override
 8     public String filterType() {
 9         return PRE_TYPE;
10     }
11 
12     @Override
13     public boolean shouldFilter() {
14         RequestContext ctx = RequestContext.getCurrentContext();
15         return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded
16                 && !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId
17     }
18     @Override
19     public Object run() {
20         RequestContext ctx = RequestContext.getCurrentContext();
21         HttpServletRequest request = ctx.getRequest();
22         if (request.getParameter("sample") != null) {
23             // put the serviceId in `RequestContext`
24             ctx.put(SERVICE_ID_KEY, request.getParameter("foo"));
25         }
26         return null;
27     }
28 }

上面的過濾器使用sample請求參數填充SERVICE_ID_KEY。實際上不該該作這種直接映射,Service ID應該從sample的值中查找。

如今,SERVICE_ID_KEY已經被填充,因此PreDecorationFilter將不會執行,RibbonRoutingFilter會執行。

注意:若是想路由到一個完整的URL,調用ctx.setRouteHost(url)。

要修改路由過濾器轉發到的路徑,請設置REQUEST_URI_KEY。

How to Write a Route Filter

Route filters在pre filters後執行。它轉發請求到其餘服務。這裏的大部分工做是將請求和響應數據轉換到客戶機所需的模型。

 1 public class OkHttpRoutingFilter extends ZuulFilter {
 2     @Autowired
 3     private ProxyRequestHelper helper;
 4 
 5     @Override
 6     public String filterType() {
 7         return ROUTE_TYPE;
 8     }
 9 
10     @Override
11     public int filterOrder() {
12         return SIMPLE_HOST_ROUTING_FILTER_ORDER - 1;
13     }
14 
15     @Override
16     public boolean shouldFilter() {
17         return RequestContext.getCurrentContext().getRouteHost() != null
18                 && RequestContext.getCurrentContext().sendZuulResponse();
19     }
20 
21     @Override
22     public Object run() {
23         OkHttpClient httpClient = new OkHttpClient.Builder()
24                 // customize
25                 .build();
26 
27         RequestContext context = RequestContext.getCurrentContext();
28         HttpServletRequest request = context.getRequest();
29 
30         String method = request.getMethod();
31 
32         String uri = this.helper.buildZuulRequestURI(request);
33 
34         Headers.Builder headers = new Headers.Builder();
35         Enumeration<String> headerNames = request.getHeaderNames();
36         while (headerNames.hasMoreElements()) {
37             String name = headerNames.nextElement();
38             Enumeration<String> values = request.getHeaders(name);
39 
40             while (values.hasMoreElements()) {
41                 String value = values.nextElement();
42                 headers.add(name, value);
43             }
44         }
45 
46         InputStream inputStream = request.getInputStream();
47 
48         RequestBody requestBody = null;
49         if (inputStream != null && HttpMethod.permitsRequestBody(method)) {
50             MediaType mediaType = null;
51             if (headers.get("Content-Type") != null) {
52                 mediaType = MediaType.parse(headers.get("Content-Type"));
53             }
54             requestBody = RequestBody.create(mediaType, StreamUtils.copyToByteArray(inputStream));
55         }
56 
57         Request.Builder builder = new Request.Builder()
58                 .headers(headers.build())
59                 .url(uri)
60                 .method(method, requestBody);
61 
62         Response response = httpClient.newCall(builder.build()).execute();
63 
64         LinkedMultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<>();
65 
66         for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {
67             responseHeaders.put(entry.getKey(), entry.getValue());
68         }
69 
70         this.helper.setResponse(response.code(), response.body().byteStream(),
71                 responseHeaders);
72         context.setRouteHost(null); // prevent SimpleHostRoutingFilter from running
73         return null;
74     }
75 }

上面的過濾器將Servlet請求信息轉換到OkHttp3請求信息中,而且發送一個HTTP請求,而後將OkHttp3響應信息轉換到Servlet響應信息中。

How to Write a Post Filter

Post filters主要是用來修改響應。下面的例子中在響應頭中添加一個X-Sample頭部而且設置爲UUID。

 1 public class AddResponseHeaderFilter extends ZuulFilter {
 2     @Override
 3     public String filterType() {
 4         return POST_TYPE;
 5     }
 6 
 7     @Override
 8     public int filterOrder() {
 9         return SEND_RESPONSE_FILTER_ORDER - 1;
10     }
11 
12     @Override
13     public boolean shouldFilter() {
14         return true;
15     }
16 
17     @Override
18     public Object run() {
19         RequestContext context = RequestContext.getCurrentContext();
20         HttpServletResponse servletResponse = context.getResponse();
21         servletResponse.addHeader("X-Sample", UUID.randomUUID().toString());
22         return null;
23     }
24 }

注意:其餘操做,好比轉換響應體,則要複雜得多,計算量也大得多。

1.16.7 How Zuul Errors Work(Zuul錯誤)

Zuul過濾器的生命週期的任何階段出現異常,error過濾器將會執行。當RequestContext.getThrowable()不爲null時,SendErrorFilter將會執行。它而後在請求中設置javax.servlet.error.*屬性,

而後將請求轉發到spring boot的錯誤頁面。

1.16.8 Zuul Eager Application Context Loading

Zuul內部使用Ribbon來請求遠程URL。默認,Ribbon客戶端在第一次被使用時才被Spring Cloud加載。能夠經過下面的配置來改變默認行爲。它會在啓動時初始化Ribbon相關的上下文。

application.yml. 

1 zuul:
2   ribbon:
3     eager-load:
4       enabled: true

1.17 Retrying Failed Requests(重試失敗請求)

Spring Cloud Netflix提供了許多發送HTTP請求的方法。你可使用RestTemplate, Ribbon, 或者 Feign。不管怎麼選擇建立HTTP請求,都有可能請求失敗。

當請求失敗時,你可能想要請求自動重試。當使用Sping Cloud Netflix時,你須要添加Spring Retry到classpath上。這樣RestTemplates, Feign, 和 Zuul會在請求失敗時

自動重試。

1.17.1 BackOff Policies(補償政策)

默認,在使用重試機制時是沒有補償政策的。若是你想配置一個補償政策,則須要建立一個LoadBalancedBackOffPolicyFactory類型的bean。它會爲指定的服務建立一個BackOffPolicy。以下:

 1 @Configuration
 2 public class MyConfiguration {
 3     @Bean
 4     LoadBalancedBackOffPolicyFactory backOffPolicyFactory() {
 5         return new LoadBalancedBackOffPolicyFactory() {
 6             @Override
 7             public BackOffPolicy createBackOffPolicy(String service) {
 8                 return new ExponentialBackOffPolicy();
 9             }
10         };
11     }
12 }

1.17.2 Configuration(配置)

當你使用Ribbon和Spring Retry時,你能夠經過配置某些Ribbon屬性來控制重試功能。例如,client.ribbon.MaxAutoRetriesclient.ribbon.MaxAutoRetriesNextServer, 和 client.ribbon.OkToRetryOnAllOperations。

注意:開啓client.ribbon.OkToRetryOnAllOperations的話將會包括重試POST請求,這樣會對服務器資源有些影響,由於它會緩存請求體數據。

另外,你可能但願對某些響應中的狀態碼進行重試請求。可經過設置clientName.ribbon.retryableStatusCodes。

1 clientName:
2   ribbon:
3     retryableStatusCodes: 404,502

你也能夠建立一個LoadBalancedRetryPolicy類型的bean,而且實現retryableStatusCode方法。

1.17.2.1 Zuul

能夠經過zuul.retryable設置爲false來關閉zuul中的重試機制。也能夠設置zuul.routes.routename.retryable爲false來關閉某個指定路由上的重試機制。

1.18 HTTP Clients

Spring Cloud Netflix 會爲Ribbon, Feign, 和 Zuul自動建立HTTP客戶端。你也能夠提供你本身的HTTP客戶端。若是您使用的是Apache Http Cient,那麼能夠建立類型爲ClosableHttpClient的bean,或者若是您使用的是OK Http,則能夠建立OkHttpClient。

注意:當您建立本身的HTTP客戶機時,您還負責爲這些客戶機實現正確的鏈接管理策略。若是沒作好會致使資源管理問題。

相關文章
相關標籤/搜索