zoukankan      html  css  js  c++  java
  • spring cloud gateway 日志打印

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

  • 相关阅读:
    HDU 2236 无题Ⅱ
    Golden Tiger Claw(二分图)
    HDU 5969 最大的位或 (思维,贪心)
    HDU 3686 Traffic Real Time Query System (图论)
    SCOI 2016 萌萌哒
    Spring Boot支持控制台Banner定制
    构建第一个Spring Boot程序
    Spring Boot重要模块
    Java fastjson JSON和String互相转换
    BCompare 4 Windows激活方法【试用期30天重置】
  • 原文地址:https://www.cnblogs.com/westlin/p/10960251.html
Copyright © 2011-2022 走看看