spring cloud gateway 服务网关的使用

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;
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值