第06課:服務網關

本文,咱們將學習 Spring Cloud的另外一個組件:zuul,它提供微服務的網關功能,即中轉站,經過它提供的接口,能夠轉發不一樣的服務。在學習 zuul 以前,咱們先接着上一篇的代碼,來看看服務提供者是如何提供服務的。spring

在服務提供者的 module 下建立 HelloController 類,添加內容以下:api

@RestController
public class HelloController {

    @RequestMapping("index")
    public String index(){
        return "Hello World!";
    }
}

而後分別啓動服務註冊中心和服務提供者,瀏覽器輸入:http://localhost:8762/index,便可看見以下畫面:
圖片描述瀏覽器

在實際的項目中,一個項目可能會包含不少個服務,每一個服務的端口和 IP 均可能不同。那麼,若是咱們以這種形式提供接口給外部調用,代價是很是大的。從安全性上考慮,系統對外提供的接口應該進行合法性校驗,防止非法請求,若是按照這種形式,那每一個服務都要寫一遍校驗規則,維護起來也很麻煩。安全

這個時候,咱們須要統一的入口,接口地址所有由該入口進入,而服務只部署在局域網內供這個統一的入口調用,這個入口就是咱們一般說的服務網關。服務器

Spring Cloud 給咱們提供了這樣一個解決方案,那就是 zuul,它的做用就是進行路由轉發、異常處理和過濾攔截。下面,我將演示若是使用 zuul 建立一個服務網關。app

建立 gateway 工程負載均衡

在父項目上右鍵 -> New -> Module,建立一個名爲 gateway 的工程,在其 pom.xml 中,加入以下依賴:ide

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

建立 Application 啓動類,並增長 @EnableZuulProxy 註解:微服務

@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

最後添加 application.yml 配置文件,內容以下:post

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8080
spring:
  application:
    name: gateway
zuul:
  routes:
    api:
      path: /api/**
      serviceId: eurekaclient

咱們能夠看到,服務網關的配置多了幾項,具體含義以下。

  • zuul.routes.api.path:指定請求基礎地址,其中 API 能夠是任何字符。
  • serviceId:轉發到的服務 ID,也就是指定服務的 application.name,上述實例的含義表示只要包含 /api/
    的地址,都自動轉發到 eurekaclient 的服務去。

而後咱們啓動服務註冊中心、服務提供者、服務網關,訪問地址:http://localhost:8080/api/index,咱們能夠看到和以前的界面徹底同樣。其實只要引入了 zuul,它就會自動幫咱們實現反向代理和負載均衡。配置文件中的地址轉發其實就是一個反向代理,那它如何實現負載均衡呢?

咱們修改服務提供者的 Controller 以下:

RestController
public class HelloController {

    @Value("${server.port}")
    private int port;

    @RequestMapping("index")
    public String index(){
        return "Hello World!,端口:"+port;
    }
}

從新啓動。而後再修改服務提供者的端口爲8673,再次啓動它(切記:原先啓動的不要中止),訪問地址:http://localhost:8761,咱們能夠看到 eurekaclient 服務有兩個地址:
圖片描述
再不斷訪問地址:http://localhost:8080/api/index,能夠看到交替出現如下界面:
圖片描述

圖片描述

由此能夠得出,當一個服務啓動多個端口時,zuul 服務網關會依次請求不一樣端口,以達到負載均衡的目的。

服務攔截

前面咱們提到,服務網關還有個做用就是接口的安全性校驗,這個時候咱們就須要經過 zuul 進行統一攔截,zuul 經過繼承過濾器 ZuulFilter 進行處理,下面請看具體用法。

新建一個類 ApiFilter 並繼承 ZuulFilter:

@Component
public class ApiFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "pre";
    }

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

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() {
        //這裏寫校驗代碼
        return null;
    }
}

其中:

  • filterType 爲過濾類型,可選值有
    pre(路由以前)、routing(路由之時)、post(路由以後)、error(發生錯誤時調用)。
  • filterOrdery 爲過濾的順序,若是有多個過濾器,則數字越小越先執行
  • shouldFilter 表示是否過濾,這裏能夠作邏輯判斷,true 爲過濾,false 不過濾
  • run 爲過濾器執行的具體邏輯,在這裏能夠作不少事情,好比:權限判斷、合法性校驗等。

下面,咱們來作一個簡單的安全驗證:

@Override
    public Object run() {
        //這裏寫校驗代碼
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        String token = request.getParameter("token");
        if(!"12345".equals(token)){
            context.setSendZuulResponse(false);
            context.setResponseStatusCode(401);
            try {
                context.getResponse().getWriter().write("token is invalid.");
            }catch (Exception e){}
        }
        return null;
    }

啓動 gateway,在瀏覽器輸入地址:http://localhost:8080/api/index,能夠看到如下界面:
圖片描述

再經過瀏覽器輸入地址:http://localhost:8080/api/index?token=12345,能夠看到如下界面:
圖片描述

錯誤攔截

在一個大型系統中,服務是部署在不一樣的服務器下面的,咱們不免會遇到某一個服務掛掉或者請求不到的時候,若是不作任何處理,服務網關請求不到會拋出500錯誤,對用戶是不友好的。

咱們爲了提供用戶的友好性,須要返回友好性提示,zuul 爲咱們提供了一個名叫 ZuulFallbackProvider 的接口,經過它咱們就能夠對這些請求不到的服務進行錯誤處理。

新建一個類 ApiFallbackProvider 而且實現 ZuulFallbackProvider 接口:

Component
public class ApiFallbackProvider implements ZuulFallbackProvider{

    @Override
    public String getRoute() {
        return "eurekaclient";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "{code:0,message:\"服務器異常!\"}";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream(getStatusText().getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }

其中,getRoute 方法返回要處理錯誤的服務名,fallbackResponse 方法返回錯誤的處理規則。

如今開始測試這部分代碼,首先停掉服務提供者 eurekaclient,再重啓 gateway,請求地址:http://localhost:8080/api/index?token=12345,便可出現如下界面:
圖片描述

相關文章
相關標籤/搜索