不同於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)的使用