Spring Cloud Zuul 除了能夠實現請求的路由功能,還有一個重要的功能就是過濾器。Zuul 的路由功能讓全部的微服務提供的接口有統一的網關入口,但並非全部的接口都是對外徹底開發的,它們的訪問權限通常都有必定的限制。那咱們能夠在每一個服務都加上對應的校驗和權限鑑定,那這些一般都是用過濾器或攔截器實現的,並且一個系統的各個服務的校驗也大都是類似,這些類似的校驗邏輯打碼在每一個服務都會有一份,不只冗餘且維護麻煩。更好的辦法就是在請求的最前端統一去作這樣的事情,而統一的 API 服務網關入口就是合適的選擇前端
Zuul 能夠經過定義過濾器來實現請求的攔截和過濾,而它自己的大部分功能也是經過過濾器實現的java
在 Zuul 中自定義過濾器須要繼承抽象類 ZuulFilter
,須要實現如下4個方法:git
String filterType(); // 過濾器類型 int filterOrder(); // 執行順序,數值越小優先級越高 boolean shouldFilter(); // 執行過濾器的條件 Object run() throws ZuulException; // 具體的過濾操做
從名字咱們也能知道各個方法的做用,下面來了解下過濾器類型和過濾器的生命週期,以及自定義過濾器的使用github
Zuul 定義了4種不一樣的過濾器類型,對應着請求的典型生命週期web
PRE
: 在請求被路由以前執行。能夠用於請求身份驗證、選擇源服務器和記錄調試信息ROURING
: 該過濾器將請求路由到服務。用於構建和發送給微服務的請求(使用 Apache HttpClient 或 Netflix Ribbon 請求服務)POST
: 在請求被路由到服務以後執行。能夠用於向響應添加標準的 HTTP Header、收集統計數據和指標,以及將響應從源服務發送到客戶端ERROR
: 該過濾器在其餘階段發生錯誤時執行除了默認的過濾器流,Zuul 還容許咱們建立自定義過濾器類型並顯式地執行它們。例如,咱們能夠自定義一個 STATIC
類型的過濾器,它在Zuul 中生成響應,而不是將請求轉發到後端的服務spring
下面是 Zuul 的生命週期圖,描述着各類類型的過濾器的執行順序(圖片來源於 Zuul Wiki)json
Spring Cloud Zuul 做爲服務網關的大部分功能都是經過過濾器實現的,它在請求的各個階段實現了一系列的過濾器,在 Spring Cloud Zuul 網關服務啓動時自動加載和啓用。實現的這些過濾器是在 spring-cloud-netflix-zuul
模塊中的 org.springframework.cloud.netflix.zuul.filters
包下面後端
下面介紹部分過濾器的功能服務器
Pre filtersapp
filter | order | 說明 |
---|---|---|
ServletDetectionFilter |
-3 | 檢測請求是否經過 Spring 調度程序,即判斷請求是交由 Spring DispatcherServlet 處理,仍是 ZuulServlet 處理(主要是用於大文件上傳) |
Servlet30WrapperFilter |
-2 | 把原始 HttpServletRequest 包裝成 Servlet30RequestWrapper 對象 |
FormBodyWrapperFilter |
-1 | 解析表單數據併爲下游服務從新編碼 |
DebugFilter |
1 | 若是設置 debug 請求參數,則此過濾器將RequestContext.setDebugRouting() 和 RequestContext.setDebugRequest() 設置爲true |
PreDecorationFilter |
5 | 根據提供的 RouteLocator 肯定路由的位置和方式,它還爲下游請求設置各類與代理相關的頭文件 |
Route filters
filter | order | 說明 |
---|---|---|
RibbonRoutingFilter |
10 | 使用 Ribbon、Hystrix 和 可插拔 HTTP客戶機發送請求。只對 RequestContext 存在 serviceId 參數的請求進行處理,即只對經過 serviceId配置路由規則的請求路由。可使用不一樣的 HTTP 客戶端:HttpClient、OkHttpClient、Netflix Ribbon HTTP client |
SimpleHostRoutingFilter |
100 | 經過 Apache HttpClient 發送請求到預約的 url,這些 url 能夠在 RequestContext.getRouteHost() 中找到,即只對經過 url 配置路由規則的請求路由 |
SendForwardFilter |
500 | 經過使用 Servlet RequestDispatcher 轉發請求。用於轉發請求到當前應用的端點 |
Post filters
filter | order | 說明 |
---|---|---|
LocationRewriteFilter |
900 | 負責將 Location header 重寫爲 Zuul URL |
SendResponseFilter |
1000 | 將代理請求的響應寫入當前響應 |
Error filters
filter | order | 說明 |
---|---|---|
SendErrorFilter |
0 | 利用請求上下文中的錯誤信息來組織成一個 forward 到 /error 錯誤端點的請求來產生錯誤響應 |
禁用過濾器
默認狀況下,這些過濾器在代理和服務器模式下都是啓用的。若是在某些場景下,禁用某個過濾器,能夠設置
zuul.<SimpleClassName>.<filterType>.disable=true
。例如,要禁用org.springframework.cloud.netflix.zuul.filter.post.sendresponsefilter
,設置zuul.SendResponseFilter.post.disable=true
建立一個 Spring Boot 項目 zuul-filters
,Zuul 的服務網關路由配置能夠見 Spring Cloud Zuul 構建微服務網關
spring: application: name: zuul-filters server: port: 8090 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ zuul: ignoredServices: '*' routes: product: path: /product/** serviceId: product-service management: endpoints: web: exposure: include: '*'
自定義一個過濾器 AccessFilter
,對請求中沒有 accessToken 參數的請求,返回 401拒絕訪問
@Log4j2 public class AccessFilter extends ZuulFilter { @Override public int filterOrder() { // run before PreDecoration return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1; } @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); // a filter has already forwarded // a filter has already determined serviceId return !ctx.containsKey(FilterConstants.FORWARD_TO_KEY) && !ctx.containsKey(FilterConstants.SERVICE_ID_KEY); } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString()); String token = request.getParameter("accessToken"); if(StringUtils.isBlank(token)) { log.warn("access token is empty"); // 過濾該請求,不對其進行路由 ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); return null; } log.info("access token ok"); return null; } }
實現自定義過濾器後,把它添加到 Spring 的 Beans 中
@SpringBootApplication @EnableZuulProxy public class ZuulFiltersApplication { public static void main(String[] args) { SpringApplication.run(ZuulFiltersApplication.class, args); } @Bean public AccessFilter accessFilter() { return new AccessFilter(); } }
下面咱們來測試下結果,啓動項目 eureka-server
、product-serivce
(做爲代理的服務)、zuul-filters
訪問 http://localhost:8090/product/product/1
返回 401
訪問 http://localhost:8090/product/product/1?accessToken=111
會正常路由到 product-service 的 /product/1
參考代碼見:demo
@EnableZuulProxy
註解配合 Spring Boot Actuator,Zuul 會暴露額外的兩個管理端點:Routes
和 Filters
。分別是關於路由和過濾器的端點(服務路由的端點在這裏介紹 Spring Cloud Zuul 構建微服務網關)
spring-cloud-starter-netflix-zuul
已經依賴了 spring-boot-starter-actuator
,因此上面的工程已經包含了路由管理的功能。關於過濾器的管理端點的路徑爲 /filters
訪問路徑 http://localhost:8090/actuator/filters
{ "error": [ { "class": "org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter", "order": 0, "disabled": false, "static": true } ], "post": [ { "class": "org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter", "order": 1000, "disabled": false, "static": true } ], "pre": [ { "class": "org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter", "order": 1, "disabled": false, "static": true }, { "class": "org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter", "order": -1, "disabled": false, "static": true }, { "class": "com.turbosnail.zuul.filter.AccessFilter", "order": 4, "disabled": false, "static": true }, { "class": "org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter", "order": -2, "disabled": false, "static": true }, { "class": "org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter", "order": -3, "disabled": false, "static": true }, { "class": "org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter", "order": 5, "disabled": false, "static": true } ], "route": [ { "class": "org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter", "order": 100, "disabled": false, "static": true }, { "class": "org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter", "order": 10, "disabled": false, "static": true }, { "class": "org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter", "order": 500, "disabled": false, "static": true } ] }
訪問404是由於沒有暴露端點,能夠設置 management.endpoints.web.exposure.include: '*'