spring cloud gateway的使用
1、springcloud gateway 简介
SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 2.0之前的非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。Reactor模式是非阻塞的,在这样的模式下,不容易在请求过程中修改请求的内容等信息。下面整理了一些 spring cloud gateway 请求转发前|后request信息的修改。
2、springcloud gateway 的搭建(仅是gateway部分、nacos等其他部分自行搭建)
2.1引入相关依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2.2 配置文件的设置
server.port=8760
spring.application.name=sgeocserver-gateway
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.namespace=9ada16fd-0a8a-42b5-9666-67f1c3e73be6
spring.cloud.nacos.config.server-addr=${spring.cloud.nacos.discovery.server-addr}
spring.cloud.nacos.config.namespace=
spring.cloud.nacos.config.group=gis
feign.sentinel.enabled=true
spring.cloud.gateway.routes[1].id=customRedirect
spring.cloud.gateway.routes[1].uri=http://localhost:8080/
spring.cloud.gateway.routes[1].predicates[0]=Path=/sgeocserver/service/visit/**
spring.cloud.gateway.routes[1].filters[0].name=ServiceRedirect
3、springcloud gateway 实现请求的转发并记录日志或处理业务逻辑
@Slf4j
@Component
public class ServiceRedirectGatewayFilterFactory extends AbstractGatewayFilterFactory<ServiceRedirectGatewayFilterFactory.Config> {
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String requestUrl = request.getURI().toString();
// http://localhost:9090/sgeocserver/service/visit/8801b435b32dbe80007da899c7740d61/data/show
// 获取加密后url中的token信息
String token = "";
if (StringUtils.isEmpty(token)) {
// 判定token 密钥逻辑
return setUnauthorizedResponse(exchange, "非法token!", HttpStatus.UNAUTHORIZED);
}
// 获取验证后的真实url地址 例如 https://www.baidu.com
URI uri = null;
try {
uri = new URI("https://www.baidu.com");
} catch (URISyntaxException e) {
e.printStackTrace();
}
// 服务转发
ServerHttpRequest newRequest = request.mutate().uri(uri).build();
Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
// 根据旧路由信息构建新的路由,并替换其路由配置
Route newRoute = Route.async()
.asyncPredicate(route.getPredicate())
.filters(route.getFilters())
.id(route.getId())
.uri(uri).build();
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, newRoute);
ServerWebExchange build = exchange.mutate().request(newRequest).build();
return chain.filter(build).then(Mono.fromRunnable(() -> {
// 记录日志业务 。。。。。。。
}));
};
}
}
4、springcloud gateway 实现对请求body的修改
@Slf4j
@Component
public class GatewayContextFilter implements GlobalFilter, Ordered {
/**
* default HttpMessageReader
*/
private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 上下文
GatewayContext gatewayContext = new GatewayContext();
exchange.getAttributes().put(GatewayContext.CACHE_GATEWAY_CONTEXT, gatewayContext);
HttpHeaders headers = request.getHeaders();
MediaType contentType = headers.getContentType();
long contentLength = headers.getContentLength();
if(contentLength>0 && request.getMethod()== HttpMethod.POST){
if(MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)){
return readBody(exchange, chain,gatewayContext);
}
}
log.debug("[GatewayContext]ContentType:{},Gateway context is set with {}",contentType, gatewayContext);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Integer.MIN_VALUE;
}
/**
* 读取body后再将请求返回Flux 流中
*
* @param exchange the exchange
* @param chain the chain
* @param gatewayContext the gateway context
* @return mono
*/
private Mono<Void> readBody(ServerWebExchange exchange,GatewayFilterChain chain,GatewayContext gatewayContext){
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
DataBufferUtils.retain(dataBuffer);
Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
return ServerRequest.create(mutatedExchange, messageReaders)
.bodyToMono(String.class)
.doOnNext(objectValue -> {
**获取到body信息后设置到 gatewayContext 上下文中**
**修改body逻辑.....**
gatewayContext.setCacheBody(objectValue);
log.debug("[GatewayContext]Read JsonBody:{}",objectValue);
}).then(chain.filter(mutatedExchange));
});
}
}
获取上下文中的请求body信息
GatewayContext gatewayContext = exchange.getAttributeOrDefault(GatewayContext.CACHE_GATEWAY_CONTEXT, new GatewayContext());
MultiValueMap<String, String> queryParams = gatewayContext.getFormData();
5、springcloud gateway 获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> queryParams = request.getQueryParams();
6、springcloud gateway 添加请求头
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
headers.add("token", "123");
7、springcloud gateway 修改返回的内容
@Component
@Slf4j
public class EncryptResponseBodyFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
ServerHttpResponseDecorator response = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (Objects.equals(getStatusCode(), HttpStatus.OK) && body instanceof Flux) {
// 获取ContentType,判断是否返回JSON格式数据
String originalResponseContentType = exchange.getResponse().getHeaders().getFirst("Content-Type");
String encode = exchange.getResponse().getHeaders().getFirst("Content-Encoding");
if(StringUtils.isNotBlank(originalResponseContentType) && originalResponseContentType.contains("application/json") &&
StringUtils.isNotBlank(encode) && "gzip".equals(encode)) {
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
String responseData = new String(uncompress(content), Charsets.UTF_8);
// 修改返回信息的逻辑
byte[] uppedContent = compress(responseData,"UTF-8");
originalResponse.getHeaders().setContentLength(uppedContent.length);
//originalResponse.getHeaders().set("encrypt", "true");
return bufferFactory.wrap(uppedContent);
}));
}
}
return super.writeWith(body);
}
@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body).flatMapSequential(p -> p)); }
};
return chain.filter(exchange.mutate().response(response).build());
}
@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
}
/*解码 gzip*/
public static byte[] uncompress(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return null;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
try {
GZIPInputStream ungzip = new GZIPInputStream(in);
byte[] buffer = new byte[256];
int n;
while ((n = ungzip.read(buffer)) >= 0) {
out.write(buffer, 0, n);
}
} catch (IOException e) {
log.error("gzip uncompress error.", e);
}
return out.toByteArray();
}
/*编码 gzip*/
public static byte[] compress(String str, String encoding) {
if (str == null || str.length() == 0) {
return null;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gzip;
try {
gzip = new GZIPOutputStream(out);
gzip.write(str.getBytes(encoding));
gzip.close();
} catch (IOException e) {
log.error("gzip compress error.", e);
}
return out.toByteArray();
}
}
8、springcloud gateway 自定义异常返回
/**
* 设置异常Response
*
* @param exchange the exchange
* @param message the message
* @param status the status
* @return the unauthorized response
*/
private Mono<Void> setUnauthorizedResponse(ServerWebExchange exchange, String message, HttpStatus status) {
ServerHttpResponse response = exchange.getResponse();
if (!response.getHeaders().containsKey("Content-Type")) {
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
}
response.setStatusCode(status);
return response.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = response.bufferFactory();
return bufferFactory.wrap(JsonUtil.object2Json(ResultUtil.fail(message)).getBytes());
}));
}
9、springcloud gateway 全局异常处理
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
private static Logger logger = LoggerFactory.getLogger(JsonExceptionHandler.class);
public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
/**
* 获取异常属性
*/
@Override
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
int code = HttpStatus.INTERNAL_SERVER_ERROR.value();
Throwable error = super.getError(request);
if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
code = HttpStatus.NOT_FOUND.value();
}
return response(code, this.buildMessage(request, error));
}
/**
* 指定响应处理方法为JSON处理的方法
* @param errorAttributes
*/
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
/**
* 根据code获取对应的HttpStatus
* @param errorAttributes
*/
@Override
protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
int statusCode = (int) errorAttributes.get("code");
return HttpStatus.valueOf(statusCode);
}
/**
* 构建异常信息
* @param request
* @param ex
* @return
*/
private String buildMessage(ServerRequest request, Throwable ex) {
StringBuilder message = new StringBuilder("Failed to handle request [");
message.append(request.methodName());
message.append(" ");
message.append(request.uri());
message.append("]");
if (ex != null) {
message.append(": ");
message.append(ex.getMessage());
}
return message.toString();
}
/**
* 构建返回的JSON数据格式
* @param status 状态码
* @param errorMessage 异常信息
* @return
*/
public static Map<String, Object> response(int status, String errorMessage) {
Map<String, Object> map = new HashMap<>();
map.put("code", status);
map.put("message", errorMessage);
map.put("data", null);
logger.error(map.toString());
return map;
}
}
配置文件
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfiguration {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public ErrorHandlerConfiguration(ServerProperties serverProperties,
ResourceProperties resourceProperties,
ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer,
ApplicationContext applicationContext) {
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
errorAttributes,
this.resourceProperties,
this.serverProperties.getError(),
this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
}