介绍
SpringCloud2.x的网关gateway提供了一个在springwebflux之上构建API网关的库。旨在提供一种简单而有效的方法来路由到api,并为它们提供跨领域的关注点,例如:安全性、监视/度量和恢复能力。
spring cloud gateway官网
快速跳转:https://spring.io/projects/spring-cloud-gateway
特色
- 基于springframework5.x springboot2.x构建
- 支持任意路径、谓词(Predicates)、过滤器(filter)路由匹配,路径重写及定制
- 支持熔断处理
- 支持spring云客户端(DiscoveryClient)
- 支持限速自定义处理
基于springframework5.x springboot2.x构建
增加了如jdk9的运行时支持,及采用了jdk8新特性的开发,如函数式,模块化,响应式编程等内容
支持路由匹配及路径重写
如下:
分别对路径路由,主机路由,重写地址,熔断路由,及熔断回调路由进行了响应的举例。如路径路由的原理为:当网关接收到地址为path_route的请求时将会调向 http://httpbin.org/get方法
@Configeration
public class GatewayConfig {
@Bean
public RouteLocator customRoute(RouteLocatorBuilder builder) {
return builder.routes()
.route("rount1", r -> r.path("/get")
.uri("http://httpbin.org"))
.route("host_route", r -> r.host("*.myhost.org")
.uri("http://httpbin.org"))
.route("rewrite_route", r -> r.host("*.rewrite.org")
.filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}"))
.uri("http://httpbin.org"))
.route("hystrix_route", r -> r.host("*.hystrix.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd")))
.uri("http://httpbin.org"))
.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
.uri("http://httpbin.org"))
.route("limit_route", r -> r
.host("*.limited.org").and().path("/anything/**")
.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
.uri("http://httpbin.org"))
.build();
}
}
支持路由的定制
如下基于redis路由配置的实现,原理是实现RouteDefinitionRepository的保存,删除方法即可对路径的配置进行自定义操作。
import com.springboot.cloud.gateway.service.impl.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class RedisRouteRepository implements RouteDefinitionRepository {
@Autowired
private RedisRouteService redisRouteService;
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(redisRouteService.getRouteDefinitions());
}
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return redisRouteService.save(route);
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
return redisRouteService.delete(routeId);
}
}
支持网关级别过滤器的定制
如下,通过实现GlobalFilter接口,实现其filter方法,即可对所有进入网关的请求进行响应的过滤操作,下述代码中主要进行了鉴权的操作。
public class AccessGatewayFilter implements GlobalFilter {
private final static String X_CLIENT_TOKEN_USER = "x-client-token-user";
private final static String X_CLIENT_TOKEN = "x-client-token";
private static final String BEARER = "Bearer";
/**
* 由authentication-client模块提供签权的feign客户端
*/
@Autowired
private IAuthService authService;
/**
* 1.首先网关检查token是否有效,无效直接返回401,不调用签权服务
* 2.调用签权服务器看是否对该请求有权限,有权限进入下一个filter,没有权限返回401
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String authentication = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
String method = request.getMethodValue();
String url = request.getPath().value();
log.debug("url:{},method:{},headers:{}", url, method, request.getHeaders());
//不需要网关签权的url
if (authService.ignoreAuthentication(url)) {
return chain.filter(exchange);
}
// 如果请求未携带token信息, 直接跳出
if (StringUtils.isBlank(authentication) || !authentication.startsWith(BEARER)) {
log.debug("url:{},method:{},headers:{}, 请求未携带token信息", url, method, request.getHeaders());
return unauthorized(exchange);
}
//调用签权服务看用户是否有权限,若有权限进入下一个filter
if (authService.hasPermission(authentication, url, method)) {
ServerHttpRequest.Builder builder = request.mutate();
//TODO 转发的请求都加上服务间认证token
builder.header(X_CLIENT_TOKEN, "TODO zhoutaoo添加服务间简单认证");
//将jwt token中的用户信息传给服务
builder.header(X_CLIENT_TOKEN_USER, authService.getJwt(authentication).getClaims());
return chain.filter(exchange.mutate().request(builder.build()).build());
}
return unauthorized(exchange);
}
/**
* 网关拒绝,返回401
*
* @param
*/
private Mono<Void> unauthorized(ServerWebExchange serverWebExchange) {
serverWebExchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
DataBuffer buffer = serverWebExchange.getResponse()
.bufferFactory().wrap(HttpStatus.UNAUTHORIZED.getReasonPhrase().getBytes());
return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
}
}
支持熔断处理
通过使用注解@EnableCircuitBreaker及配置即可开启熔断处理
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.xgfx.cloud.auth.client")
@EnableCircuitBreaker
@EnableMethodCache(basePackages = "com.xgfx.cloud")
@EnableCreateCacheAnnotation
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
支持spring云客户端(DiscoveryClient)
通过使用注解@EnableDiscoveryClient与@EnableFeignClients及配置即可开启spring云处理
支持限流自定义处理
基于分布式缓存redis的限流处理,通过实现RedisRateLimiter,重写isAllowed来验证是否限流
@Configuration
public class CustomRateLimiter extends RedisRateLimiter {
Config getDefaultConfig() {
return super.getConfig().get("customRateLimiter");
}
public CustomRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate,
RedisScript<List<Long>> script,
@Qualifier("defaultValidator") Validator validator) {
super(redisTemplate, script, validator);
}
@Override
public Mono<Response> isAllowed(String routeId, String id) {
if (null == super.getConfig().get(routeId)) {
getConfig().put(routeId, getDefaultConfig());
}
return super.isAllowed(routeId, id);
}
}
限流配置
如下我们分别配置基于IP,基于路径,基于用户名的限流处理
@Component
public class RequestRateLimiterConfig {
/**
* ip地址限流
*
* @return 限流key
*/
@Bean
@Primary
public KeyResolver remoteAddressKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
/**
* 请求路径限流
*
* @return 限流key
*/
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
/**
* username限流
*
* @return 限流key
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("username"));
}
}
工作原理及核心内容
