轉載請表明出處 https://blog.csdn.net/Amor_Leo/article/details/87885822 謝謝
Spring Cloud Zuul
Spring Cloud Zuul概述
當我們在使用微服務的時候,完成一個業務可能需要同時調用多個微服務,則需要分別請求多個服務接口,首先客戶端需要請求不同的微服務,客戶端的複雜性增大認證複雜,每個微服務都需要自己獨立的認證方式,某些情況下會存在一些問題,例如跨域問題,每個服務存在的協議可能不同。
Spring Cloud Zuul是基於Netflix Zuul實現的API網關,他可以和Eureka,Ribbon,Hystrix等組件配合使用。他除了Zuul Core還整合了actuator,hystrix,ribbon,提供動態路由,監控,彈性,安全等的邊緣服務。Zuul組件的核心是一系列的過濾器,這些過濾器可以完成以下功能:
- 身份認證和安全: 識別每一個資源的驗證要求,並拒絕那些不符的請求
- 審查與監控:
- 動態路由:動態將請求路由到不同後端集羣
- 壓力測試:逐漸增加指向集羣的流量,以瞭解性能
- 負載分配:爲每一種負載類型分配對應容量,並棄用超出限定值的請求
- 靜態響應處理:邊緣位置進行響應,避免轉發到內部集羣
- 多區域彈性:跨域AWS Region進行請求路由,旨在實現ELB(ElasticLoad Balancing)使用多樣化
Zuul使用的默認是Apache的HTTP Client,也可以使用Rest Client或者okhttp3.OkHttpClient。使用Rest Client可以設置ribbon.restclient.enabled=true,使用okhttp3.OkHttpClient可以設置ribbon.okhttp.enabled=true。
網關的優勢
- 阻止將內部的敏感信息暴露給外部的客戶端: API網關通過提供微服務綁定和解綁的能力來將外部的公開API與內部微服務API分離開。這樣就具備重構和裁切微服務而不會影響外部綁定的客戶端的能力。它同時對客戶端隱藏了服務發現和服務版本這些細節,因爲它對所有微服務提供單一的訪問點。
- 爲服務增加額外的安全層: API網關通過提供額外的安全層幫助阻止大規模攻擊。這些攻擊包括SQL注入,XML解析漏洞和DDOS攻擊。
- 可以支持混合通訊協議: 不同於外部API一般使用HTTP或REST,內部微服務可以從使用不用通訊協議中收穫益處。這些協議可以是ProtoBuf或AMQP,甚至是諸如SOAP,JSON-RPC或者XML-RPC這樣的系統集成協議。API網關可以對這些協議提供統一的外部REST接口,這就允許開發團隊挑選一款最適合於內部架構的協議。
- 降低微服務的複雜度: 微服務中有一些常見的要點,諸如使用API令牌進行授權,訪問控制和調用頻次限制。這每個點都需要每個服務區增加額外的時間去實現它們。API網關將這些要點從你的代碼中提取出來,允許你的服務只關注於它們需要關注的任務。
- 微服務模擬和虛擬化: 隨着微服務API從外部API中分離出來,你可以模擬你的服務去做設計驗證或者很方便的做集成測試。
Spring Cloud Zuul搭建
基本
- pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- Application類上
@EnableZuulProxy //打開zuul
- yml
- 簡單 請求按默認的方式處理(即通過http://zuulHost:zuulPort/服務名/請求路徑)
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream
- 自定義指定微服務的訪問路徑
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: provider-server: /provider/** consumer-server: /consumer/** management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream
- 忽略指定微服務,代理其他微服務
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: ignored-services: provider-server,consumer-server management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream
- 忽略所有微服務,路由指定微服務
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: ignored-services: '*' # 使用'*'可忽略所有微服務 routes: provider-server: /provider/** consumer-server: /consumer/** management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream
- 同時指定微服務的serviceId和對應路徑
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: provider-route: # 該配置方式中,provider-route只是給路由一個名稱,可以任意起名。 service-id: provider-server path: /provider/** # service-id對應的路徑 consumer-route: # 該配置方式中,consumer-route只是給路由一個名稱,可以任意起名。 service-id: consumer-server path: /consumer/** # service-id對應的路徑 management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream
- 同時指定path和url
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: provider-route: # 該配置方式中,provider-route只是給路由一個名稱,可以任意起名。 url: http://localhost:8000/ # 指定的url path: /provider/** # url對應的路徑。 consumer-route: # 該配置方式中,consumer-route只是給路由一個名稱,可以任意起名。 url: http://localhost:8010/ # 指定的url path: /consumer/** # url對應的路徑。 management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream
- 同時指定path和URL,並且不破壞Zuul的Hystrix、Ribbon特性
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: provider-route: # 該配置方式中,provider-route只是給路由一個名稱,可以任意起名。 path: /provider/** service-id: provider-server ribbon: eureka: enabled: false # 禁用掉ribbon的eureka使用 provider-server: ribbon: listOfServers: localhost:8000,localhost:8001 management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream
- 爲Zuul添加映射前綴1
strip-prefix是剝離前綴的意思,設置爲false就是不剝離前綴,Zuul默認是剝離前綴的。
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: prefix: /api strip-prefix: false routes: provider-server: /provider/** logging: level: com.netflix: DEBUG management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream # 訪問Zuul的/api/provider-server/1路徑,請求將會被轉發到provider-server的/api/1,可查看日誌打印,有助於理解。
- 爲Zuul添加映射前綴2
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: provider-server: path: /provider/** strip-prefix: false logging: level: com.netflix: DEBUG management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream # 這樣訪問Zuul的/provider/1路徑,請求將會被轉發到provider-server的/provider/1,可查看日誌打印,有助於理解。
- 忽略某些路徑
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: ignoredPatterns: /**/admin/** # 忽略所有包括/admin/的路徑 routes: provider-server: /provider/** management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream
- 本地轉發
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: route-name: path: /path-a/** url: forward:/path-b management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream
- 使用正則表達式指定zuul的路由匹配規則
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream
@SpringBootApplication @EnableZuulProxy public class ZuulApplication { @Bean public PatternServiceRouteMapper serviceRouteMapper() { // 調用構造函數PatternServiceRouteMapper(String servicePattern, String routePattern) // servicePattern指定微服務的正則 // routePattern指定路由正則 return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); } public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); } }
- Zuul的安全與Heard
如果不希望敏感Headers向下遊泄漏到外部服務器,可以在路由配置中指定忽略的Headers列表
sensitive-headers是黑名單,默認不爲空,因此要使Zuul發送所有Headers(“忽略”的Headers除外),您必須將其明確設置爲空。 如果要將cookie或授權Headers傳遞給後端,則必須執行此操作。server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: provider-route: # 該配置方式中,provider-route只是給路由一個名稱,可以任意起名。 url: http://localhost:8000/ # 指定的url path: /provider/** # url對應的路徑。 sensitive-headers: Cookie,Set-Cookie,Authorization # 過濾 management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream
也可以通過設置zuul.sensitive-headers來全局設置敏感的Headers。 如果在路由上設置了sensitive-headers,則會覆蓋全局sensitive-headers設置。server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: provider-route: # 該配置方式中,provider-route只是給路由一個名稱,可以任意起名。 url: http://localhost:8000/ # 指定的url path: /provider/** # url對應的路徑。 sensitive-headers: # 不過濾,則須顯式設爲空。 management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream
忽略某些headserver: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: sensitive-headers: # 全局不過濾 routes: provider-route: # 該配置方式中,provider-route只是給路由一個名稱,可以任意起名。 url: http://localhost:8000/ # 指定的url path: /provider/** # url對應的路徑。 management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream
默認情況下zuul.ignored-headers爲空值,但如果是Spring Security在項目的classpath中,那麼zuul.ignored-headers默認爲Pragma,Cache-Control,X-Frame-Options,X-Content-Type-Options,X-XSS-Protection,Expires。如果需要使用下游微服務的Spring Security的Header頭部信息,就可以設置zuul.ignoreSecurity-headers=false,保留Spring Security的安全頭部信息和代理的頭部信息。當然,我們也可以不設置這個值,僅僅獲取來自代理的頭部信息。server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: ignored-headers: header1,header2 # header1,header2不會傳播到其他服務 management: security: enabled: false endpoints: web: exposure: include: "*" #因爲springboot2.1.必須加上,支持訪問/actuator/hystrix.stream
- 通過Zuul上傳文件
對於小文件(1M以內)上傳,即可正常上傳,對於大文件(10M以上)上傳,需要爲上傳路徑添加/zuul前綴,也可以使用zuul.servlet-path自定義前綴.如果zuul使用了Ribbon做負載均衡,那麼對於超大的文件需要提升超時設置。
文件上傳微服務:
pom
yml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
Controllerserver: port: 8050 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true spring: application: name: file-upload-server http: multipart: max-file-size: 2000Mb # Max file size,單個文件大小,默認1M max-request-size: 2500Mb # Max request size,總上傳數據的大小,默認10M
Application類上@Controller public class FileUploadController { /** * 上傳文件 * 測試方法: * 有界面的測試:http://localhost:8050/index.html * 使用命令:curl -F "file=@文件全名" localhost:8050/upload * ps.該示例比較簡單,沒有做IO異常、文件大小、文件非空等處理 * @param file 待上傳的文件 * @return 文件在服務器上的絕對路徑 * @throws IOException IO異常 */ @RequestMapping(value = "/upload", method = RequestMethod.POST) public @ResponseBody String handleFileUpload(@RequestParam(value = "file", required = true) MultipartFile file) throws IOException { byte[] bytes = file.getBytes(); File fileToSave = new File(file.getOriginalFilename()); FileCopyUtils.copy(bytes, fileToSave); return fileToSave.getAbsolutePath(); } }
Zuul-Server:@EnableEurekaClient
pom
yml<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
Application類上server: port: 8040 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true spring: application: name: gateway-zuul-server # 上傳大文件得將超時時間設置長一些,否則會報超時異常 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000 ribbon: ConnectTimeout: 3000 ReadTimeout: 60000
@EnableZuulProxy
Zuul的回退
- pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- yml
spring:
application:
name: gateway-zuul-fallback-server
server:
port: 8060
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
zuul:
routes:
provide-rote:
service-id: provider-server
path: /provide/**
stripPrefix: false
- Application類上
@EnableZuulProxy
- 回退類
@Component
public class MyZuulFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
//api服務id,如果需要所有調用都支持回退,則return "*"或return null
return "provide-server";
// return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse(){
@Override
public InputStream getBody() throws IOException {
JSONObject r = new JSONObject();
r.put("state", "9999");
r.put("msg", "系統錯誤,請求失敗");
// 響應體
return new ByteArrayInputStream(r.toJSONString().getBytes("UTF-8"));
}
@Override
public HttpHeaders getHeaders() {
//headers設定
HttpHeaders headers = new HttpHeaders();
//和body中的內容編碼一致,否則容易亂碼
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return headers;
}
/**
* 網關向api服務請求是失敗了,但是消費者客戶端向網關發起的請求是OK的,
* 不應該把api的404,500等問題拋給客戶端
* 網關和api服務集羣對於客戶端來說是黑盒子
*/
@Override
public HttpStatus getStatusCode() throws IOException {
// fallback時的狀態碼
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
// 數字類型的狀態碼,本例返回的其實就是200,詳見HttpStatus
return HttpStatus.OK.value();
}
@Override
public String getStatusText() throws IOException {
// 狀態文本,本例返回的其實就是OK,詳見HttpStatus
return HttpStatus.OK.getReasonPhrase();
}
@Override
public void close() {
}
};
}
}
Zuul的過濾
- pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- yml
spring:
application:
name: gateway-zuul-filter-server
server:
port: 8060
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
zuul:
routes:
provide-rote:
service-id: provider-server
path: /provide/**
stripPrefix: false
單個過濾器
- Application類上
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
@Bean
public PreZuulFilter preZuulFilter() {
return new PreZuulFilter();
}
}
- Filter類
public class PreZuulFilter extends ZuulFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(PreZuulFilter.class);
/**
* @Description: 過濾器類型 順序: pre ->routing -> post ,以上3個順序出現異常時都可以觸發error類型的filter
* pre 代表在路由代理之前執行
* route 代表代理的時候執行
* error 代表出現錯的時候執行
* post 代表在route 或者是 error 執行完成後執行
* @method: filterType
* @Param:
* @return: java.lang.String
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* @Description: 執行順序 同filterType類型中,order值越大,優先級越低,order值越小,優先級越高
* filter 爲鏈式過濾器,多個filter按順序執行
* @method: filterOrder
* @Param:
* @return: int
*/
@Override
public int filterOrder() {
return 0;
}
/**
* @Description: 是否應該執行該過濾器,如果是false,則不執行該filter
* @method: shouldFilter
* @Param:
* @return: boolean
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* @Description: 過濾的邏輯,執行業務操作
* @method: run
* @Param:
* @return: java.lang.Object
*/
@Override
public Object run() throws ZuulException {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
String host = request.getRemoteHost();
PreZuulFilter.LOGGER.info("請求的host:{}", host);
return null;
}
}
多個過濾器
- Application類上
@EnableZuulProxy
@SpringCloudApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
//http://192.168.0.1:8040/provide/find/1?prefilter01=true&prefilter02=true&post=true
@Bean
public TestPre01Filter testPre01Filter(){
return new TestPre01Filter();
}
@Bean
public TestPre02Filter testPre02Filter(){
return new TestPre02Filter();
}
@Bean
public TestPostFilter testPostFilter(){
return new TestPostFilter();
}
}
- Filter類
/**
* 第一個pre類型的filter,prefilter01=true才能通過
*/
public class TestPre01Filter extends ZuulFilter {
/**
* 是否應該執行該過濾器,如果是false,則不執行該filter
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 過濾器類型
* 順序: pre ->routing -> post ,以上3個順序出現異常時都可以觸發error類型的filter
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 同filterType類型中,order值越大,優先級越低
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 執行業務操作,可執行sql,nosql等業務
*/
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String prefilter01 = request.getParameter("prefilter01");
System.out.println("執行pre01Filter .....prefilter01=" + prefilter01 );
//如果用戶名和密碼都正確,則繼續執行下一個filter
if("true".equals(prefilter01) ){
//會進行路由,也就是會調用api服務提供者
ctx.setSendZuulResponse(true);
ctx.setResponseStatusCode(200);
//可以把一些值放到ctx中,便於後面的filter獲取使用
ctx.set("isOK",true);
}else{
//不需要進行路由,也就是不會調用api服務提供者
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
//可以把一些值放到ctx中,便於後面的filter獲取使用
ctx.set("isOK",false);
//返回內容給客戶端,返回錯誤內容
ctx.setResponseBody("{\"result\":\"pre01Filter auth not correct!\"}");
}
return null;
}
}
/**
* prefilter02 校驗 prefilter02=true才能通過
*/
public class TestPre02Filter extends ZuulFilter {
/**
* 是否應該執行該過濾器,如果是false,則不執行該filter
*/
@Override
public boolean shouldFilter() {
//上一個filter設置該值
return RequestContext.getCurrentContext().getBoolean("isOK");
}
/**
* 過濾器類型
* 順序: pre ->routing -> post ,以上3個順序出現異常時都可以觸發error類型的filter
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 同filterType類型中,order值越大,優先級越低
*/
@Override
public int filterOrder() {
return 2;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String prefilter02 = request.getParameter("prefilter02");
System.out.println("執行pre02Filter .....prefilter02=" + prefilter02 );
//如果用戶名和密碼都正確,則繼續執行下一個filter
if("true".equals(prefilter02) ){
ctx.setSendZuulResponse(true);//會進行路由,也就是會調用api服務提供者
ctx.setResponseStatusCode(200);
ctx.set("isOK",true);//可以把一些值放到ctx中,便於後面的filter獲取使用
}else{
ctx.setSendZuulResponse(false);//不需要進行路由,也就是不會調用api服務提供者
ctx.setResponseStatusCode(401);
ctx.set("isOK",false);//可以把一些值放到ctx中,便於後面的filter獲取使用
//返回內容給客戶端
ctx.setResponseBody("{\"result\":\"pre02Filter auth not correct!\"}");// 返回錯誤內容
}
return null;
}
}
/**
* post類型的filter,post=true才能通過
*/
public class TestPostFilter extends ZuulFilter {
/**
* 是否應該執行該過濾器,如果是false,則不執行該filter
*/
@Override
public boolean shouldFilter() {
//上一個filter設置該值
return RequestContext.getCurrentContext().getBoolean("isOK");
}
/**
* 過濾器類型
* 順序: pre ->routing -> post ,以上3個順序出現異常時都可以觸發error類型的filter
*/
@Override
public String filterType() {
return FilterConstants.ROUTE_TYPE;
}
/**
* 同filterType類型中,order值越大,優先級越低
*/
@Override
public int filterOrder() {
return 3;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String post = request.getParameter("post");
System.out.println("執行postFilter .....post=" + post );
//如果用戶名和密碼都正確,則繼續執行下一個filter
if("true".equals(post) ){
//會進行路由,也就是會調用api服務提供者
ctx.setSendZuulResponse(true);
ctx.setResponseStatusCode(200);
//可以把一些值放到ctx中,便於後面的filter獲取使用
ctx.set("isOK",true);
}else{
//不需要進行路由,也就是不會調用api服務提供者
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
//可以把一些值放到ctx中,便於後面的filter獲取使用
ctx.set("isOK",false);
//返回內容給客戶端,返回錯誤內容
ctx.setResponseBody("{\"result\":\"post auth not correct!\"}");
}
return null;
}
}
禁用Zuul Filters
默認會使用很多filters,可採用如下方式禁止
zuul.SendResponseFilter.post.disable=true
Zuul的重試
- pom
<!-- 服務發現 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- hystrix dashboard的支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!-- 服務網關 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!-- 重試機制,必須配,否則重試不生效 -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
- yml
spring:
application:
name: gateway-zuul-server
#啓動負載均衡的重試機制,默認false
cloud:
loadbalancer:
retry:
enabled: true
server:
port: 8040
eureka:
client:
healthcheck:
enabled: true #開啓健康檢查
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
#Hystrix是否啓用超時時間
hystrix:
command:
default:
execution:
timeout:
enabled: true
#Hystrix斷路器的超時時間,默認是1s,斷路器的超時時間需要大於ribbon的超時時間,不然不會觸發重試。
isolation:
thread:
timeoutInMilliseconds: 2000
#hystrix dashboard的信息收集頻率,默認500毫秒
stream:
dashboard:
intervalInMilliseconds: 5000
#ribbon請求連接的超時時間
ribbon:
ConnectTimeout: 250
#請求處理的超時時間
ReadTimeout: 1000
#對所有請求操作都進行重試
OkToRetryOnAllOperations: true
#對當前服務的重試次數(第一次分配給9082的時候,如果404,則再重試MaxAutoRetries次,如果還是404,則切換到其他服務MaxAutoRetriesNextServer決定)
MaxAutoRetries: 0
#切換服務的次數(比如本次請求分配給9082處理,發現404,則切換分配給9081處理,如果還是404,則返回404給客戶端)
MaxAutoRetriesNextServer: 1
zuul:
ignoredServices: '*' #只有下面配置的服務會路由,其他的不會路由
routes:
provide-api:
path: /provide-api/**
serviceId: provide-server #eureka中對應的服務名稱
sensitiveHeaders:
retryable: true #重試,默認false
stripPrefix: false
consumer-api:
path: /consumer-api/**
serviceId: consumer-server #eureka中對應的服務名稱
sensitiveHeaders:
retryable: true #重試,默認false
stripPrefix: false
#ribbo負載均衡策略配置,默認是依次輪詢,可配置隨機
#api-user-server.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
logging:
level:
com.netflix: DEBUG
Zuul的限流
- pom
<!-- 服務發現 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 服務網關 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- yml
# redis
spring:
redis:
port: 6379
host: 127.0.0.1
timeout: 2000
zuul:
ratelimit:
# key-prefix: your-prefix #對應用來標識請求的key的前綴
enabled: true
repository: REDIS #對應存儲類型(用來存儲統計信息)
behind-proxy: true #代理之後
default-policy: #可選 - 針對所有的路由配置的策略,除非特別配置了policies
limit: 10 #可選 - 每個刷新時間窗口對應的請求數量限制
quota: 1000 #可選- 每個刷新時間窗口對應的請求時間限制(秒)
refresh-interval: 60 # 刷新時間窗口的時間,默認值 (秒)
type: #可選 限流方式
- user
- origin
- url
policies:
provide-server: #特定的路由
limit: 10 #可選- 每個刷新時間窗口對應的請求數量限制
quota: 1000 #可選- 每個刷新時間窗口對應的請求時間限制(秒)
refresh-interval: 60 # 刷新時間窗口的時間,默認值 (秒)
type: #可選 限流方式
- user
- origin
- url
- limit 單位時間內允許訪問的次數
- quota 單位時間內允許訪問的總時間(單位時間窗口期內,所有的請求的總時間不能超過這個時間限制)
- refresh-interval 單位時間設置
- type 限流類型type 限流類型
- url類型的限流就是通過請求路徑區分
- origin是通過客戶端IP地址區分
- user是通過登錄用戶名進行區分,也包括匿名用戶
- default-policy 可選 - 針對所有的路由配置的策略,除非特別配置了policy-list
- policies 對特定的服務id進行限流; 缺點: 可以配置多個url,但是這些url都使用一個限流配置,沒有辦法指定每個url的限流配置
- policy-list 對特定的服務id進行限流; 優點: 可以爲某個服務id的每個url 指定不同的限流配置
- 自定義Key策略
如果希望自己控制key的策略,可以通過自定義RateLimitKeyGenerator的實現來增加自己的策略邏輯。RateLimitKeyGenerator的實現:
@Bean
public RateLimitKeyGenerator rateLimitKeyGenerator(RateLimitProperties properties,RateLimitUtils rateLimitUtils) {
return new DefaultRateLimitKeyGenerator(properties, rateLimitUtils) {
@Override
public String key(final HttpServletRequest request,final Route route,final RateLimitProperties.Policy policy) {
final List<Type> types = policy.getType();
final StringJoiner joiner = new StringJoiner(":");
joiner.add(properties.getKeyPrefix());
if (route != null) {
joiner.add(route.getId());
}
if (!types.isEmpty()) {
if (types.contains(Type.URL) && route != null) {
joiner.add(route.getPath());
}
if (types.contains(Type.ORIGIN)) {
joiner.add(getRemoteAddr(request));
}
// 這個結合文末總結。
if (types.contains(Type.USER)) {
joiner.add(request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : ANONYMOUS_USER);
}
}
return joiner.toString();
//return super.key(request, route, policy) + "_" + request.getMethod();
}
};
}
- 個性化錯誤處理
@Bean
public RateLimiterErrorHandler rateLimitErrorHandler() {
return new DefaultRateLimiterErrorHandler() {
@Override
public void handleSaveError(String key, Exception e) {
// implementation
}
@Override
public void handleFetchError(String key, Exception e) {
// implementation
}
@Override
public void handleError(String msg, Exception e) {
// implementation
}
};
}
Zuul跨域
- 在Application類裏添加
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // 允許cookies跨域
config.addAllowedOrigin("*");// #允許向該服務器提交請求的URI,*表示全部允許,在SpringMVC中,如果設成*,會自動轉成當前請求頭中的Origin
config.addAllowedHeader("*");// #允許訪問的頭信息,*表示全部
config.setMaxAge(18000L);// 預檢請求的緩存時間(秒),即在這個時間段裏,對於相同的跨域請求不會再預檢了
config.addAllowedMethod("OPTIONS");// 允許提交請求的方法,*表示全部允許
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");// 允許Get的請求方法
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}