当前位置: 首页 > news >正文

SpringCloud Gateway 打印请求响应日志、跨域全局配置

version:spring-cloud 2021.0.1,spring-boot 2.6.3,spring-cloud-alibaba 2021.0.1.0

SpringCloudGateway中Post请求参数只能读取一次。
这是因为Gateway网关默认使用的是SpringWebflux,解决这个问题需要容重新构造一个request来替换原先的request。

CacheBodyGlobalFilter这个全局过滤器把原有的request请求中的body内容读出来,并且使用ServerHttpRequestDecorator这个请求装饰器对request进行包装,重写getBody方法,并把包装后的请求放到过滤器链中传递下去。这样后面的过滤器中再使用exchange.getRequest().getBody()来获取body时,实际上就是调用的重载后的getBody方法,获取的最先已经缓存了的body数据。这样就能够实现body的多次读取了。

//
过滤器的Ordered.HIGHEST_PRECEDENCE,即最高优先级的过滤器。优先级设置高的原因是某些系统内置的过滤器可能也会去读body。

Request Log
@Configuration
public class RequestLogGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();URI uri = request.getURI();//String path = request.getPath().value();String path = request.getPath().pathWithinApplication().value();//打印请求路径String requestUrl = this.getOriginalRequestUrl(exchange);//打印请求urlString method = request.getMethodValue();//corsHttpHeaders headers = request.getHeaders();log.info("--> method: {} url: {} header: {}", method, requestUrl, headers);if ("POST".equals(method)) {return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {byte[] bytes = new byte[dataBuffer.readableByteCount()];dataBuffer.read(bytes);String bodyString = new String(bytes, StandardCharsets.UTF_8);log.info("--> {}", bodyString);//log.info("--> {}", formatStr(bodyString)); //formDataexchange.getAttributes().put("POST_BODY", bodyString);DataBufferUtils.release(dataBuffer);Flux<DataBuffer> cachedFlux = Flux.defer(() -> {DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);return Mono.just(buffer);});ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(request) {@Overridepublic Flux<DataBuffer> getBody() { return cachedFlux; }};return chain.filter(exchange.mutate().request(mutatedRequest).build());});} else if ("GET".equals(method)) {MultiValueMap<String, String> queryParams = request.getQueryParams();log.info("请求参数:" + queryParams);return chain.filter(exchange);}return chain.filter(exchange);}private String getOriginalRequestUrl(ServerWebExchange exchange) {ServerHttpRequest req = exchange.getRequest();LinkedHashSet<URI> uris = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);URI requestUri = uris.stream().findFirst().orElse(req.getURI());MultiValueMap<String, String> queryParams = req.getQueryParams();//打印 /api/rest/feign/order/detail// return UriComponentsBuilder.fromPath(requestUri.getRawPath()).queryParams(queryParams).build().toUriString();return requestUri.toString(); // http://localhost:8091/api/rest/feign/order/detail}@Overridepublic int getOrder() {return Ordered.LOWEST_PRECEDENCE;}/*** 去掉FormData 空格,换行和制表符*/private static String formatStr(String str){if (str != null && str.length() > 0) {Pattern p = Pattern.compile("\\s*|\t|\r|\n");Matcher m = p.matcher(str);return m.replaceAll("");}return str;}}

Response Log
响应报文内容分段传输导致不全的解决方法:
1.大报文对fluxBody流循环拼接处理,把fluxBody.map变为fluxBody.buffer().map,从而可以foreach循环Body体了。

2.排除Excel导出。

3.对JSON统一格式处理,日期统一格式处理

@Configuration
public class ResponseLogGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {try {ServerHttpResponse originalResponse = exchange.getResponse();DataBufferFactory bufferFactory = originalResponse.bufferFactory();HttpStatus statusCode = originalResponse.getStatusCode();if (statusCode != HttpStatus.OK) {return chain.filter(exchange);//降级处理返回数据}ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {@Overridepublic Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {if (body instanceof Flux) {Flux<? extends DataBuffer> fluxBody = Flux.from(body);return super.writeWith(fluxBody.buffer().map(dataBuffers -> {// 合并多个流集合,解决返回体分段传输DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();DataBuffer buff = dataBufferFactory.join(dataBuffers);byte[] content = new byte[buff.readableByteCount()];buff.read(content);DataBufferUtils.release(buff);//释放掉内存//排除Excel导出,不是application/json不打印。若请求是上传图片则在最上面判断。MediaType contentType = originalResponse.getHeaders().getContentType();if (!MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {return bufferFactory.wrap(content);}// 构建返回日志String joinData = new String(content);String result = modifyBody(joinData);List<Object> rspArgs = new ArrayList<>();rspArgs.add(originalResponse.getStatusCode().value());rspArgs.add(exchange.getRequest().getURI());rspArgs.add(result);log.info("<-- {} {}\n{}", rspArgs.toArray());getDelegate().getHeaders().setContentLength(result.getBytes().length);return bufferFactory.wrap(result.getBytes());}));} else {log.error("<-- {} 响应code异常", getStatusCode());}return super.writeWith(body);}};return chain.filter(exchange.mutate().response(decoratedResponse).build());} catch (Exception e) {log.error("gateway log exception.\n" + e);return chain.filter(exchange);}}@Overridepublic int getOrder() {return Ordered.HIGHEST_PRECEDENCE;}//返回统一的JSON日期数据 2024-02-23 11:00, null转空字符串private String modifyBody(String jsonStr){JSONObject json = JSON.parseObject(jsonStr, Feature.AllowISO8601DateFormat);JSONObject.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm";return JSONObject.toJSONString(json, (ValueFilter) (object, name, value) -> value == null ? "" : value, SerializerFeature.WriteDateUseDateFormat);}
}//    public void setJSONObjectWriteNullStringAsEmpty() {
//    JSON.toJSONString(data,SerializerFeature.WriteNullStringAsEmpty);
//    JSONObject jsonObj = JSON.parseObject(data, Feature.AllowISO8601DateFormat);
//    JSON.toJSONString(data,SerializerFeature.DisableCircularReferenceDetect);
//    JSONObject.DEFFAULT_DATE_FORMAT ="yyyy-MM-dd HH:mm";
//    JSONObject.toJSONString(jsonObject,SerializerFeature.WriteDateUseDateFormat);
//
//    String dataJson = JSON.toJSONString(data, (ValueFilter) (object, name, value) -> {
//        log.info("data:{} ", data);
//
//        log.info("object:{}, name:{}, value:{}", object, name, value);
//        if (value == null) {
//            return "";
//        }
//        return value;
//    });
//    JSONObject jsonObject = new JSONObject();
//    把json对象转换成字节数组
//    byte[] bits = data.getBytes(StandardCharsets.UTF_8);
//    DataBuffer buffer = originalResponse.bufferFactory().wrap(bits);
//    originalResponse.writeWith(Mono.just(buffer));
//    }}

CorsWebFilterConfig 跨域配置

Gateway跨域处理
https://blog.csdn.net/weixin_43730516/article/details/127040628

@Configuration
public class CorsWebFilterConfig {@Beanpublic CorsWebFilter corsWebFilter() {CorsConfigurationSource corsConfigurationSource = new CorsConfigurationSource() {@Overridepublic CorsConfiguration getCorsConfiguration(ServerWebExchange serverWebExchange) {CorsConfiguration corsConfig = new CorsConfiguration();corsConfig.addAllowedHeader("*");corsConfig.addAllowedMethod("*");corsConfig.addAllowedOriginPattern("*");corsConfig.setMaxAge(1800L);corsConfig.setAllowCredentials(true);return corsConfig;}};return new CorsWebFilter(corsConfigurationSource);
} }


————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/qq_19636353/article/details/126759522

相关文章:

  • 2024!再见前端!
  • 网络编程(8)+字节序处理
  • Redis 五大基本数据类型及其应用场景进阶(缓存预热、雪崩 、穿透 、击穿)
  • SpringCloud-Netflix第一代微服务快速入门
  • u盘拷贝文件管控怎么设置?禁止往U盘拷贝文件的8种方法!(图文详解)
  • Java面试题真题·人才招聘系统项目介绍
  • autogen改变屏幕亮度
  • VMware搭建DVWA靶场
  • 【Vue】为什么 Vue 不使用 React 的分片更新?
  • 如何提升网页加载和跳转速度:Flask 模板渲染 vs Nginx 静态资源处理
  • 第二百五十五节 JPA教程 - JPA 多对多连接表示例
  • Springboot + netty + rabbitmq + myBatis
  • C++冷门知识点1
  • jeesite集成redis,redis工具类
  • Iptables,ufw,firewalld的关系与区别
  • Google 是如何开发 Web 框架的
  • 《Javascript数据结构和算法》笔记-「字典和散列表」
  • 【node学习】协程
  • Django 博客开发教程 16 - 统计文章阅读量
  • Docker: 容器互访的三种方式
  • HTTP--网络协议分层,http历史(二)
  • JWT究竟是什么呢?
  • STAR法则
  • 湖南卫视:中国白领因网络偷菜成当代最寂寞的人?
  • 基于Vue2全家桶的移动端AppDEMO实现
  • 离散点最小(凸)包围边界查找
  • 如何使用 OAuth 2.0 将 LinkedIn 集成入 iOS 应用
  • 学习笔记DL002:AI、机器学习、表示学习、深度学习,第一次大衰退
  • 再谈express与koa的对比
  • 2017年360最后一道编程题
  • 进程与线程(三)——进程/线程间通信
  • # .NET Framework中使用命名管道进行进程间通信
  • # Python csv、xlsx、json、二进制(MP3) 文件读写基本使用
  • #{}和${}的区别是什么 -- java面试
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (C++二叉树05) 合并二叉树 二叉搜索树中的搜索 验证二叉搜索树
  • (C语言)深入理解指针2之野指针与传值与传址与assert断言
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (附源码)springboot 个人网页的网站 毕业设计031623
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • (十八)三元表达式和列表解析
  • .NET 8 中引入新的 IHostedLifecycleService 接口 实现定时任务
  • .NET 中选择合适的文件打开模式(CreateNew, Create, Open, OpenOrCreate, Truncate, Append)
  • .NET/C# 推荐一个我设计的缓存类型(适合缓存反射等耗性能的操作,附用法)
  • .NET/C# 异常处理:写一个空的 try 块代码,而把重要代码写到 finally 中(Constrained Execution Regions)
  • .NET是什么
  • @ohos.systemParameterEnhance系统参数接口调用:控制设备硬件(执行shell命令方式)
  • @RequestBody与@ResponseBody的使用
  • @require_PUTNameError: name ‘require_PUT‘ is not defined 解决方法
  • @TableLogic注解说明,以及对增删改查的影响
  • []C/C++读取串口接收到的数据程序
  • [1]从概念到实践:电商智能助手在AI Agent技术驱动下的落地实战案例深度剖析(AI Agent技术打造个性化、智能化的用户助手)
  • [BZOJ] 2044: 三维导弹拦截
  • [C# 开发技巧]实现属于自己的截图工具