相关文章推荐
伤情的莴苣  ·  kafka 问题 client fails ...·  11 月前    · 
八块腹肌的小熊猫  ·  React ...·  1 年前    · 
飘逸的领结  ·  Python ...·  1 年前    · 
刚毅的长颈鹿  ·  Enabling TLS v1.2 in ...·  1 年前    · 

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