spring cloud gateway2 请求和响应处理

SCG踩坑总结

版本选择

spring-cloud-starter-gateway 2.2.2.RELEASE
spring-cloud-dependencies Hoxton.SR3
spring-boot-dependencies 2.2.6.RELEASE
直接进入正题,查了很多大神提供的资料(踩坑过来,都是泪),总结出一套ok。

全局post请求解密+修改请求体

/**
 * 请求体的封装处理
 */
@Component
@Slf4j
public class RequestBodyFilter implements GlobalFilter, Ordered {


    private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("处理请求体------------order为:" + this.getOrder());
        ServerHttpRequest request = exchange.getRequest();
        //对post的请求体处理
        if (request.getMethod() == HttpMethod.POST) {
            return operationExchange(exchange, chain);
        }
        return chain.filter(exchange);
    }

    private Mono<Void> operationExchange(ServerWebExchange exchange, GatewayFilterChain chain) {
        MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
        // read & modify body
        ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);
//        ServerRequest serverRequest = new DefaultServerRequest(exchange);
        Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
            if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
                // 对原先的body进行操作,处理解密,添加参数等等
                JSONObject jsonObject = JSON.parseObject(body);
                jsonObject.put("demo", "zw");
                //将处理后的数据保存到属性里,后面业务逻辑使用
                exchange.getAttributes().put("newBody", jsonObject);
                return Mono.just(jsonObject.toJSONString());
            }
            return Mono.empty();
        });
        BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(exchange.getRequest().getHeaders());
        headers.remove(HttpHeaders.CONTENT_LENGTH);
        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
        return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
            ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public HttpHeaders getHeaders() {
                    //对请求头修改,可以添加请求头参数等等
                    long contentLength = headers.getContentLength();
                    HttpHeaders httpHeaders = new HttpHeaders();
                    httpHeaders.putAll(super.getHeaders());
                    if (contentLength > 0) {
                        httpHeaders.setContentLength(contentLength);
                    } else {
                        httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                    }
                    return httpHeaders;
                }
                @Override
                public Flux<DataBuffer> getBody() {
                    return outputMessage.getBody();
                }
            };
            return chain.filter(exchange.mutate().request(decorator).build());
        }));
    }

    @Override
    public int getOrder() {
        return -100;
    }
}

业务处理,验签|白名单|等等

/**
 * 验签
 */
@Component
public class TestSignCheckFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //RequestBodyFilter处理后的请求参数json,直接获取用于业务
        Object postBody = exchange.getAttribute("newBody");
        //todo 验签
        if (true) {
            //mock签名验证失败,直接返回错误。下面是个例子,自行封装吧
            ServerHttpResponse response = exchange.getResponse();
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("code", 100002);
            jsonObject.put("msg", "签名有误!");
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            return response.writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(jsonObject.toJSONString().getBytes())));
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -4;
    }

微服务端的响应加密

参考的
https://blog.csdn.net/suchahaerkang/article/details/108204840

到这,肯定会了吧,处理响应体加密后返回

/**
 微服务端的响应加密
 */
@Component
public class GlobalResponseFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("处理响应体------------");
        Object postBody = exchange.getAttribute("postBody");
        System.out.println("封装的请求体还在吗??" + postBody);
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.map(dataBuffer -> {
                        // probably should reuse buffers
                        byte[] content = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(content);
                        //释放掉内存
                        DataBufferUtils.release(dataBuffer);
                        String s = new String(content, StandardCharsets.UTF_8);
                        //修改response之后的字符串
                        System.out.println("获取到响应体:" + s);
                        String lastStr = s;
                        //修改response的值
                        if (StrUtil.containsIgnoreCase(s, "code") && JSONUtil.isJson(s)) {
                            GlobalResponse globalResponse = getGlobalResponse(originalResponse, s);
                            if (globalResponse != null) {
                                lastStr = JSONUtil.toJsonStr(globalResponse);
                            }
                        }

                        byte[] uppedContent = lastStr.getBytes();
                        originalResponse.getHeaders().setContentLength(uppedContent.length);
                        return bufferFactory.wrap(uppedContent);
                    }));
                }
                // if body is not a flux. never got there.
                return super.writeWith(body);
            }
        };
        // replace response with decorator
        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

    @Override
    public int getOrder() {
    	//必须小于默认过滤器 NettyWriteResponseFilter(-1)
        return -2;
    }


    /**
     * @param originalResponse
     * @param originalResponseStr
     * @return
     */
    private GlobalResponse getGlobalResponse(ServerHttpResponse originalResponse, String originalResponseStr) {
        JSONObject jsonObject = JSONUtil.parseObj(originalResponseStr, false);
        if (jsonObject.containsKey("code")) {
            if (jsonObject.getInt("code") == 200 || jsonObject.getInt("code") == 0) {
                GlobalResponse globalResponse = new GlobalResponse();
                globalResponse.setCode(HttpStatus.OK.value());
                globalResponse.setMsg("成功");
                globalResponse.setSuccess(true);

                if (jsonObject.containsKey("count")) {
                    Map<String, Object> map = new HashMap<>();
                    map.put("total", jsonObject.getInt("count"));
                    map.put("list", Optional.ofNullable(jsonObject.getObj("data")).orElse("[]"));
                    globalResponse.setData(map);
                } else {
                    globalResponse.setData(Optional.ofNullable(jsonObject.getObj("data")).orElse("[]"));
                }
                return globalResponse;
            } else {
                GlobalResponse globalResponse = new GlobalResponse();
                if (jsonObject.getInt("code") == 401) {
                    globalResponse.setCode(403);
                    globalResponse.setMsg("处方平台token失效");
                    globalResponse.setData("[]");
                } else {
                    globalResponse.setCode(originalResponse.getStatusCode().value());
                    globalResponse.setMsg(Optional.ofNullable(jsonObject.getStr("msg")).orElse(""));
                    globalResponse.setData(Optional.ofNullable(jsonObject.getObj("data")).orElse("[]"));
                }
                return globalResponse;
            }
        }
        return null;
    }

    /**
     * @param
     * @description: 返回给前端的统一格式
     * @return:
     * @author: sukang
     * @date: 2020/8/24 15:36
     */
    @Data
    private class GlobalResponse implements Serializable {
        private static final long serialVersionUID = 1L;

        private int code;

        private boolean success = false;

        private String msg = "";

        private Object data;
    }
}

web微服务端全局异常处理

@ControllerAdvice
@Slf4j
public class GlobeExceptionHandler {
    @ExceptionHandler
    @ResponseBody
    public JsonResult exceptionHandler(HttpServletRequest req, HttpServletResponse res, Exception e) {
    //自己的业务异常处理,返回一个json
        if (e instanceof RuntimeException) {
            return new JsonResult(e) ;
        }
        //非业务抛错通用json
		return JsonResult.commonError();
    }
}

其他说明

  1. 网关的路由配置,策略,动态路由。网上资料很多,我没遇到问题,一步就成功了,就不赘述了
  2. 网关实在不适合作全局异常处理,为了方便统一处理响应体,微服务端要作全局异常封装处理,返回一个包含错误信息的json响应体,上面已经贴出来
  3. 网关结合redis,防范措施等,可以自行百度查,自定义一个网关的filter即可
  4. filter按照order顺序执行,过滤器业务处理中,拦截请求并返回相应消息(比如签名错误),就不会再走后面的filter
  5. 自定义过滤器权重值的协定,不能乱写哦,自定义的全局过滤器必须和官方默认的兼容一起用。整明白的可以自由发挥,还不是很明白的可以参考我上面例子写的权重值

**由于不熟悉weblux,以前都是用Zuul作为网关,不熟悉gateway2,一路踩了不少坑。 写这篇博客,让后面的同行者少走弯路,加油,共勉!

springcloud版本的选择很重要

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果你想在Spring Cloud Gateway中根据响应体的内容来判断是否进行转发请求或返回结果,可以使用Spring Cloud Gateway提供的GlobalFilter来实现。 具体步骤如下: 1. 在Spring Cloud Gateway中定义一个GlobalFilter,例如: ``` @Component public class MyFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange).then(Mono.fromRunnable(() -> { ServerHttpResponse response = exchange.getResponse(); HttpStatus statusCode = response.getStatusCode(); if (statusCode == HttpStatus.OK) { // 获取响应体内容 byte[] bytes = ((DataBuffer) response.getBody()).asByteBuffer().array(); String responseBody = new String(bytes, StandardCharsets.UTF_8); // 判断响应体内容是否需要转发请求 if (responseBody.contains("需要转发请求的关键字")) { // 构造新的请求URI URI newUri = UriComponentsBuilder.fromUri(exchange.getRequest().getURI()) .host("newhost") .path("newpath") .build() .toUri(); // 构造新的请求对象 ServerHttpRequest newRequest = exchange.getRequest().mutate().uri(newUri).build(); // 转发新的请求 chain.filter(exchange.mutate().request(newRequest).build()); } else { // 返回响应体内容 response.getHeaders().setContentLength(responseBody.length()); DataBuffer buffer = response.bufferFactory().wrap(responseBody.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Flux.just(buffer)); } } })); } } ``` 这个GlobalFilter会在每个请求响应之后执行,获取响应体内容并根据内容判断是否需要转发请求或返回响应体内容。 2. 在Spring Cloud Gateway的配置文件中配置这个GlobalFilter,例如: ``` spring: cloud: gateway: default-filters: - MyFilter ``` 这个配置将这个GlobalFilter添加到默认的过滤器列表中,使其在每个请求中都被执行。 注意:这个GlobalFilter的核心逻辑是根据响应体内容来判断是否需要转发请求或返回响应体内容。如果需要转发请求,可以构造新的请求URI和请求对象,然后使用GatewayFilterChain的filter方法来执行新的请求。如果需要返回响应体内容,可以构造一个新的DataBuffer对象,并使用ServerHttpResponse的writeWith方法来返回响应体内容。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值