spring-cloud-gateway的簡單使用

不同於zuul(基於servlet),spring-cloud-gateway 基於webflux

1.基於zookeeper作爲服務註冊與發現中心的spring-cloud-gateway的使用

使用zookeeper作爲服務註冊中心參考我的這篇文章spring-cloud使用zookeeper作爲服務註冊發現中心(下面會使用到該文章所建工程)

新建工程gateway

<groupId>com.wl.springcloud</groupId>
<artifactId>gateway</artifactId>
<version>1.0-SNAPSHOT</version>

pom.xml

<?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.wl.springcloud</groupId>
  <artifactId>gateway</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>gateway</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring-cloud-config-version>2.0.3.RELEASE</spring-cloud-config-version>
    <spring-cloud-eureka-server>2.0.3.RELEASE</spring-cloud-eureka-server>
    <spring-boot-version>2.0.8.RELEASE</spring-boot-version>
    <spring-cloud-zuul-version>2.0.3.RELEASE</spring-cloud-zuul-version>

    <spring-cloud-eureka-client-version>2.0.3.RELEASE</spring-cloud-eureka-client-version>
    <MainClass>com.wl.springcloud.gateway.GatewayApplication</MainClass>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webflux</artifactId>
      <version>${spring-boot-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
      <version>${spring-boot-version}</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-client -->
    <!--<dependency>-->
    <!--<groupId>org.springframework.cloud</groupId>-->
    <!--<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
    <!--<version>${spring-cloud-eureka-client-version}</version>-->
    <!--</dependency>-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
      <version>${spring-boot-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-config</artifactId>
      <version>${spring-cloud-config-version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
      <version>2.0.0.RELEASE</version>
      <exclusions>
        <exclusion>
          <groupId>org.apache.httpcomponents</groupId>
          <artifactId>httpclient</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.apache.zookeeper</groupId>
          <artifactId>zookeeper</artifactId>
        </exclusion>

      </exclusions>
    </dependency>

    <dependency>
      <groupId>org.apache.zookeeper</groupId>
      <artifactId>zookeeper</artifactId>
      <version>3.4.10</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-gateway -->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-gateway</artifactId>
      <version>2.0.3.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.1.32.Final</version>
    </dependency>


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

  <!-- Package as an executable jar -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>${spring-boot-version}</version>
        <configuration>
          <mainClass>${MainClass}</mainClass>
          <layout>JAR</layout>
        </configuration>
        <!-- repackage  生成兩個 jar.original -->
        <executions>
          <execution>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <!-- 指定maven 打包java 版本 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
    <!-- maven  編譯打包resource 和 java 目錄下所有文件  maven默認資源路徑是resources -->
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/*.*</include>
          <include>*.*</include>
        </includes>
      </resource>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.*</include>
          <include>*.*</include>
        </includes>
      </resource>
    </resources>
  </build>

</project>

注意這裏手動導入了netty-all依賴,如果沒有這個依賴會報Caused by: java.lang.ClassNotFoundException: io.netty.resolver.DefaultAddressResolverGroup

application.yml(修改了zookeeper工程的服務id爲zookeeper1,服務id與path路徑相同時StripPrefix無效)

server:
  port: 8080
spring:
  cloud:
    zookeeper:
      connect-string: 192.168.245.130:2181
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
      - id: zookeeper1
        uri: lb://zookeeper1
        predicates:
        - Path= /zookeeper/**
        filters:
        - StripPrefix=0
        #配置多個路由
#      - id: clientId
#        uri: lb://clientId
#        predicates:
#        - Path= /zookeeper/*
#        filters:
#        - StripPrefix=0

1.spring.cloud.gateway.discovery.locator.enable=true表示啓用服務註冊發現組件(可以通過服務id進行轉發),默認爲false

2.id表示註冊到zookeeper上的服務實例id即spring-application-name

3.uri 與zuul中的url配置基本相同,可以是http路徑也可以是服務id。與zuul直接使用服務id不同,spring-cloud-gateway需要寫成lb://服務id

4.Path 與zuul中path相同 。這裏表示以/zookeeper 開始的路徑將轉發到zookeeper1服務上

5.StripPrefix表示去掉前綴的數量。例如StripPrefix=1 則請求 /name/bar/foo 轉發到目標服務的路徑爲/bar/foo。以此類推值爲2則轉發到目標服務的路徑爲/foo。注意StripPrefix=0等價於zuul中stripPrefix=false,StripPrefix=1等價於zuul中stripPrefix=true

6.predicates(路由方式)、filters(過濾器)配置。下面介紹過濾器時會進行詳細說明。這兩個配置主要針對org.springframework.cloud.gateway.filter.factory、org.springframework.cloud.gateway.handler.predicate中的類,Path、StripPrefix分別是PathRoutePredicateFactory、StripPrefixGatewayFilterFactory的前綴,當然還有更多的pridicate 和 filter

啓動類

package com.wl.springcloud.gateway;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;

/**
 * Created by Administrator on 2019/4/22.
 */
@SpringBootApplication(exclude = {
        DataSourceAutoConfiguration.class,
        DataSourceTransactionManagerAutoConfiguration.class,
        HibernateJpaAutoConfiguration.class,             //不使用數據庫
        GsonAutoConfiguration.class                      //spring-boot2.0.0以上版本需要引入高版本的gson依賴,如果不引用gson依賴需要加此屬性
},scanBasePackages = "com.wl")
public class GatewayApplication {
    private static final Logger logger = LoggerFactory.getLogger(GatewayApplication.class);

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

啓動gateway和zookeeper(注意修改spring.application.name=zookeeper1)工程

瀏覽器輸入http://localhost:8080/zookeeper/zookeeper

 2.spring-cloud-gateway 過濾器

2.1 GatewayFilter

      GatewayFilter是路由綁定的過濾器,只會在綁定的路由中執行

      自定義GatewayFilter有兩種方式:1.通過實現GatewayFilter   2.通過自定義GatewayFilterFactory然後通過RouteDefinitionRouteLocator類中的loadGatewayFilters方法加載到過濾器鏈中

方式一

過濾器

package com.wl.springcloud.gateway.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.ipc.netty.ByteBufFlux;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by Administrator on 2019/4/22.
 */
public class AuthGateWayFilter implements GatewayFilter,Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String token = request.getQueryParams().getFirst("token");
        if(token != null && !token.equals("")){
            return chain.filter(exchange);
        }
        
        ServerHttpResponse response = exchange.getResponse();
        //設置http響應狀態碼
        response.setStatusCode(HttpStatus.BAD_REQUEST);
        //設置響應頭信息Content-Type類型
        response.getHeaders().add("Content-Type","application/json");
        //設置返回json數據
        return response.writeAndFlushWith(Flux.just(ByteBufFlux.just(response.bufferFactory().wrap(getWrapData()))));
        //直接返回(沒有返回數據)
//        return response.setComplete().then();
        //設置返回的數據(非json格式)
//        return response.writeWith(Flux.just(response.bufferFactory().wrap("".getBytes())));

    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    private byte[] getWrapData() {
        Map<String,String> map = new HashMap<>();
        map.put("code","1");
        map.put("msg","token is empty or illegal");
        try {
            return new ObjectMapper().writeValueAsString(map).getBytes();
        } catch (JsonProcessingException e) {
            //
        }
        return "".getBytes();
    }
}

這是一個自定義的校驗token的過濾器,如果token爲空則返回{"msg":"token is empty or illegal","code":"1"}的json數據

1.如果不需要返回數據則直接response.setComplete()

2.如果返回的不是json格式的數據則response.writeWith(Flux.just(response.bufferFactory().wrap(data)))或response.writeWith(Mono.just(response.bufferFactory().wrap(data)))

3.如果返回的是application/json格式的數據則response.writeAndFlushWith(Flux.just(ByteBufFlux.just(response.bufferFactory().wrap(getWrapData()))))或

response.writeAndFlushWith(Mono.just(ByteBufMono.just(response.bufferFactory().wrap(getWrapData()))))

4.Flux 與Mono 的區別參考https://www.jianshu.com/p/611f3667c4d2

5.注意writeWith與writeAndFlushWith的參數的泛型區別,所以在writeAndFlushWith需要使用Flux包裝兩次

將過濾器綁定在某一個路由上

package com.wl.springcloud.gateway.config;

import com.wl.springcloud.gateway.filter.AuthGateWayFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec;
import org.springframework.cloud.gateway.route.builder.PredicateSpec;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.route.builder.UriSpec;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.function.Function;

/**
 * Created by Administrator on 2019/4/23.
 */
@Configuration
public class GatewayFilterConfig {

    @Bean
    public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes().route(new Function<PredicateSpec, Route.AsyncBuilder>() {
            @Override
            public Route.AsyncBuilder apply(PredicateSpec predicateSpec) {
                return predicateSpec.path("/zookeeper/**").filters(new Function<GatewayFilterSpec, UriSpec>() {
                    @Override
                    public UriSpec apply(GatewayFilterSpec gatewayFilterSpec) {
                        return gatewayFilterSpec.filter(new AuthGateWayFilter()).stripPrefix(0);
                    }
                }).uri("lb://zookeeper1").order(0).id("zookeeper1");

            }
        }).build();
    }
}

這裏將過濾器綁定在了/zookeeper/**這個路由上

瀏覽器輸入http://localhost:8080/zookeeper/zookeeper

瀏覽器輸入http://localhost:8080/zookeeper/zookeeper?token=123

 

方式二 使用GatewayFilterFactory 參考StripPrefixGatewayFilterFactory

package com.wl.springcloud.gateway.filter.factory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.ipc.netty.ByteBufFlux;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by Administrator on 2019/4/23.
 */
@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {

    private final String KEY = "token";

    public AuthGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("enabled");
    }


    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                if(!config.enabled){
                    return chain.filter(exchange);
                }
                ServerHttpRequest request = exchange.getRequest();
                String token = request.getQueryParams().getFirst(KEY);
                if(token != null && !token.equals("")){
                    return chain.filter(exchange);
                }

                ServerHttpResponse response = exchange.getResponse();
                //設置http響應狀態碼
                response.setStatusCode(HttpStatus.BAD_REQUEST);
                //設置響應頭信息Content-Type類型
                response.getHeaders().add("Content-Type","application/json");
                //設置返回json數據
                return response.writeAndFlushWith(Flux.just(ByteBufFlux.just(response.bufferFactory().wrap(getWrapData()))));
                //直接返回(沒有返回數據)
//        return response.setComplete().then();
                //設置返回的數據(非json格式)
//        return response.writeWith(Flux.just(response.bufferFactory().wrap("".getBytes())));
            }
            private byte[] getWrapData() {
                Map<String,String> map = new HashMap<>();
                map.put("code","1");
                map.put("msg","token is empty or illegal");
                try {
                    return new ObjectMapper().writeValueAsString(map).getBytes();
                } catch (JsonProcessingException e) {
                    //
                }
                return "".getBytes();
            }
        };

    }

    public static class Config {
        private boolean enabled;

        public boolean isEnabled() {
            return enabled;
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    }
}

修改配置

server:
  port: 8080
spring:
  cloud:
    zookeeper:
      connect-string: 192.168.245.130:2181
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
      - id: zookeeper1
        uri: lb://zookeeper1
        predicates:
        - Path= /zookeeper/**
        filters:
        - StripPrefix=0
        - Auth=true
        #配置多個路由
#      - id: zookeeper1
#        uri: lb://zookeeper1
#        predicates:
#        - Path= /zookeeper/**
#        filters:
#        - StripPrefix=0

配置添加了- Auth=true

注意filters配置裏面的StripPrefix和Auth是StripPrefixGatewayFilterFactory和AuthGatewayFilterFactory的前綴,分別代表了兩個GatewayFilterFactory的名稱,後面的值0和true會通過

@Override
public List<String> shortcutFieldOrder() {
    return Arrays.asList("enabled");
}
public static final String PARTS_KEY = "parts";
@Override
public List<String> shortcutFieldOrder() {
   return Arrays.asList(PARTS_KEY);
}

 

傳遞到其內部類Config的屬性中(屬性名稱與Arrays.asList() 中的字符串相同)即StripPrefixGatewayFilterFactory.Config中的屬性名稱parts與Arrays.asList(PARTS_KEY)中的PARTS_KEY相同;AuthGatewayFilterFactory.Config中的屬性名稱enabled與Arrays.asList("enabled")相同

注意不要忘記AuthGatewayFilterFactory類上的@Component註解

配置- Auth=true之後會通過RouteDefinitionRouteLocator類中的loadGatewayFilters方法加載到過濾器鏈中。

更多GatewayFilterFactory在org.springframework.cloud.gateway.filter.factory包中

 

相同的 predicates的配置對應的有RoutePredicateFactory接口,Path是PathRoutePredicateFactory的前綴,代表了PathRoutePredicateFactory的名稱,後面的值/zookeeper/**會通過

@Override
public List<String> shortcutFieldOrder() {
   return Arrays.asList(PATTERN_KEY, MATCH_OPTIONAL_TRAILING_SEPARATOR_KEY);
}

傳遞到其內部類Config的pattern屬性中。可以看到其內部類中有三個屬性名稱分別對應的與Arrays.asList(PATTERN_KEY, MATCH_OPTIONAL_TRAILING_SEPARATOR_KEY)中的參數名稱相同

更多的RoutePredicateFactory在org.springframework.cloud.gateway.handler.predicate中

其它的RoutePredicateFactory和GatewayFilterFactory以此類推!

 將之前的過濾器註釋掉並重啓應用

瀏覽器輸入http://localhost:8080/zookeeper/zookeeper

 2.2GlobalFilter

顧名思義爲全局的過濾器

自定義GlobalFilter非常簡單隻需實現GlobalFilter即可

package com.wl.springcloud.gateway.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.ipc.netty.ByteBufFlux;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by Administrator on 2019/4/24.
 */
@Component
public class GlobalAuthFilter implements GlobalFilter,Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String token = request.getQueryParams().getFirst("token");
        if(token != null && !token.equals("")){
            return chain.filter(exchange);
        }

        ServerHttpResponse response = exchange.getResponse();
        //設置http響應狀態碼
        response.setStatusCode(HttpStatus.BAD_REQUEST);
        //設置響應頭信息Content-Type類型
        response.getHeaders().add("Content-Type","application/json");
        //設置返回json數據
        return response.writeAndFlushWith(Flux.just(ByteBufFlux.just(response.bufferFactory().wrap(getWrapData()))));
        //直接返回(沒有返回數據)
//        return response.setComplete().then();
        //設置返回的數據(非json格式)
//        return response.writeWith(Flux.just(response.bufferFactory().wrap("".getBytes())));
    }

    private byte[] getWrapData() {
        Map<String,String> map = new HashMap<>();
        map.put("code","1");
        map.put("msg","token is empty or illegal");
        map.put("filter","global");
        try {
            return new ObjectMapper().writeValueAsString(map).getBytes();
        } catch (JsonProcessingException e) {
            //
        }
        return "".getBytes();
    }

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

配置增加一個/abc/**的路由

server:
  port: 8080
spring:
  cloud:
    zookeeper:
      connect-string: 192.168.245.130:2181
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
      - id: zookeeper1
        uri: lb://zookeeper1
        predicates:
        - Path= /zookeeper/**
        filters:
        - StripPrefix=0
        - Auth=true
        #配置多個路由
      - id: zookeeper1
        uri: lb://zookeeper1
        predicates:
        - Path= /abc/**
        filters:
        - StripPrefix=1

瀏覽器輸入

http://localhost:8080/abc/zookeeper/zookeeper

 加上token

 Spring-cloud-gateway更多GatewayFilterFactory 參考https://www.cnblogs.com/liukaifeng/p/10055863.html

Spring Cloud GateWay動態路由配置參考https://blog.csdn.net/lazasha/article/details/84942823

使用eureka作爲服務註冊發現中心參考我的另一篇文章spring-cloud-starter-netflix-zuul(原spring-cloud-starter-zuul)的使用

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