這篇文章講述瞭如何簡單地使用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);
}
}