【SpringCloud技術專題】「Gateway網(wǎng)關系列」(3)微服務網(wǎng)關服務的Gateway全流程開發(fā)實踐指南(2.2.X)

開發(fā)指南須知

本次實踐主要在版本:2.2.0.BUILD-SNAPSHOT上進行構(gòu)建雅采,這個項目提供了構(gòu)建在Spring生態(tài)系統(tǒng)之上API網(wǎng)關委刘。

Spring Cloud Gateway的介紹

Spring Cloud Gateway目標是用一個簡單谁鳍、有效的方式路由到API,并且提供橫切的一些關注點稍途,例如:安全阁吝、監(jiān)控、系統(tǒng)性能和彈性等械拍。

image

API網(wǎng)關介紹

API 網(wǎng)關出現(xiàn)的原因是微服務架構(gòu)的出現(xiàn)突勇,不同的微服務一般會有不同的網(wǎng)絡地址,而外部客戶端可能需要調(diào)用多個服務的接口才能完成一個業(yè)務需求殊者,如果讓客戶端直接與各個微服務通信与境,會有以下的問題:

  1. 客戶端會多次請求不同的微服務验夯,增加了客戶端的復雜性猖吴。
  2. 存在跨域請求,在一定場景下處理相對復雜挥转。
  3. 認證復雜海蔽,每個服務都需要獨立認證。
  4. 難以重構(gòu)绑谣,隨著項目的迭代党窜,可能需要重新劃分微服務。
    • 例如借宵,可能將多個服務合并成一個或者將一個服務拆分成多個幌衣。如果客戶端直接與微服務通信,那么重構(gòu)將會很難實施壤玫。
  5. 某些微服務可能使用了防火墻 / 瀏覽器不友好的協(xié)議豁护,直接訪問會有一定的困難。

以上這些問題可以借助 API 網(wǎng)關解決欲间。API 網(wǎng)關是介于客戶端和服務器端之間的中間層楚里,所有的外部請求都會先經(jīng)過 API 網(wǎng)關這一層。也就是說猎贴,API 的實現(xiàn)方面更多的考慮業(yè)務邏輯班缎,而安全、性能她渴、監(jiān)控可以交由 API 網(wǎng)關來做达址,這樣既提高業(yè)務靈活性又不缺安全性。

SpringCloud Gateway技術基礎

它是基于spring官方Spring 5.0趁耗、Spring Boot2.0和Project Reactor等技術開發(fā)的網(wǎng)關苏携,Spring Cloud Gateway旨在為微服務架構(gòu)提供簡單、有效和統(tǒng)一的API路由管理方式对粪,Spring Cloud Gateway作為Spring Cloud生態(tài)系統(tǒng)中的網(wǎng)關右冻,目標是替代Netflix Zuul装蓬,其不僅提供統(tǒng)一的路由方式,并且還基于Filer鏈的方式提供了網(wǎng)關基本的功能纱扭,例如:安全牍帚、監(jiān)控/埋點、限流等乳蛾。

如何引用Spring Cloud Gateway

maven坐標為:

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

有關使用當前Spring Cloud構(gòu)建系統(tǒng)的詳細信息暗赶,如果你引入了starter,但不想開啟gateway肃叶,可以設置:

spring.cloud.gateway.enabled=false蹂随。

注意

  1. Spring Cloud Gateway 構(gòu)建在 Spring Boot 2.0, Spring WebFlux, and Project Reactor之上,因此因惭,許多熟悉的同步庫(例如:Spring Data 岳锁、Spring Security)或模式不適用于Spring Cloud Gateway。

  2. Spring Cloud Gateway需要SpringBoot和SpringWebFlux提供的netty運行時蹦魔,它不再運行于傳統(tǒng)的Servlet容器或一個WAR包激率。

Route

網(wǎng)關基本構(gòu)件塊,也是網(wǎng)關最基礎的部分勿决,路由信息有一個ID乒躺、一個目的URL、一組斷言predicates和一組filters組成低缩。如果聚合斷言為真嘉冒,則匹配路由,說明請求的URL和配置咆繁。

Predicate

Java8中的斷言函數(shù)讳推。Spring Cloud Gateway中的斷言函數(shù)輸入類型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的斷言函數(shù)允許開發(fā)者去定義匹配來自于http request中的任何信息么介,比如請求頭和參數(shù)等娜遵。

Java 8 Function Predicate. 輸入類型是SpringFramework ServerWebExchange. 這允許開發(fā)人員匹配來自HTTP請求的任何內(nèi)容,例如頭或參數(shù)壤短。

Filter

使用特定工廠構(gòu)造的 Spring Framework GatewayFilter 實例设拟。在這里,可以在發(fā)送downstream 請求之前或之后修改requests和responses久脯。

一個標準的Spring webFilter纳胧。Spring cloud gateway中的filter分為兩種類型的Filter,分別是Gateway Filter和Global Filter帘撰。過濾器Filter將會對請求和響應進行修改處理跑慕。

理解:
  1. 斷言(Predicate):請求匹配;
  2. 過濾器(Filter):對請求或者返回進行過濾增強。

網(wǎng)關提供API全托管服務核行,豐富的API管理功能牢硅,輔助企業(yè)管理大規(guī)模的API,以降低管理成本和安全風險芝雪,包括協(xié)議適配减余、協(xié)議轉(zhuǎn)發(fā)、安全策略惩系、防刷位岔、流量、監(jiān)控日志等功能堡牡。一般來說網(wǎng)關對外暴露的URL或者接口信息抒抬,我們統(tǒng)稱為路由信息。

Spring Cloud Gateway的工作原理

GatewayClient請求 Spring Cloud Gateway晤柄,如果Gateway Handler Mapping 確定請求與路由匹配擦剑,該請求被發(fā)送到Gateway Web Handler。此Handler運行時發(fā)送請求到具體的請求可免,其中通過過濾器鏈抓于。

image

過濾器鏈被虛線分隔的原因是過濾器可以在發(fā)送代理請求之前或之后執(zhí)行邏輯做粤。執(zhí)行所有“預”過濾邏輯浇借,然后發(fā)出代理請求。在發(fā)出代理請求后怕品,將執(zhí)行“post”過濾器邏輯妇垢。URIs 在路由中沒有設置端口,則按照HTTP和HTTPS默認端口設置為80和443肉康。

Spring cloud Gateway發(fā)出請求闯估。然后再由Gateway Handler Mapping中找到與請求相匹配的路由,將其發(fā)送到Gateway web handler吼和。Handler再通過指定的過濾器鏈將請求發(fā)送到我們實際的服務執(zhí)行業(yè)務邏輯涨薪,然后返回。

Spring Cloud Gateway-路由斷言工廠

Spring Cloud Gateway匹配路由作為SpringWebFlux HandlerMapping基礎設施的一部分炫乓。Spring Cloud Gateway包含許多內(nèi)置的路由斷言工廠刚夺,這些斷言匹配不同屬性的HTTP請求,可以組合多個路由斷言工廠,并通過邏輯組合末捣。

Before Route Predicate Factory

Before Route Predicate Factory 有一個時間參數(shù)侠姑,此斷言匹配發(fā)生在該時間參數(shù)之前的請求。

image

這個路由匹配發(fā)生在 Jan 20, 2017 17:42 Mountain Time (Denver)之前的請求箩做。

After Route Predicate Factory

After Route Predicate Factory有一個時間參數(shù)莽红,此斷言匹配發(fā)生在該時間參數(shù)之后的請求。

image

這個路由匹配發(fā)生在 Jan 20, 2017 17:42 Mountain Time (Denver)之后的請求邦邦。

Between Route Predicate Factory

Between Route Predicate Factory 有兩個時間參數(shù)安吁。此斷言匹配發(fā)生在這兩個時間之間的請求醉蚁。

image

這個路由匹配發(fā)生在 Jan 20, 2017 17:42 Mountain Time (Denver)與Jan 21, 2017 17:42 Mountain Time (Denver)之間的請求」淼辏可應用于維護窗口馍管。

Cookie Route Predicate Factory

Cookie Route Predicate Factory 有兩個參數(shù),包括cookie名稱和正則表達式薪韩。此斷言匹配cookies包括給定的名稱和符合正則表達式的值确沸。

image

此路由匹配cookie名稱為chocolate ,cookie值為ch.p正則表達式俘陷,匹配chap,chbp等罗捎。

Header Route Predicate Factory

Header Route Predicate Factory 包括兩個參數(shù)包括頭名稱和值的正則表達式。此斷言匹配一個頭信息包括該名稱和符合該正則表達式值得請求拉盾。

此路由匹配頭名稱為X-Request-Id且值匹配\d+ 表達式(包含一個或多個數(shù)字)桨菜。

image

Host Route Predicate Factory

Host Route Predicate Factory包括一個參數(shù)host 名稱模式列表。此模式是一種 Ant 風格模式捉偏,以 "." 作為分隔符倒得。此斷言匹配Host頭。另外Host頭來源有兩種:第一種是請求地址夭禽;第二種是自己在http的header頭中放入Host變量值霞掺。

image
URI 模板變量也支持這種格式 {sub}.myhost.org。

此路由匹配頭文件中的Host值www.somehost.org讹躯,beta.somehost.orgwww.anotherhost.org菩彬。此示例均為默認端口80,如果為其他端口潮梯,需要在表達式中定義骗灶。

此斷言提取URI模板變量(如上面示例中定義的子變量)作為名稱和值的映射,并將其放置在ServerWebExchange.getAttributes()中秉馏,其鍵在ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE屬性中定義耙旦。

在過濾其中加入如下代碼示例:
Map uri_template_variables_attribute=exchange.getAttribute(ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
   // Map variables=ServerWebExchangeUtils.getUriTemplateVariables(exchange);
     uri_template_variables_attribute.forEach((K,V)->{
            System.out.println("K:"+K+"--V:"+V);
      });

Filter規(guī)律器

Global Filter接口與GatewayFilter具有相同的簽名。這些是有條件地應用于所有路由的特殊過濾器萝究。

Combined Global Filter and GatewayFilter Ordering
  • 當請求進入(并匹配路由)時免都,F(xiàn)iltering Web Handler會將GlobalFilter的所有實例和GatewayFilter的所有路由特定實例添加到過濾器鏈。

  • 此組合過濾器鏈通過org.springframework.core.Ordered接口進行排序糊肤∏倮ィ可以通過實現(xiàn)getOrder()方法或者使用@Order注解。

Spring Cloud Gateway區(qū)分了過濾器邏輯執(zhí)行的“請求”和“響應”階段馆揉,具有最高優(yōu)先級的過濾器將是“請求”階段的第一個和“響應”階段的最后一個 业舍。

image
ANT通配符有三種:

而上面多數(shù)的匹配規(guī)則運算符號都是有AntPathMatcher對象進行實現(xiàn)的

private AntPathMatcher antPathMatcher = new AntPathMatcher();
  • ?:匹配任意一個單個字符
    • :匹配0或者任意數(shù)量的字符
  • ** :匹配0和或者更多目錄的字符
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        //商城api接口,校驗用戶必須登錄
        if(antPathMatcher.match("/api/**/auth/**", path)) {
            List<String> tokenList = request.getHeaders().get("token”);
            if(null == tokenList) {
                ServerHttpResponse response = exchange.getResponse();
                return out(response);
            } else {
//                Boolean isCheck = JwtUtils.checkToken(tokenList.get(0));
//                if(!isCheck) {
                    ServerHttpResponse response = exchange.getResponse();
                    return out(response);
//                }
            }
        }
        //內(nèi)部服務接口,不允許外部訪問
        if(antPathMatcher.match("/**/inner/**", path)) {
            ServerHttpResponse response = exchange.getResponse();
            return out(response);
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

    private Mono<Void> out(ServerHttpResponse response) {
        JsonObject message = new JsonObject();
        message.addProperty("success", false);
        message.addProperty("code", 28004);
        message.addProperty("data", "鑒權失敗”);
        byte[] bits = message.toString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //指定編碼舷暮,否則在瀏覽器中會中文亂碼
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8”);
        return response.writeWith(Mono.just(buffer));
    }
}

自定義異常處理

服務網(wǎng)關調(diào)用服務時可能會有一些異程铮或服務不可用,它返回錯誤信息不友好下面,需要我們覆蓋處理复颈。

ErrorHandlerConfig
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfig {

    private final ServerProperties serverProperties;

    private final ApplicationContext applicationContext;

    private final ResourceProperties resourceProperties;

    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;

    public ErrorHandlerConfig(ServerProperties serverProperties,
                                     ResourceProperties resourceProperties,
                                     ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                        ServerCodecConfigurer serverCodecConfigurer,
                                     ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
                errorAttributes,
                this.resourceProperties,
                this.serverProperties.getError(),
                this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
}

參考資料

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市沥割,隨后出現(xiàn)的幾起案子耗啦,更是在濱河造成了極大的恐慌,老刑警劉巖机杜,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帜讲,死亡現(xiàn)場離奇詭異,居然都是意外死亡椒拗,警方通過查閱死者的電腦和手機似将,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚀苛,“玉大人在验,你說我怎么就攤上這事《挛矗” “怎么了腋舌?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長兴溜。 經(jīng)常有香客問我侦厚,道長耻陕,這世上最難降的妖魔是什么拙徽? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮诗宣,結(jié)果婚禮上膘怕,老公的妹妹穿的比我還像新娘。我一直安慰自己召庞,他們只是感情好岛心,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著篮灼,像睡著了一般忘古。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诅诱,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天髓堪,我揣著相機與錄音,去河邊找鬼。 笑死干旁,一個胖子當著我的面吹牛驶沼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播争群,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼回怜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了换薄?” 一聲冷哼從身側(cè)響起玉雾,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎轻要,沒想到半個月后抹凳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡伦腐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年赢底,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柏蘑。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡幸冻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出咳焚,到底是詐尸還是另有隱情洽损,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布革半,位于F島的核電站碑定,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏又官。R本人自食惡果不足惜延刘,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望六敬。 院中可真熱鬧碘赖,春花似錦、人聲如沸外构。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽审编。三九已至撼班,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間垒酬,已是汗流浹背砰嘁。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工序臂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留积担,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像饺蚊,于是被迫代替她去往敵國和親衡便。 傳聞我的和親對象是個殘疾皇子肛循,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容