Spring Cloud:第五章:Zuul服務(wù)網(wǎng)關(guān)

快速入門

定義user,order,pay服務(wù),定義zull服務(wù)網(wǎng)關(guān)服務(wù)都注冊(cè)到eureka服務(wù)上,

通過(guò)一下接口訪問(wèn)user,order,pay的服務(wù),

http://localhost:7070/pay/index
http://localhost:8080/user/index
http://localhost:9090/order/index

定義服務(wù)網(wǎng)關(guān)服務(wù)zuul,我們看看其相關(guān)配置,zuul-service加入依賴:

   <dependencies>
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-zuul</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-eureka</artifactId>
          </dependency>
    </dependencies>

配置文件:

spring:
  application:
    name: zuul-service
eureka:
  client:
    service-url:
     defaultZone: http://localhost:8761/eureka
  instance:
    instance-id:  ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
    prefer-ip-address: true
server:
  port: 6069

定義啟動(dòng)類:

package com.zhihao.miao;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
 
//使用@EnableZuulProxy注解開啟zuul的api網(wǎng)關(guān)服務(wù)功能
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class,args);
    }
}

然后可以通過(guò)服務(wù)網(wǎng)關(guān)進(jìn)行訪問(wèn)上面的三個(gè)服務(wù)接口,

http://localhost:6069/pay-service/pay/index
http://localhost:6069/user-service/user/index
http://localhost:6069/order-service/order/index

注意:
默認(rèn)的zuul結(jié)合eureka會(huì)將注冊(cè)到eureka的服務(wù)名作為訪問(wèn)的ContextPath。

也可以指定一些路由機(jī)制,application.yml中定義如下,

zuul:
  routes:
    user-service:
      path: /users/**
      serviceId: user-service

url符合/users/**規(guī)則的就被轉(zhuǎn)發(fā)到user-service實(shí)例中了。

http://localhost:6069/users/user/index

之前的通過(guò)服務(wù)實(shí)例名稱的也能訪問(wèn)

http://localhost:6069/user-service/user/index

也可以忽略所有的代理,只保留自己配置的即可。

zuul:
  ignored-services: '*'
  routes:
    user-service: /users/**

此時(shí)只能訪問(wèn)user-service服務(wù),并且只能以u(píng)sers這種路由來(lái)訪問(wèn)。
不能訪問(wèn)

http://localhost:6069/pay-service/pay/index
http://localhost:6069/order-service/order/index

之前默認(rèn)的注冊(cè)到eureka上的服務(wù)(user-service)也不能訪問(wèn)。
不能訪問(wèn)

http://192.168.1.57:6069/user-service/user/index

可以訪問(wèn)

http://192.168.1.57:6069/users/user/index

以上的配置也可以這樣指定

zuul:
  ignored-services: order-service,pay-service
  routes:
    user-service: /users/**

當(dāng)然此時(shí)除了http://192.168.1.57:6069/users/user/index能被代理訪問(wèn)到
http://192.168.1.57:6069/user-service/user/index也能訪問(wèn)了
請(qǐng)求過(guò)濾

在實(shí)現(xiàn)請(qǐng)求理由功能之后,我們的微服務(wù)應(yīng)用提供的接口就可以通過(guò)統(tǒng)一的api網(wǎng)關(guān)入口被客戶端訪問(wèn)到了。每個(gè)客戶端用戶請(qǐng)求服務(wù)應(yīng)用提供的接口時(shí),他們的訪問(wèn)權(quán)限往往都有一定的限制,系統(tǒng)并不會(huì)將所有的微服務(wù)接口對(duì)他們開放。然而,目前的服務(wù)路由并沒有限制權(quán)限這樣的功能,所有請(qǐng)求都會(huì)被毫無(wú)保留的轉(zhuǎn)發(fā)到具體的應(yīng)用并返回結(jié)果,為了實(shí)現(xiàn)對(duì)客戶端請(qǐng)求的安全校驗(yàn)和權(quán)限控制,最簡(jiǎn)單和粗暴的方法就是在每個(gè)微服務(wù)應(yīng)用都實(shí)現(xiàn)一套用于校驗(yàn)簽名和鑒別權(quán)限的過(guò)濾器或攔截器。這樣有個(gè)問(wèn)題就是功能實(shí)現(xiàn)太過(guò)冗余。比較好的做法就是將這些校驗(yàn)邏輯剝離出去,構(gòu)建一個(gè)獨(dú)立的鑒權(quán)服務(wù)。在完成剝離之后,直接在微服務(wù)應(yīng)用中通過(guò)調(diào)用鑒權(quán)系統(tǒng)服務(wù)來(lái)實(shí)現(xiàn)校驗(yàn),但是這樣僅僅只是解決了鑒權(quán)邏輯的分離,并沒有在本質(zhì)上將這部分不屬于冗余的邏輯從原有的微服務(wù)應(yīng)用中拆出去,冗余的攔截器或者過(guò)濾器依然會(huì)存在。

對(duì)于這樣的問(wèn)題,更好的做法是通過(guò)前置的網(wǎng)關(guān)服務(wù)來(lái)完成這些非業(yè)務(wù)性質(zhì)的校驗(yàn)。由于網(wǎng)關(guān)服務(wù)的加入,外部客戶端訪問(wèn)我們的系統(tǒng)已經(jīng)有了統(tǒng)一的入口,既然這些校驗(yàn)與具體的業(yè)務(wù)無(wú)關(guān),那何不在請(qǐng)求到達(dá)的時(shí)候就完成校驗(yàn)和過(guò)濾,微服務(wù)應(yīng)用端就可以去除各種復(fù)雜的過(guò)濾器和攔截器了,這使得微服務(wù)應(yīng)用接口的開發(fā)和測(cè)試復(fù)雜度也得到了相應(yīng)的降低。這就涉及到了zuul的另一個(gè)主要功能,請(qǐng)求過(guò)濾。

在zuul-service服務(wù)中定義一個(gè)過(guò)濾器,

public class AccessFilter extends ZuulFilter{
 
    private Logger logger = LoggerFactory.getLogger(getClass());
 
    //過(guò)濾器的類型,它決定過(guò)濾器在請(qǐng)求的哪個(gè)生命周期中執(zhí)行,這里定義為pre,代表會(huì)在請(qǐng)求被理由之前執(zhí)行。
    @Override
    public String filterType() {
        return "pre";
    }
 
    //過(guò)濾器的執(zhí)行順序。當(dāng)請(qǐng)求在一個(gè)階段中存在多個(gè)過(guò)濾器時(shí),需要根據(jù)該方法返回的值來(lái)依次執(zhí)行
    @Override
    public int filterOrder() {
        return 0;
    }
 
    //判斷該過(guò)濾器是否需要被執(zhí)行。這里我們直接返回了true,因此該過(guò)濾器對(duì)所有的請(qǐng)求都生效。實(shí)際運(yùn)行中我們可以利用該函數(shù)
    //來(lái)指定過(guò)濾器的有效范圍。
    @Override
    public boolean shouldFilter() {
        return true;
    }
 
    //過(guò)濾器的具體執(zhí)行邏輯。
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
 
        logger.info("send {} request to {}",request.getMethod(),request.getRequestURI().toString());
 
        Object accessToken = request.getParameter("accessToken");
        if(accessToken == null){
            logger.warn("access token is empty");
            ctx.setSendZuulResponse(false); //令zuul過(guò)濾該請(qǐng)求,不對(duì)其進(jìn)行路由
            ctx.setResponseStatusCode(401); //設(shè)置返回的錯(cuò)誤碼
        }
 
        logger.info("access token ok");
        return null;
    }
}

ZuulFilter接口中定義四個(gè)方法:
filterType:過(guò)濾器的類型,它決定過(guò)濾器在請(qǐng)求的哪個(gè)生命周期中執(zhí)行,這里定義為pre,代表會(huì)在請(qǐng)求被理由之前執(zhí)行。
filterOrder:過(guò)濾器的執(zhí)行順序。當(dāng)請(qǐng)求在一個(gè)階段中存在多個(gè)過(guò)濾器時(shí),需要根據(jù)該方法返回的值來(lái)依次執(zhí)行。
shouldFilter:判斷該過(guò)濾器是否需要被執(zhí)行。這里我們直接返回了true,因此該過(guò)濾器對(duì)所有的請(qǐng)求都生效。實(shí)際運(yùn)行中我們可以利用該函數(shù)。
run:過(guò)濾器的具體執(zhí)行邏輯。

在將過(guò)濾器納入spring容器中,

@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class,args);
    }
 
    @Bean
    public AccessFilter accessFilter(){
        return new AccessFilter();
    }
}

訪問(wèn)服務(wù):

http://localhost:6069/users/user/index

狀態(tài)碼錯(cuò)誤為401,正確訪問(wèn)姿勢(shì)是

http://localhost:6069/users/user/index?accessToken=111

過(guò)濾器類型與請(qǐng)求生命周期

PRE:這種過(guò)濾器在請(qǐng)求被路由之前調(diào)用。我們可利用這種過(guò)濾器實(shí)現(xiàn)身份驗(yàn)證、在集群中選擇請(qǐng)求的微服務(wù)、記錄調(diào)試信息等。
ROUTING:這種過(guò)濾器將請(qǐng)求路由到微服務(wù)。這種過(guò)濾器用于構(gòu)建發(fā)送給微服務(wù)的請(qǐng)求,并使用Apache HttpClient或Netfilx Ribbon請(qǐng)求微服務(wù)。
POST:這種過(guò)濾器在路由到微服務(wù)以后執(zhí)行。這種過(guò)濾器可用來(lái)為響應(yīng)添加標(biāo)準(zhǔn)的HTTP Header、收集統(tǒng)計(jì)信息和指標(biāo)、將響應(yīng)從微服務(wù)發(fā)送給客戶端等。
ERROR:在其他階段發(fā)生錯(cuò)誤時(shí)執(zhí)行該過(guò)濾器。

除了默認(rèn)的過(guò)濾器類型,Zuul還允許我們創(chuàng)建自定義的過(guò)濾器類型。例如,我們可以定制一種STATIC類型的過(guò)濾器,直接在Zuul中生成響應(yīng),而不將請(qǐng)求轉(zhuǎn)發(fā)到后端的微服務(wù)。

總結(jié)

就目前掌握的api網(wǎng)關(guān)知識(shí),總結(jié)出四點(diǎn)如下

    它作為系統(tǒng)的統(tǒng)一入口,屏蔽了系統(tǒng)內(nèi)部各個(gè)微服務(wù)的細(xì)節(jié)。
    它可以與服務(wù)治理框架結(jié)合,實(shí)現(xiàn)自動(dòng)化的服務(wù)實(shí)例維護(hù)以及負(fù)載均衡的路由轉(zhuǎn)發(fā)。
    它可以實(shí)現(xiàn)接口權(quán)限校驗(yàn)與微服務(wù)業(yè)務(wù)邏輯的解耦。
    通過(guò)服務(wù)網(wǎng)關(guān)中的過(guò)濾器,在各個(gè)生命周期中去校驗(yàn)請(qǐng)求的內(nèi)容,將原本在對(duì)外服務(wù)層做的校驗(yàn)遷移,保證了微服務(wù)的無(wú)狀態(tài)性,同時(shí)降低了微服務(wù)的測(cè)試難度,讓服務(wù)本身更集中關(guān)注業(yè)務(wù)邏輯的處理。