Spring Cloud學習系列第四章:路由與過濾服務Zuul

一、簡介

  前面討論的知識基本上都是圍繞後臺微服務,但是我們的微服務最終是要提供給客戶端調用的,例如WEB、Android、IOS,畢竟用戶又不關心你的後臺服務架構多麼優秀。那麼問題來了,幾十上百個微服務,都要用反向代理服務器配置,那麼多服務器ip,那個配置文件我都不忍直視。既然我們的服務都已經註冊在Eureka Server中了,那麼直接去代理Eureka Server不就好了,but Eureka Server的服務是不能直接反向代理的,所以Netflix又又又提供給我們一個服務路由器Zuul,簡單來說,Zuul是Netflix的基於JVM的路由器和服務器端負載均衡器。

  一個簡單的微服務架構如下圖,前端需要訪問的接口都通過訪問Zuul集羣代理的接口去訪問,這樣反向代理服務器只需要配置zuul集羣就行了,易擴展易維護;微服務之間都可以通過Feign等相互調用。


二、Zuul路由功能

  創建Zuul項目,首先肯定要依賴Zuul,其次我們要去發現Eureka裏面的服務,所以要成爲Eureka client,完整pom如下。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nan</groupId>
    <artifactId>zuuldemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>zuuldemo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Dalston.SR2</spring-cloud.version>
    </properties>

    <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>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

  Zuul項目我們也不需要編寫自己的業務代碼,只需在項目啓動入口加上對應的註解即可。

package com.nan.zuuldemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ZuuldemoApplication {

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

  最後就是配置文件了,新增主要是zuul.routes的配置。每一個服務我們都給它取個名字,配置好訪問路徑和在Eureka裏面註冊的服務id。

spring:
  application:
    name: ZuulDemo
server:
  port: 9200
eureka:
  client:
    service-url:
      defaultZone: http://node01:8081/eureka/,http://node02:8082/eureka/
zuul:
  routes:
    api-hello:
      path: /api-hello/**
      serviceId: HelloService
    api-user:
      path: /api-user/**
      serviceId: UserService

  然後啓動Eureka server以及2個service,最後啓動zuul項目。在瀏覽器分別輸入http://localhost:9200/api-hello/hello?name=秦始皇和http://localhost:9200/api-user/user/2,可以看到頁面回顯的內容就是調用了HelloService和UserService的結果,這就是Zuul的路由功能。

三、Zuul過濾功能

  Zuul路由功能很酷也很重要,因爲是核心嘛,但是還有一個功能也很重要,就是過濾。一般來說我們提供對外的接口是有一定權限控制的,比如微信或支付寶的接口,都有一大推token、appkey之類的,所以我們也實現一下權限過濾,不過我們只實現一下簡單的IP過濾,複雜的權限控制,基本也差不多是這個套路,我們只對HelloService進行過濾,代碼如下。


package com.nan.zuuldemo.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.regex.Pattern;

@Component
public class IpFilter extends ZuulFilter {

    Logger logger = LoggerFactory.getLogger(IpFilter.class);

    @Value("${zuul.routes.api-hello.path}")
    private String apiHelloPath;

    /**
     * 獲取過濾器類型,可以返回下面4個字符串之一
     * pre:可以在請求被路由之前調用
     * route:在路由請求時候被調用
     * post:在route和error過濾器之後被調用
     * error:處理請求時發生錯誤時被調用
     */
    @Override
    public String filterType() {
        //ip過濾器,表示是前置過濾器
        return "pre";
    }

    @Override
    public int filterOrder() {
        //過濾器優先級,數字越大,優先級越低,其實就是過濾器鏈裏面用到的
        return 0;
    }

    /**
     * 是否執行該過濾器
     * true 過濾 false 不過濾
     */
    @Override
    public boolean shouldFilter() {
        //我們要執行此過濾器,所以返回true
        return true;
    }

    /**
     * 過濾邏輯方法
     */
    @Override
    public Object run() {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();
        if (request.getRequestURI().startsWith(apiHelloPath.split("\\*")[0])) {
            String ip = getIpAddress(request);
            logger.info("Request real ip = " + ip);
            //ip不是127.0.0.1,禁止訪問
            if (!ip.equals("127.0.0.1")) {
                //不路由該請求
                requestContext.setSendZuulResponse(false);
                //設置錯誤碼
                requestContext.setResponseStatusCode(401);
                //設置返回內容
                requestContext.setResponseBody("Sorry,request ip forbidden");
            }
        }
        return null;
    }

    /**
     * 獲取真實請求ip
     */
    private String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

  代碼裏面都有註釋,就不囉嗦了,簡單繼承Zuul提供的抽象類,實現抽象方法。然後重新啓動zuuldemo項目,在瀏覽器輸入http://127.0.0.1:9200/api-hello/hello?name=秦始皇,url的hostname一定要用ip哦,不然會有意想不到的效果,頁面回顯正常;如果用真實ip訪問,比如我的是http://192.168.0.104:9200/api-hello/hello?name=秦始皇,頁面就會回顯Sorry,request ip forbidden,甚至隨便輸入個地址,http://192.168.0.104:9200/api-hello/9527,正常應該市404錯誤,但也會被攔截掉。


  以上就是筆者目前所學,還是很初級的內容,如果有什麼錯誤和遺漏,還請見諒並指正,不勝感激。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章