从api请求中获取访问的具体信息,是一个很常见的功能,这几天在研究springcloud,使用到了其中的gateway,刚好将研究的过程结果都记录下来

0. Version

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent> <properties>
<spring-cloud.version>Greenwich.M3</spring-cloud.version>
</properties> <dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

1. GET请求

对于记录get的请求,gateway中过滤器的exchange.getRequest().getQueryParams()方法就可以获取的到了,关键的代码如下

// 记录请求的参数信息 针对GET 请求
MultiValueMap<String, String> queryParams = request.getQueryParams();
for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
builder.append(entry.getKey()).append("=").append(StringUtils.join(entry.getValue(), ",")).append(",");
}

2. POST请求

对于将请求的参数,存放在body这类的请求(如post),网上的很多方法是从ServerHttpRequest对象的getBody()方法返回的Flux<DataBuffer>进行读取的,依靠响应式编程来进行读取,但在自己demo中都没有办法真正获取到

在参考一遍网友的文章后,可以参照ModifyRequestBodyGatewayFilterFactory提供的类的做法来进行,自己的实现,需要注意的是因为从body中读取出来的内容,是依靠响应式编程的,也就是subscribe()被调用过一次后,不能被springboot内部再调用一次,所以我们需要重新返回一个新的request回去,以下是比较核心的代码

/**
* 过滤器的内部类
*/
private class InnerFilter implements GatewayFilter, Ordered { @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取用户传来的数据类型
MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
ServerRequest serverRequest = new DefaultServerRequest(exchange); // 如果是json格式,将body内容转化为object or map 都可
if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)){
Mono<Object> modifiedBody = serverRequest.bodyToMono(Object.class)
.flatMap(body -> {
recordLog(exchange.getRequest(), body);
return Mono.just(body);
}); return getVoidMono(exchange, chain, Object.class, modifiedBody);
}
// 如果是表单请求
else if(MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)){
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
// .log("modify_request_mono", Level.INFO)
.flatMap(body -> {
recordLog(exchange.getRequest(), body); return Mono.just(body);
}); return getVoidMono(exchange, chain, String.class, modifiedBody);
}
// TODO 这里未来还可以限制一些格式 // 无法兼容的请求,则不读取body,像Get请求这种
recordLog(exchange.getRequest(), "");
return chain.filter(exchange.mutate().request(exchange.getRequest()).build());
} /**
* 优先级默认设置为最高
* @return
*/
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
} /**
* 参照 ModifyRequestBodyGatewayFilterFactory.java 截取的方法
* @param exchange
* @param chain
* @param outClass
* @param modifiedBody
* @return
*/
private Mono<Void> getVoidMono(ServerWebExchange exchange, GatewayFilterChain chain, Class outClass, Mono<?> modifiedBody) {
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass);
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())
// .log("modify_request", Level.INFO)
.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 {
// TODO: this causes a 'HTTP/1.1 411 Length Required' on httpbin.org
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
return httpHeaders;
} @Override
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
};
return chain.filter(exchange.mutate().request(decorator).build());
}));
} /**
* 记录到请求日志中去
* @param request request
* @param body 请求的body内容
*/
private void recordLog(ServerHttpRequest request, Object body) {
// 记录要访问的url
StringBuilder builder = new StringBuilder(" request url: ");
builder.append(request.getURI().getRawPath()); // 记录访问的方法
HttpMethod method = request.getMethod();
if (null != method){
builder.append(", method: ").append(method.name());
} // 记录头部信息
builder.append(", header { ");
for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
builder.append(entry.getKey()).append(":").append(StringUtils.join(entry.getValue(), ",")).append(",");
} // 记录参数
builder.append("} param: ");
// 处理get的请求
if (null != method && HttpMethod.GET.matches(method.name())) {
// 记录请求的参数信息 针对GET 请求
MultiValueMap<String, String> queryParams = request.getQueryParams();
for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
builder.append(entry.getKey()).append("=").append(StringUtils.join(entry.getValue(), ",")).append(",");
}
}
else {
// 从body中读取参数
builder.append(body);
} LogUtil.info(builder.toString());
}
}

关于项目的完整代码,在我的 github

运行情况如下

在日志中的打印为

2019-06-01 16:47:30.442 [reactor-http-nio-2] INFO - request url: /open/check, method: POST, header { Accept:/,Content-type:application/json,User-Agent:curl/7.58.0,Host:localhost:8888,Content-Length:68,} param: {name=zhangsan, address=3678921789378217397128973982189321}

spring cloud gateway 日志打印的更多相关文章

  1. spring cloud gateway之filter篇

    转载请标明出处: https://www.fangzhipeng.com 本文出自方志朋的博客 在上一篇文章详细的介绍了Gateway的Predict,Predict决定了请求由哪一个路由处理,在路由 ...

  2. 微服务网关实战——Spring Cloud Gateway

    导读 作为Netflix Zuul的替代者,Spring Cloud Gateway是一款非常实用的微服务网关,在Spring Cloud微服务架构体系中发挥非常大的作用.本文对Spring Clou ...

  3. Spring Cloud gateway 网关服务二 断言、过滤器

    微服务当前这么火爆的程度,如果不能学会一种微服务框架技术.怎么能升职加薪,增加简历的筹码?spring cloud 和 Dubbo 需要单独学习.说没有时间?没有精力?要学俩个框架?而Spring C ...

  4. spring cloud gateway 自定义GatewayFilterFactory

    spring cloud gateway提供了很多内置的过滤器,那么因为需求的关系,需要自定义实现,并且要可配置,在一番折腾之后,总算是解决了,那么久记录下来 对于自定义的factory,我们可以选择 ...

  5. springcloud3(五) spring cloud gateway动态路由的四类实现方式

    写这篇博客主要是为了汇总下动态路由的多种实现方式,没有好坏之分,任何的方案都是依赖业务场景需求的,现在网上实现方式主要有: 基于Nacos, 基于数据库(PosgreSQL/Redis), 基于Mem ...

  6. Spring Cloud Gateway 没有链路信息,我 TM 人傻了(中)

    本系列是 我TM人傻了 系列第五期[捂脸],往期精彩回顾: 升级到Spring 5.3.x之后,GC次数急剧增加,我TM人傻了 这个大表走索引字段查询的 SQL 怎么就成全扫描了,我TM人傻了 获取异 ...

  7. Spring Cloud Gateway GatewayFilter的使用

    Spring Cloud Gateway GatewayFilter的使用 一.GatewayFilter的作用 二.Spring Cloud Gateway内置的 GatewayFilter 1.A ...

  8. Spring Cloud Gateway夺命连环10问?

    大家好,我是不才陈某~ 最近有很多小伙伴私信我催更 <Spring Cloud 进阶>,陈某也总结了一下,最终原因就是陈某之前力求一篇文章将一个组件重要知识点讲透,这样导致了文章篇幅很长, ...

  9. 从0开始构建你的api网关--Spring Cloud Gateway网关实战及原理解析

    API 网关 API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题 ...

随机推荐

  1. nginx学习之——CentOS6.0下安装nginx

    1.下载对应nginx版本 #注:下载地址:http://nginx.org/download/ wget -c http://nginx.org/download/nginx-1.10.3.tar. ...

  2. 使用Binlog日志恢复误删的MySQL数据

    解到: MySQL的binlog日志是什么?通常是用来干什么的? 模拟一次误删数据的操作,并且使用binlog日志恢复误删的数据. 写这篇文章的初衷,是有一次我真的险些把测试数据库的一张表给删除了,当 ...

  3. Linux下MySQL数据库的备份与恢复

    Linux下MySQL数据库的备份与恢复 作者:Grey 原文地址: Github 语雀 博客园 基于版本 MySQL5.7 Deepin Linux 15.11 xtrabackup-2.4.18 ...

  4. Linux 安装 MySQL 8 数据库(图文详细教程)

    本教程手把手教你如何在 Linux 安装 MySQL 数据库,以 CentOS 7为例. 1. 下载并安装 MySQL 官方的 Yum Repository wget -i -c https://re ...

  5. Go实现ssh执行远端命令及远程终端

    什么是ssh? SSH是一种网络协议,用于计算机之间的加密登录. 如果一个用户从本地计算机,使用SSH协议登录另一台远程计算机,我们就可以认为,这种登录是安全的,即使被中途截获,密码也不会泄露. 互联 ...

  6. 微信小程序手机号解密失败-43001

    今天是2020年1月16号,从昨天下午开始,一直遇到一个问题: 客户在注册会员的时候的第二步,验证手机号的时候,一直提示验证失败,-43001 直接说原因:用户的session_key过期 期间大部分 ...

  7. Spring Boot 2.4版本前后的分组配置变化及对多环境配置结构的影响

    前几天在<Spring Boot 2.4 对多环境配置的支持更改>一文中,给大家讲解了Spring Boot 2.4版本对多环境配置的配置变化.除此之外,还有一些其他配置变化,所以今天我们 ...

  8. Python利用pandas处理数据后画图

    pandas要处理的数据是一个数据表格.代码: 1 import pandas as pd 2 import numpy as np 3 import matplotlib.pyplot as plt ...

  9. 【对线面试官】Java注解

    public void send(String userName) {  try {    // qps 上报    qps(params);    long startTime = System.c ...

  10. h5问题总结

    一.下拉刷新上拉加载 主要依赖一款插件mescroll.js  http://www.mescroll.com/ 简单好用.以前同事的用法是初始化执行执行上拉会调,页数从0开始,下拉重新加载当前地址走 ...