这篇文章讲述了如何简单地使用Spring Cloud Gateway,来源于Spring Cloud官方案例,地址https://spring.io/guides/gs/gateway
一、简介
gateway是什么:Spring Cloud Gateway
是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。这里需要注意一下gateway使用的netty+webflux实现,不要加入web依赖,需要加入webflux依赖。
gateway与zuul的区别的简单比较:gateway使用的是异步请求,zuul是同步请求,gateway的数据封装在ServerWebExchange里,zuul封装在RequestContext里。
二、配置转发路由
1、通过配置文件转发路由
spring:
cloud:
gateway:
# 配置所有路由的默认过滤器 这里配置的是gatewayFilter
default-filters:
routes:
- id: server-test # 服务的id
uri: lb://server-test #服务的application名称
order: 0 #路由级别
predicates:
- Path=/bus/** #前缀
filters:
- StripPrefix=1 #去前缀 去几层,1表示去bus **其他留下
2、通过修改启动类转发路由
/**
* gateway 方式实现
*/
@SpringBootApplication
public class ServiceGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceGatewayApplication.class, args);
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
StripPrefixGatewayFilterFactory.Config config =
new StripPrefixGatewayFilterFactory.Config();
config.setParts(1);
return builder.routes()
.route("test", a -> a.path("/test/**")
.filters(b -> b.stripPrefix(1))
.uri("http://localhost:8762"))
.route("api-test2", r -> r.path("/api-test2/**")
.filters(f -> f.stripPrefix(1))
.uri("http://localhost:8762"))
.route("api-test3", r -> r.path("/api-test3/**").
filters(f -> f.stripPrefix(1)).
uri("lb://service-test"))
.build();
}
}
3、使用Hystrix
在spring cloud gateway中可以使用Hystrix。Hystrix是 spring cloud中一个服务熔断降级的组件,在微服务系统有着十分重要的作用。
Hystrix是 spring cloud gateway中是以filter的形式使用的,代码如下:
/**
* gateway 方式实现
*/
@SpringBootApplication
public class ServiceGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceGatewayApplication.class, args);
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
StripPrefixGatewayFilterFactory.Config config =
new StripPrefixGatewayFilterFactory.Config();
config.setParts(1);
return builder.routes()
.route("test", a -> a.path("/test/**")
.filters(b -> b.stripPrefix(1))
.uri("http://localhost:8762"))
.route("api-test2", r -> r.path("/api-test2/**")
.filters(f -> f.stripPrefix(1))
.uri("http://localhost:8762"))
.route("api-test3", r -> r.path("/api-test3/**").
filters(f -> f.stripPrefix(1)).
uri("lb://service-test"))
.route(p -> p
.host("*.hystrix.com")
.filters(f -> f
.hystrix(config -> config
.setName("mycmd")
.setFallbackUri("forward:/fallback")))
.uri(httpUri))
.build();
}
}
在上面的代码中,我们使用了另外一个router,该router使用host去断言请求是否进入该路由,当请求的host有“*.hystrix.com”,都会进入该router,该router中有一个hystrix的filter,该filter可以配置名称、和指向性fallback的逻辑的地址,比如本案例中重定向到了“/fallback”。
现在写的一个“/fallback”的l逻辑:
@RequestMapping("/fallback")
public Mono<String> fallback() {
return Mono.just("fallback");
}
因此带xxx.hystrix.com的请求执行了hystrix的fallback的逻辑。
三、配置过滤器
过滤器:gateway有两种filter,一种是GlobalFilter一种是GatewayFilter,全局过滤器默认对所有路由有效,gatewayFilter需要进行指定。
filter的作用和生命周期:
由filter工作流程点,可以知道filter有着非常重要的作用,在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等。首先需要弄清一点为什么需要网关这一层,这就不得不说下filter的作用了。
1、GlobalFilter 配置全局过滤器
/**
*
* 配置全局过滤器
**/
@Configuration
@Slf4j
public class AccessGatewayFilter implements GlobalFilter{
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, GatewayFilterChain gatewayFilterChain) {
//方法
return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build());
}
}
注意:在这里可以实现记录日志和访问权限校验等
2.自定义GatewayFilter
GatewayFiltery有两种类型的filter,分别为pre和post类型,以下提供一个demo的配置
定义PreGatewayFilter:
public class PreGatewayFilterFactory extends AbstractGatewayFilterFactory {
public PreGatewayFilterFactory() {
super(Config.class);
}
public GatewayFilter apply() {
return apply(o -> {
});
}
@Override
public GatewayFilter apply(Config config) {
// grab configuration from Config object
return (exchange, chain) -> {
//If you want to build a "pre" filter you need to manipulate the
//request before calling change.filter ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
builder.header("GatewayFilter", "PreGatewayFilterFactory success");
//use builder to manipulate the request
return chain.filter(exchange.mutate().request(builder.build()).build());
};
}
public static class Config {
//Put the configuration properties for your filter here
}
}
定义PostGatewayFilter:
public class PostGatewayFilterFactory extends AbstractGatewayFilterFactory {
private Logger logger = LoggerFactory.getLogger(PostGatewayFilterFactory.class);
public PostGatewayFilterFactory() {
super(Config.class);
}
public GatewayFilter apply() {
return apply(o -> {
});
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
logger.info("PostGatewayFilter...");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
//Manipulate the response in some way
}));
};
}
public static class Config {
//Put the configuration properties for your filter here
}
}
四、跨域配置
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator;
import org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.support.DefaultServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
/**
* 跨域允许
*/
@Configuration
public class CorsConfig {
private static final String MAX_AGE = "18000L";
@Bean
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (CorsUtils.isCorsRequest(request)) {
HttpHeaders requestHeaders = request.getHeaders();
ServerHttpResponse response = ctx.getResponse();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders
.getAccessControlRequestHeaders());
if(requestMethod != null){
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(ctx);
};
}
@Bean
public ServerCodecConfigurer serverCodecConfigurer() {
return new DefaultServerCodecConfigurer();
}
/**
* 如果使用了注册中心(如:Eureka),进行控制则需要增加如下配置
*/
@Bean
public RouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient,
DiscoveryLocatorProperties properties) {
return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
}
}