范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

springbootgateway记录请求和响应日志

  springboot gateway 记录请求和响应日志
  spring cloud gateway是基于webflux的项目,因而不能跟使用spring mvc一样直接获取request body,因此需要重新构造再转发。
  如果我们在spring cloud gateway 封装之前读取了一次request body,比如打印request body日志,在下游获取数据的时候会出现错误:[spring cloud] [error] java.lang.IllegalStateException: Only one connection receive subscriber allowed. 因为request body只能读取一次,它是属于消费类型的。
  出现这样的原因是InputStream的read()方法内部有一个postion,标志当前流被读取到的位置,每读取一次,该标志就会移动一次,如果读到最后,read()会返回-1,表示已经读取完了。如果想要重新读取则需要调用reset()方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读了。调用reset()方法的前提是已经重写了reset()方法,当然能否reset也是有条件的,它取决于markSupported()方法是否返回true,InputStream默认不实现reset(),并且markSupported()默认也是返回false。综上,InputStream默认不实现reset的相关方法,而ServletInputStream也没有重写reset的相关方法,这样就无法重复读取流,这就是我们从request对象中获取的输入流就只能读取一次的原因。
  直接上代码 import lombok.Data;  import java.io.Serializable; import java.util.Date;  @Data public class GatewayLog implements Serializable {     private static final long serialVersionUID = 1983879536575766072L;     /**访问实例*/     private String targetServer;     /**请求路径*/     private String requestPath;     /**请求方法*/     private String requestMethod;     /**协议 */     private String schema;     /**请求体*/     private String requestBody;     /**响应体*/     private String responseData;     /**请求ip*/     private String ip;    /**请求时间*/     private Date requestTime;    /**响应时间*/     private Date responseTime;     /**执行时间*/     private long executeTime;     /**返回码*/     private long code;     /**返回数据类型*/     private String responseContentType;     /**请求数据类型*/     private String requestContentType;     /**请求用户id*/     private String userId;   }
  public interface AccessLogService {      void saveAccessLog(GatewayLog gatewayLog);  }
  import cn.hutool.core.collection.CollectionUtil; import com.shouwei.gateway.entity.GatewayLog; import com.shouwei.gateway.service.AccessLogService; import com.shouwei.gateway.utils.IpUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.reactivestreams.Publisher; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.support.BodyInserterContext; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.core.Ordered; import org.springframework.core.ResolvableType; import org.springframework.core.io.buffer.*; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.multipart.FormFieldPart; import org.springframework.http.codec.multipart.Part; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequestDecorator; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.HandlerStrategies; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono;  import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern;  /**  * /**  * 全局拦截器,作用所有的微服务  * 1. 对请求的API调用过滤,记录接口的请求时间,方便日志审计、告警、分析等运维操作  * 2. 后期可以扩展对接其他日志系统  */ @Slf4j @Component public class AccessLogFilter implements GlobalFilter, Ordered {      /**      * default HttpMessageReader.      */     private static final List> MESSAGE_READERS = HandlerStrategies.withDefaults().messageReaders();       @Autowired     private AccessLogService accessLogService;      private final List> messageReaders = HandlerStrategies.withDefaults().messageReaders();       /**      * 顺序必须是<-1,否则标准的NettyWriteResponseFilter将在您的过滤器得到一个被调用的机会之前发送响应      * 也就是说如果不小于 -1 ,将不会执行获取后端响应的逻辑      *      * @return      */     @Override     public int getOrder() {         return -100;     }      @Override     @SuppressWarnings("unchecked")     public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {          ServerHttpRequest request = exchange.getRequest();          // 请求路径         String requestPath = request.getPath().pathWithinApplication().value();          Route route = getGatewayRoute(exchange);          String ipAddress = IpUtils.getIpAddress(request);          GatewayLog gatewayLog = new GatewayLog();         gatewayLog.setSchema(request.getURI().getScheme());         gatewayLog.setRequestMethod(request.getMethodValue());         gatewayLog.setRequestPath(requestPath);         gatewayLog.setTargetServer(route.getId());         gatewayLog.setRequestTime(new Date());         gatewayLog.setIp(ipAddress);          MediaType mediaType = request.getHeaders().getContentType();         gatewayLog.setRequestContentType(mediaType.getType() + "/" + mediaType.getSubtype());         //json格式         if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {             return writeBodyLog(exchange, chain, gatewayLog);         }          //form-data格式         else if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(mediaType) || MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)) {             return readFormData(exchange, chain, gatewayLog);         }          //其他格式         else {             return writeBasicLog(exchange, chain, gatewayLog);         }     }      private Mono writeBasicLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog accessLog) {          return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {             DataBufferUtils.retain(dataBuffer);             final Flux cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));             final ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {                 @Override                 public Flux getBody() {                     return cachedFlux;                 }                  @Override                 public MultiValueMap getQueryParams() {                     return UriComponentsBuilder.fromUri(exchange.getRequest().getURI()).build().getQueryParams();                 }              };              StringBuilder builder = new StringBuilder();             MultiValueMap queryParams = exchange.getRequest().getQueryParams();             if (CollectionUtil.isNotEmpty(queryParams)) {                 for (Map.Entry> entry : queryParams.entrySet()) {                     builder.append(entry.getKey()).append("=").append(StringUtils.join(entry.getValue(), ","));                 }             }              accessLog.setRequestBody(builder.toString());              //获取响应体             ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, accessLog);              return chain.filter(exchange.mutate().request(mutatedRequest).response(decoratedResponse).build())                     .then(Mono.fromRunnable(() -> {                         // 打印日志                         writeAccessLog(accessLog);                     }));          });      }      /**      * 读取form-data数据      *      * @param exchange      * @param chain      * @param accessLog      * @return      */     private Mono readFormData(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog accessLog) {          return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {             DataBufferUtils.retain(dataBuffer);             final Flux cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));             final ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {                 @Override                 public Flux getBody() {                     return cachedFlux;                 }                  @Override                 public MultiValueMap getQueryParams() {                     return UriComponentsBuilder.fromUri(exchange.getRequest().getURI()).build().getQueryParams();                 }             };              final HttpHeaders headers = exchange.getRequest().getHeaders();             if (headers.getContentLength() == 0) {                 return chain.filter(exchange);             }              ResolvableType resolvableType;             if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(headers.getContentType())) {                 resolvableType = ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Part.class);             } else {                 //解析 application/x-www-form-urlencoded                 resolvableType = ResolvableType.forClass(String.class);             }              return MESSAGE_READERS.stream().filter(reader -> reader.canRead(resolvableType, mutatedRequest.getHeaders().getContentType())).findFirst()                     .orElseThrow(() -> new IllegalStateException("no suitable HttpMessageReader.")).readMono(resolvableType, mutatedRequest, Collections.emptyMap()).flatMap(resolvedBody -> {                         if (resolvedBody instanceof MultiValueMap) {                             LinkedMultiValueMap map = (LinkedMultiValueMap) resolvedBody;                             if (CollectionUtil.isNotEmpty(map)) {                                  StringBuilder builder = new StringBuilder();                                  final Part bodyPartInfo = (Part) ((MultiValueMap) resolvedBody).getFirst("body");                                 if (bodyPartInfo instanceof FormFieldPart) {                                     String body = ((FormFieldPart) bodyPartInfo).value(); //                                    log.info("body ==== " + body);                                     builder.append("body=").append(body);                                 }                                  final Part uidPartInfo = (Part) ((MultiValueMap) resolvedBody).getFirst("uid");                                 if (uidPartInfo instanceof FormFieldPart) {                                     String uid = ((FormFieldPart) uidPartInfo).value(); //                                    log.info("uid ==== " + uid);                                     accessLog.setUserId(uid);                                     if (builder.length() > 0) {                                         builder.append("&uid=").append(uid);                                     } else {                                         builder.append("uid=").append(uid);                                     }                                  }                                  final Part timeStampPartInfo = (Part) ((MultiValueMap) resolvedBody).getFirst("timeStamp");                                 if (timeStampPartInfo instanceof FormFieldPart) {                                     String timeStamp = ((FormFieldPart) timeStampPartInfo).value(); //                                    log.info("timeStamp ==== " + timeStamp );                                     if (builder.length() > 0) {                                         builder.append("&timeStamp=").append(timeStamp);                                     } else {                                         builder.append("timeStamp=").append(timeStamp);                                     }                                  }                                  final Part tokenPartInfo = (Part) ((MultiValueMap) resolvedBody).getFirst("token");                                 if (tokenPartInfo instanceof FormFieldPart) {                                     String token = ((FormFieldPart) tokenPartInfo).value(); //                                    log.info("token ==== " + token);                                     if (builder.length() > 0) {                                         builder.append("&token=").append(token);                                     } else {                                         builder.append("token=").append(token);                                     }                                  }                                  final Part signPartInfo = (Part) ((MultiValueMap) resolvedBody).getFirst("sign");                                 if (signPartInfo instanceof FormFieldPart) {                                     String sign = ((FormFieldPart) signPartInfo).value(); //                                    log.info("sign ==== " + sign);                                     if (builder.length() > 0) {                                         builder.append("&sign=").append(sign);                                     } else {                                         builder.append("sign=").append(sign);                                     }                                 }                                  accessLog.setRequestBody(builder.toString());                             }                         } else {                             accessLog.setRequestBody((String) resolvedBody);                         }                          //获取响应体                         ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, accessLog);                          return chain.filter(exchange.mutate().request(mutatedRequest).response(decoratedResponse).build())                                 .then(Mono.fromRunnable(() -> {                                     // 打印日志                                     writeAccessLog(accessLog);                                 }));                     });          });     }       /**      * 解决 request body 只能读取一次问题,      * 参考: org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory      *      * @param exchange      * @param chain      * @param gatewayLog      * @return      */     @SuppressWarnings("unchecked")     private Mono writeBodyLog(ServerWebExchange exchange, GatewayFilterChain chain, GatewayLog gatewayLog) {         ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);          Mono modifiedBody = serverRequest.bodyToMono(String.class)                 .flatMap(body -> {                     gatewayLog.setRequestBody(body);                     return Mono.just(body);                 });          // 通过 BodyInserter 插入 body(支持修改body), 避免 request body 只能获取一次         BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);         HttpHeaders headers = new HttpHeaders();         headers.putAll(exchange.getRequest().getHeaders());         // the new content type will be computed by bodyInserter         // and then set in the request decorator         headers.remove(HttpHeaders.CONTENT_LENGTH);          CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);          return bodyInserter.insert(outputMessage, new BodyInserterContext())                 .then(Mono.defer(() -> {                     // 重新封装请求                     ServerHttpRequest decoratedRequest = requestDecorate(exchange, headers, outputMessage);                      // 记录响应日志                     ServerHttpResponseDecorator decoratedResponse = recordResponseLog(exchange, gatewayLog);                      // 记录普通的                     return chain.filter(exchange.mutate().request(decoratedRequest).response(decoratedResponse).build())                             .then(Mono.fromRunnable(() -> {                                 // 打印日志                                 writeAccessLog(gatewayLog);                             }));                 }));     }      /**      * 打印日志      *      * @param gatewayLog 网关日志      * @author javadaily      * @date 2021/3/24 14:53      */     private void writeAccessLog(GatewayLog gatewayLog) {         accessLogService.saveAccessLog(gatewayLog);     }       private Route getGatewayRoute(ServerWebExchange exchange) {         return exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);     }       /**      * 请求装饰器,重新计算 headers      *      * @param exchange      * @param headers      * @param outputMessage      * @return      */     private ServerHttpRequestDecorator requestDecorate(ServerWebExchange exchange, HttpHeaders headers,                                                        CachedBodyOutputMessage outputMessage) {         return 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 {                     // TODO: this causes a "HTTP/1.1 411 Length Required" // on                     // httpbin.org                     httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");                 }                 return httpHeaders;             }              @Override             public Flux getBody() {                 return outputMessage.getBody();             }         };     }       /**      * 记录响应日志      * 通过 DataBufferFactory 解决响应体分段传输问题。      */     private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, GatewayLog gatewayLog) {         ServerHttpResponse response = exchange.getResponse();         DataBufferFactory bufferFactory = response.bufferFactory();          return new ServerHttpResponseDecorator(response) {             @Override             public Mono writeWith(Publisher<? extends DataBuffer> body) {                 if (body instanceof Flux) {                     Date responseTime = new Date();                     gatewayLog.setResponseTime(responseTime);                     // 计算执行时间                     long executeTime = (responseTime.getTime() - gatewayLog.getRequestTime().getTime());                      gatewayLog.setExecuteTime(executeTime);                      // 获取响应类型,如果是 json 就打印                     String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR); //                    log.info("originalResponseContentType =========== " + originalResponseContentType);                      gatewayLog.setResponseContentType(originalResponseContentType);                      gatewayLog.setCode(this.getStatusCode().value());  //                    if (ObjectUtils.equals(this.getStatusCode(), HttpStatus.OK) //                            && !StringUtil.isNullOrEmpty(originalResponseContentType) //                            && originalResponseContentType.contains("application/json")) {                      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);                          // 释放掉内存                         DataBufferUtils.release(join);                         String responseResult = new String(content, StandardCharsets.UTF_8); //                            log.info("responseResult =========== " + responseResult);                         gatewayLog.setResponseData(responseResult);                          return bufferFactory.wrap(content);                     }));                 } //                }                  // if body is not a flux. never got there.                 return super.writeWith(body);             }         };     }   }
  用到的依赖     org.springframework.cloud     spring-cloud-starter-gateway     2.2.5.RELEASE          org.projectlombok       lombok       1.18.22            cn.hutool       hutool-all       5.2.5 

年末换机全价位指南,三星GalaxyA系列诚意满满总有一款适合你随着近几年国内智能手机市场竞争的加剧,不少手机厂商都开始聚焦中端手机市场,加大了对中端手机研发的投入力度,三星便是其中厂商之一。2019年,通过深耕面向中端用户的三星GalaxyA2000出头尽享高端旗舰曲面屏!realme真我X7Pro至尊版上市在国内手机市场上,realme去年推出的多款机型都成为了年轻消费者的热门选择,比如真我X7系列。凭借着全新的C位色设计65W智慧闪充6400万像素三摄等卖点,赢得年轻群体的追捧。2税务局未和滴滴出行建立委托代征关系,那滴滴怎么纳税呢最近看到网上有个信息,说天津税务局在答复人大代表是这样说到天津税务局未与滴滴出行建立过委托代征关系。那么我在想,那滴滴司机的个税怎么扣的呢?滴滴公司的成本发票如何取得呢?1增值税增货币资金审计21个实质性审计程序表(很实用)货币资金主要实质性审计程序表序号实质性审计程序(一)库存现金1hr核对库存现金日记账与总账的金额是否相符,检查非记账本位币库存现金的折算汇率及折算金额是否正确。2监盘库存现金(1)李佳琦薇娅一天卖189亿元!税务局出大招!电商缴税都按这个来10月20日晚,今年双十一首轮预售已开始。你买买买了吗?(此处已添加小程序,请到今日头条客户端查看)根据淘宝直播数据,当晚共计有4。88亿人次观看李佳琦薇娅两大头部主播的直播带货,朋友强烈安利的灯饰,月影灯饰我还依稀记得给我强烈安利这款月影灯时的适合自己是并不在意的,因为我觉得不就是家里一个照明的产品吗?何必要搞得那么的漂亮,颜值那么高,能照明不就行了吗?直到入手了之后才发现是真香,他最新的研究,揭开了塔里木盆地古代木乃伊的神秘来源在我国新疆维吾尔自治区的塔里木盆地,曾经发现的数百具自然干尸一直困扰着考古学家。这些遗骸可追溯至公元前2000年至公元200年左右,因其非凡的完好保存状态奢华的服装以及埋葬在远离大中国菜刀输给了德国黑科技?进口货中的智商税,中国人看不清?中国和世界格格不入,只有中国人不想成为第二个美国。现在的中国年轻人已经完全看不上美国人了。越来越多美国青年发出这样的声音,美国可以说是站在了西方国家的金字塔塔顶,连美国人都开始发出2021年年终奖2022年发放,收入到底算哪年的?有些朋友问,2021年考核的年终奖,公司2022年2月才实际发放,那这个年终奖属于职工2021年的收入吗?(此处已添加小程序,请到今日头条客户端查看)如果这笔年终奖我并入综合所得税几个财税高频问题处理近期热点问题汇总办理过户的车辆能否申请退还已缴的车船税个人因肖像权被侵权而取得的补偿是否缴个税总包单位对甲供主体工程能否适用一般计税方法(此处已添加小程序,请到今日头条客户端查看)对白矮星死亡过程的新发现,揭示了恒星衰老的另一种方式我们都以为自己把一切都搞清楚了。当一颗一定质量的恒星停止核聚变,逐渐死亡并变成白矮星时,能让它继续发光的只有剩下的余热。最终,它会冷却到黑暗中,留下一种冰冷死亡的晶体,即黑矮星。我
安卓既然是开源的,为什么还需要谷歌授权?感谢邀请!要回答这个问题,只需了解安卓系统的来龙去脉,就会非常清晰了!(1)首先,必须明确安卓系统(Android)并不是谷歌公司自己开发的,是谷歌公司收购过来的。(2)Andro特斯拉要求多辆订单客户承诺不转卖近日,特斯拉要求消费者签署不转卖承诺函,文件指出,一次或多次累计下单购买多台特斯拉车辆的车主需签署这份承诺函,承诺一年内不得向第三方转售,违者按车辆开票价20支付违约金,否则特斯拉头条新闻之热点大事件头条新闻之热点大事件王兴称美团每送一单亏损超1元美团CEO王兴表示,第四季度公司配送服务营收143亿元,远低于183亿的相关成本,意味着每单亏超过1元。尽管配送服务业务尚未实现盈利突发!巨头被曝裁员,紧急回应!更有裁员工牌堆满一大箱,半层楼的人都空了中国基金报安曼互联网大厂的裁员潮愈演愈烈。近日,社交平台上一份致京东员工的毕业须知引起热议。有多名认证为京东员工的网友发声,京东多条业务线正在裁员,并分享被裁经历。更有网友爆料,有特斯拉在欧洲首座超级工厂开工并交付新车新华社柏林3月23日电(记者张毅荣)美国特斯拉公司设在德国首都柏林附近的超级工厂22日正式开工,并交付了首批30辆新车。这是欧洲首座也是全球第四座特斯拉超级工厂。据当地媒体报道,特一个月内近40款新能源汽车涨价最高超3万元近日,大家热议的话题就是新能源车涨价。一度掀起了涨价潮。据统计,3月以来共有近20个品牌40个车型涨价,幅度从2000元到30000元不等。从目前的涨价情况来看,特斯拉以及小鹏理想恒大布局新能源汽车是机会,还是噱头?据天眼查App显示,3月24日,恒驰国瑞新能源汽车销售(上海)有限公司成立,注册资本1亿元,法定代表人为唐琳,经营范围包括新能源汽车整车销售充电桩销售二手车经销集中式快速充电站等。长安C385实车曝光,对飚特斯拉Model3?随着北京车展的临近,这段时间也有越来越多的新车浮出水面。近日,长安曝光了旗下新车C385的官图,并且在路上也看到了该车的无伪装路试照片。作为长安全新专用电动车打造的纯电新车,新车发博敏电子股份有限公司关于全资子公司收到小鹏汽车定点开发通知书的公告证券代码603936证券简称博敏电子公告编号临2022018本公司董事会及全体董事保证本公告内容不存在任何虚假记载误导性陈述或者重大遗漏,并对其内容的真实性准确性和完整性承担个别及汽车股普跌,大摩称锂价飙升或将打击对新能源车需求格隆汇3月25日丨汽车股普跌,长城汽车跌逾7,比亚迪股份跌逾6,蔚来SW广汽集团理想汽车W雅迪控股跌逾4。5,小鹏汽车吉利汽车跌超3。最近,多家企业上调新能源汽车销售价格,以应对原富途获颁最佳数字金融服务奖项财富管理平台能力受认可中证网讯(记者林倩)日前由香港中资基金业协会彭博联合举办的第七届离岸中资基金奖揭晓,富途控股有限公司(NasdaqFUTU)旗下子公司富途证券国际(香港)有限公司获颁最佳数字金融服