zuul 是netflix開源的一個API Gateway 服務器, 本質上是一個web servlet應用。
zuul 在雲平臺上提供動態路由,監控,彈性,安全等邊緣服務的框架。Zuul 至關因而設備和 Netflix 流應用的 Web 網站後端全部請求的前門。
zuul的例子能夠參考 netflix 在github上的 simple webapp,能夠按照netflix 在github wiki 上文檔說明來進行使用(https://github.com/Netflix/zuul/wiki)。前端
zuul的核心是一系列的filters, 其做用能夠類比Servlet框架的Filter,或者AOP。
zuul把Request route到 用戶處理邏輯 的過程當中,這些filter參與一些過濾處理,好比Authentication,Load Shedding等。java
Zuul提供了一個框架,能夠對過濾器進行動態的加載,編譯,運行。git
Zuul的過濾器之間沒有直接的相互通訊,他們之間經過一個RequestContext的靜態類來進行數據傳遞的。RequestContext類中有ThreadLocal變量來記錄每一個Request所須要傳遞的數據。github
Zuul的過濾器是由Groovy寫成,這些過濾器文件被放在Zuul Server上的特定目錄下面。Zuul會按期輪詢這些目錄,修改過的過濾器會動態的加載到Zuul Server中以便過濾請求使用。web
下面有幾種標準的過濾器類型:
Zuul大部分功能都是經過過濾器來實現的。Zuul中定義了四種標準過濾器類型,這些過濾器類型對應於請求的典型生命週期。spring
內置的特殊過濾器apache
zuul還提供了一類特殊的過濾器,分別爲:StaticResponseFilter和SurgicalDebugFilter
StaticResponseFilter:StaticResponseFilter容許從Zuul自己生成響應,而不是將請求轉發到源。
SurgicalDebugFilter:SurgicalDebugFilter容許將特定請求路由到分隔的調試集羣或主機。後端
自定義的過濾器安全
除了默認的過濾器類型,Zuul還容許咱們建立自定義的過濾器類型。
例如,咱們能夠定製一種STATIC類型的過濾器,直接在Zuul中生成響應,而不將請求轉發到後端的微服務。服務器
Zuul請求的生命週期如圖,該圖詳細描述了各類類型的過濾器的執行順序。
Zuul能夠經過加載動態過濾機制,從而實現如下各項功能:
在上一節從零開始搭建spring-cloud(1) ----eureka中拷貝spring-cloud-eureka-server到這個項目中。
在上一節從零開始搭建spring-cloud(1) ----eureka中拷貝spring-cloud-eureka-provider-A-1到這個項目中。
新建項目spring-cloud-zuul,pom.xml內容以下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.vincent</groupId> <artifactId>spring-cloud-zuul</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.1.4.RELEASE</version> <scope>import</scope> <type>pom</type> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <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-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> </dependencies> </project>
咱們都知道,在zuul過濾器裏PRE_TYPE類型是在路由前執行的,因此我要給你們演示配置三個PRE_TYPE類型的過濾器,按照順序依次處理不一樣的業務。以及,三個PRE_TYPE類型過濾器中任意一個出現異常時他的下游業務應該怎麼處理。
咱們首先看一下項目的目錄結構:
各個Filter內容以下:
package com.vincent.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ERROR_TYPE; /** * @author vincent * @time 2019-06-23 16:15 */ @Component public class ErrorFilter extends ZuulFilter { @Override public String filterType() { return ERROR_TYPE; } @Override public int filterOrder() { return -1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); System.out.println("這是ErrorFilter"); return null; } }
package com.vincent.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE; /** * @author vincent * @time 2019-06-23 16:17 */ @Component public class FirstPreFilter extends ZuulFilter { @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { System.out.println("這是第一個自定義Zuul Filter!"); return null; } }
package com.vincent.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE; /** * @author vincent * @time 2019-06-23 16:18 */ @Component public class SecondPreFilter extends ZuulFilter { @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { return 2; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { System.out.println("這是SecondPreFilter!"); //從RequestContext獲取上下文 RequestContext ctx = RequestContext.getCurrentContext(); //從上下文獲取HttpServletRequest HttpServletRequest request = ctx.getRequest(); //從request嘗試獲取a參數值 String a = request.getParameter("a"); //若是a參數值爲空則進入此邏輯 if (null == a) { //對該請求禁止路由,也就是禁止訪問下游服務 ctx.setSendZuulResponse(false); //設定responseBody供PostFilter使用 ctx.setResponseBody("{\"status\":500,\"message\":\"a param is null\"}"); //logic-is-success保存於上下文,做爲同類型下游Filter的執行開關 ctx.set("logic-is-success", false); //到這裏此Filter邏輯結束 return null; } //設置避免報空 ctx.set("logic-is-success", true); return null; } }
package com.vincent.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE; /** * @author vincent * @time 2019-06-23 16:19 */ @Component public class ThirdPreFilter extends ZuulFilter { @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { return 3; } @Override public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); //從上下文獲取logic-is-success值,用於判斷此Filter是否執行 return (boolean)ctx.get("logic-is-success"); } @Override public Object run() throws ZuulException { System.out.println("這是ThirdPreFilter!"); //從RequestContext獲取上下文 RequestContext ctx = RequestContext.getCurrentContext(); //從上下文獲取HttpServletRequest HttpServletRequest request = ctx.getRequest(); //從request嘗試獲取b參數值 String b = request.getParameter("b"); //若是b參數值爲空則進入此邏輯 if (null == b) { //對該請求禁止路由,也就是禁止訪問下游服務 ctx.setSendZuulResponse(false); //設定responseBody供PostFilter使用 ctx.setResponseBody("{\"status\":500,\"message\":\"b param is null\"}"); //logic-is-success保存於上下文,做爲同類型下游Filter的執行開關,假定後續還有自定義Filter當設置此值 ctx.set("logic-is-success", false); //到這裏此Filter邏輯結束 return null; } return null; } }
package com.vincent.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.POST_TYPE; /** * @author vincent * @time 2019-06-23 16:20 */ public class PostFilter extends ZuulFilter { @Override public String filterType() { return POST_TYPE; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { System.out.println("這是PostFilter!"); //從RequestContext獲取上下文 RequestContext ctx = RequestContext.getCurrentContext(); //處理返回中文亂碼 ctx.getResponse().setCharacterEncoding("GBK"); //獲取上下文中保存的responseBody String responseBody = ctx.getResponseBody(); //若是responseBody不爲空,則說明流程有異常發生 if (null != responseBody) { //設定返回狀態碼 ctx.setResponseStatusCode(500); //替換響應報文 ctx.setResponseBody(responseBody); } return null; } }
新建App.java,內容以下:
@SpringBootApplication @EnableZuulProxy public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
咱們開始配置application.properties,內容以下:
eureka.client.service-url.defaultZone=http://127.0.0.1:8080/eureka/ spring.application.name=service-zuul zuul.routes.users.url=http://localhost:8081/ zuul.routes.users.path=/** zuul.ignored-headers=Access-Controller-Allow-Credentials, Access-Control-Allow-Origin zuul.host.connect-timeout-millis=10000000 zuul.host.socket-timeout-millis=10000000 server.port=8082
這裏zuul的匹配規則是經過url進行匹配。
zuul.host.connect-timeout-millis=10000000
zuul.host.socket-timeout-millis=10000000這兩句的做用是防止服務提供方返回響應的時間過長
先添加參數訪問微服務
控制檯輸出結果以下:
這是第一個自定義Zuul Filter! 這是SecondPreFilter! 這是PostFilter!
添加參數a訪問,
控制檯輸出結果以下:
這是第一個自定義Zuul Filter! 這是SecondPreFilter! 這是ThirdPreFilter! 這是PostFilter!
添加參數a和參數b訪問
控制檯輸出結果以下:
2019-06-23 17:29:44.348 INFO 3363 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration 這是第一個自定義Zuul Filter! 這是SecondPreFilter! 這是ThirdPreFilter! 這是PostFilter!
zuul.routes.serviceName.path=/exampleService/** zuul.routes.serviceName.serviceId=serviceId
注:
zuul.routes 是固定的
serviceName 是能夠隨便寫的,但最好根據要路由的服務取
serviceId 是 eureka 服務註冊時的名稱
exampleService 是前端請求某個微服務的一個公共的路徑名,如/users
而微服務在 Controller層的 RequestMapping 註解中能夠不包含/users
例如本項目中的配置以下:
zuul.routes.myservice.path=/** zuul.routes.myservice.service-id=service-provider-A
zuul.routes.serviceName.path=/exampleService/** zuul.routes.serviceName.url=http://127.0.0.1:8080/
若是項目還沒有使用eureka,能夠採用了第二種轉發規則。這種轉發有不少好處,最大的好處就是能夠很好地過渡到Spring Cloud,使用Zuul能夠直接HTTP調用,方便不少。
例如本項目中的URL規則轉發以下:
zuul.routes.users.url=http://localhost:8081/ zuul.routes.users.path=/**