爲何須要網關呢?html
咱們知道咱們要進入一個服務自己,很明顯咱們沒有特別好的辦法,直接輸入IP地址+端口號,咱們知道這樣的作法很糟糕的,這樣的作法大有問題,首先暴露了咱們實體機器的IP地址,別人一看你的IP地址就知道服務部署在哪裏,讓別人很方便的進行攻擊操做。java
第二,咱們這麼多服務,咱們是否是要挨個調用它呀,咱們這裏假設作了個權限認證,咱們每個客戶訪問的都是跑在不一樣機器上的不一樣的JVM上的服務程序,咱們每個服務都須要一個服務認證,這樣作煩不煩呀,明顯是很煩的。spring
那麼咱們這時候面臨着這兩個極其重要的問題,這時咱們就須要一個辦法解決它們。首先,咱們看IP地址的暴露和IP地址寫死後帶來的單點問題,我是否是對這麼服務自己我也要動態的維護它服務的列表呀,我須要調用這服務自己,是否是也要一個負載均衡同樣的玩意,後端
還有關於IP地址暴露的玩意,我是否是須要作一個代理呀,像Nginx的反向代理同樣的東西,還有這玩意上部署公共的模塊,好比全部入口的權限校驗的東西。所以咱們如今須要Zuul API網關。它就解決了上面的問題,你想調用某個服務,它會給你映射,把你服務的IP地址映射成api
某個路徑,你輸入該路徑,它匹配到了,它就去替你訪問這個服務,它會有個請求轉發的過程,像Nginx同樣,服務機器的具體實例,它不會直接去訪問IP,它會去Eureka註冊中心拿到服務的實例ID,即服務的名字。我再次使用客戶端的負載均衡ribbon訪問其中服務實例中的一臺。安全
API網關主要爲了服務自己對外的調用該怎麼調用來解決的,還有解決權限校驗的問題,你能夠在這裏整合調用一系列過濾器的,例如整合shiro,springsecurity之類的東西。cookie
Zuul能夠經過加載動態過濾機制,從而實現如下各項功能:網絡
1.驗證與安全保障: 識別面向各種資源的驗證要求並拒絕那些與要求不符的請求。app
2.審查與監控: 在邊緣位置追蹤有意義數據及統計結果,從而爲咱們帶來準確的生產狀態結論。負載均衡
3.動態路由: 以動態方式根據須要將請求路由至不一樣後端集羣處。
4.壓力測試: 逐漸增長指向集羣的負載流量,從而計算性能水平。
5.負載分配: 爲每一種負載類型分配對應容量,並棄用超出限定值的請求。
6.靜態響應處理: 在邊緣位置直接創建部分響應,從而避免其流入內部集羣。
7.多區域彈性: 跨越AWS區域進行請求路由,旨在實現ELB使用多樣化並保證邊緣位置與使用者儘量接近。
接着下來進行實戰小Demo
第一步,在原來的工程下,新建一個Zuul模塊,引入依賴,代碼以下:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.3.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> <version>1.3.5.RELEASE</version> </dependency>
接着在啓動類上打上@EnableZuulProxy註解,代碼以下:
server: port: 5000 spring: application: name: api-geteway zuul: routes: #標識你服務的名字,這裏能夠本身定義,通常方便和規範來說仍是跟本身服務的名字同樣 hello-service: #服務映射的路徑,經過這路徑就能夠從外部訪問你的服務了,目的是爲了避免爆露你機器的IP,面向服務的路由了,給你選一個可用的出來, #這裏zuul是自動依賴hystrix,ribbon的,不是面向單機 path: /hello-service/** #這裏必定要是你Eureka註冊中心的服務的名稱,是因此這裏配置serviceId由於跟eureka結合了,若是單獨使用zuul,那麼就必須寫本身機器的IP了, #如url:http://localhost:8080/ 這樣的很差就是寫死IP了,萬一這IP掛了,這高可用性,服務註冊那套東西就用不起來了 serviceId: hello-service eureka: #客戶端 client: #註冊中心地址 service-url: defaultZone: http://localhost:8888/eureka/,http://localhost:8889/eureka/
接着啓動先前文章中的註冊中心和兩個hello-service服務提供者,接着咱們運行,看一下它的請求轉發功能,看他有沒有輪詢進入兩個服務,
輸入localhost:5000/hello-service/hello,以下:
接着再刷新一遍:
能夠看到zuul進行了請求分發了。它是根據你的服務名字hello-servie來映射到具體的機器上,這不就是一個反向代理的功能嗎?
zuul還能進行請求過濾,那麼咱們進行一下token校驗來演示一下,首先咱們須要先新建一個TokenFilter類來繼承ZuulFilter這個類,實現它的四個接口,代碼以下:
package hjc.zuul; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import javax.servlet.http.HttpServletRequest; /** * Created by cong on 2018/5/18. */ public class TokenFilter extends ZuulFilter { //四種類型:pre,routing,error,post //pre:主要用在路由映射的階段是尋找路由映射表的 //routing:具體的路由轉發過濾器是在routing路由器,具體的請求轉發的時候會調用 //error:一旦前面的過濾器出錯了,會調用error過濾器。 //post:當routing,error運行完後纔會調用該過濾器,是在最後階段的 @Override public String filterType() { return "pre"; } //自定義過濾器執行的順序,數值越大越靠後執行,越小就越先執行 @Override public int filterOrder() { return 0; } //控制過濾器生效不生效,能夠在裏面寫一串邏輯來控制 @Override public boolean shouldFilter() { return true; } //執行過濾邏輯 @Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); String token = request.getParameter("token"); if (token == null){ context.setSendZuulResponse(false); context.setResponseStatusCode(401); context.setResponseBody("unAuthrized"); return null; } return null; } }
filterType:返回一個字符串表明過濾器的類型,在zuul中定義了四種不一樣生命週期的過濾器類型,具體以下:
1.pre
:能夠在請求被路由以前調用,用在路由映射的階段是尋找路由映射表的
2.route
:在路由請求時候被調用,具體的路由轉發過濾器是在routing路由器具體的請求轉發的時候會調用
3.error
:處理請求時發生錯誤時被調用
4.post
:當routing,error運行完後纔會調用該過濾器,是在最後階段的
這裏聲明一下zuul過濾器執行網絡請求發生的異常,過濾器裏面是不能直接將try-catch捕捉的異常拋出給頁面的。應用程序拋出的異常是能夠返回出的需解決辦法就是在catch裏面用context.set()方法返回給頁面。以下:
try{ 業務邏輯...... }catch(Exception e){ RequestContext context = RequestContext.getCurrentContext(); context.set("error.status_code",401); context.set("error.exception",e); context.set("error.message","sfdfsdf"); }
接着,你還須要把這個過濾器加入spring中,讓spring管理,代碼以下:
package hjc; import hjc.zuul.TokenFilter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.context.annotation.Bean; @SpringBootApplication @EnableZuulProxy public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); } //將過濾器交給Spring管理 @Bean public TokenFilter tokenFilter(){ return new TokenFilter(); } }
接着,讓咱們啓動啓動類,先進行不帶token的訪問,以下:
能夠看到,返回一個沒權限的信息,這裏要說一下,Token通常都是放在請求頭中的,這裏咱們只是爲了演示纔沒那麼幹,
接着將token帶上再去訪問,以下:
能夠看到這是已經將咱們的請求放過去了。
這裏我還要講一下什麼是默認路由,將zuul的配置刪除路由配置,以下:
server: port: 5000 spring: application: name: api-geteway eureka: #客戶端 client: #註冊中心地址 service-url: defaultZone: http://localhost:8888/eureka/,http://localhost:8889/eureka/
接着,重啓繼續訪問,以下:
、
能夠看到,仍是能繼續訪問,咱們什麼都沒配,竟然還能訪問,那是由於,這裏默認用你的服務名字hello-service自動聲明瞭。
那麼,若是說我不想讓它幫我自動聲明,我要我本身定義,那麼能夠在yml配置文件中使用zuu.ignored-services就能夠把本身像過濾的過濾,以下:」
zuul: #若是ignored-services:* 表示全部的默認路由都失效了,要本身一個個配,沒人會那麼操蛋,除非遇到奇葩業務 ignored-services:
接着咱們再說一下映射規則,比方說
zuul: routes: #標識你服務的名字,這裏能夠本身定義,通常方便和規範來說仍是跟本身服務的名字同樣 hello-service: #服務映射的路徑,經過這路徑就能夠從外部訪問你的服務了,目的是爲了避免爆露你機器的IP,面向服務的路由了,給你選一個可用的出來, #這裏zuul是自動依賴hystrix,ribbon的,不是面向單機 path: /hello-service/** #這裏必定要是你Eureka註冊中心的服務的名稱,是因此這裏配置serviceId由於跟eureka結合了,若是單獨使用zuul,那麼就必須寫本身機器的IP了, #如url:http://localhost:8080/ 這樣的很差就是寫死IP了,萬一這IP掛了,這高可用性,服務註冊那套東西就用不起來了 serviceId: hello-service zuul: routes: hello-service: path: /hello-service/ext/** serviceId: hello-service
這裏的兩個zuul配置映射路徑都有/hello-service/,能夠看到/hello-service/**是包括/hello-service/ext/**的,這兩個路徑進行匹配的時候是否是有衝突呀,怎麼處理呢?誰先匹配呢?
這裏是yml中定義的順序來匹配的。若是是application.properties格式的配置文件,它這個順序是不能保證的,yml格式的配置文件是有順序的,能夠保證,這裏要注意下一下。
若是咱們想定義一下匹配規則怎麼辦呢?那麼咱們就須要在啓動類中定義一個bean,這個類就是決定你的路由的,以下:
這裏就不演示了,須要用到的時候本身再去慢慢查找資料吧。
還有就是ignored-patterns:,以下:
zuul: routes: #標識你服務的名字,這裏能夠本身定義,通常方便和規範來說仍是跟本身服務的名字同樣 hello-service: #服務映射的路徑,經過這路徑就能夠從外部訪問你的服務了,目的是爲了避免爆露你機器的IP,面向服務的路由了,給你選一個可用的出來, #這裏zuul是自動依賴hystrix,ribbon的,不是面向單機 path: /hello-service/** #這裏必定要是你Eureka註冊中心的服務的名稱,是因此這裏配置serviceId由於跟eureka結合了,若是單獨使用zuul,那麼就必須寫本身機器的IP了, #如url:http://localhost:8080/ 這樣的很差就是寫死IP了,萬一這IP掛了,這高可用性,服務註冊那套東西就用不起來了 serviceId: hello-service ignored-patterns: /hello/**
ignored-patterns:表示屏蔽掉/hello/**的路徑,就算你/hello-service/hello/**也不行,照樣屏蔽。這個配置咱們能夠進一步細化,好比說我不想給/hello接口路由,那咱們能夠按照上面方式配置
若是咱們還想配置一個服務的前綴該怎麼辦?代碼以下:
zuul: routes: #標識你服務的名字,這裏能夠本身定義,通常方便和規範來說仍是跟本身服務的名字同樣 hello-service: #服務映射的路徑,經過這路徑就能夠從外部訪問你的服務了,目的是爲了避免爆露你機器的IP,面向服務的路由了,給你選一個可用的出來, #這裏zuul是自動依賴hystrix,ribbon的,不是面向單機 path: /hello-service/** #這裏必定要是你Eureka註冊中心的服務的名稱,是因此這裏配置serviceId由於跟eureka結合了,若是單獨使用zuul,那麼就必須寫本身機器的IP了, #如url:http://localhost:8080/ 這樣的很差就是寫死IP了,萬一這IP掛了,這高可用性,服務註冊那套東西就用不起來了 serviceId: hello-service prefix: /api/**
能夠看到那麼你訪問的服務都必需要加/api/前綴,例如/api/hello-service/**
若是咱們還想進行一個路徑訪問就跳轉到個人本地,那該怎麼辦呢?
我但願用戶在訪問/local時可以自動跳轉到這個方法上來處理,那麼此時咱們須要用到Zuul的本地跳轉,配置方式以下:
zuul: prefix: /api ignored-patterns: /**/hello/** routes: local: path: /hello-service/** url: forward:/local
咱們經常使用的一些,對接springsecurity,或者是一些第三方組件,它們會獲取你的一些cookie信息,那麼Zuul網關爲了安全起見,把你的cookie信息都給幹掉了,這個是沒辦法去搞cookie的。它是默認幹掉的。
這裏Zuul提供了zuul.sensitive-headers來給你搞這些cookie,header,這些信息不要進行過濾。控制你的敏感信息。
默認狀況下,敏感的頭信息沒法通過API網關進行傳遞,咱們能夠經過以下配置使之能夠傳遞:
zuul: routes: hello-service: path: /hello-service/** serviceId: hello-service sensitive-headers: cookie,header之類額東西
還能夠配合Hystrix的一些詳細配置一塊兒使用,前面也講過了。這裏就不說了