spring cloud連載第三篇補充之Zuul


由於Zuul的內容較多所以單獨列出一篇來講。全是干貨,如果學到東西的,動動小手給點個推薦^_^  謝謝!

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

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

zuul有下列用途:

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

 

注意:

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

2)Hystrix對所有理由的默認隔離模式是SEMAPHORE,可以通過zuul.ribbonIsolationStrategy改為THREAD。

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中執行。所以當斷路器打開時,代理將不會重試連接后端服務。

注意:Zuul starter不包含服務發現客戶端,所以想要使用服務發現功能,需要提供一個服務發現客戶端(比如Eureka)。

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

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客戶機時,您還負責為這些客戶機實現正確的連接管理策略。如果沒做好會導致資源管理問題。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM