springcloud gateway 打印请求响应报文(现成源码)

一、官方推荐的方式

参照:ModifyRequestBodyGatewayFilterFactory、ModifyResponseBodyGatewayFilterFactory

特点:实现简单,但测试发现并发吞吐量上不去,在8c16G的机器,吞吐量只有不到300/s,rt过长(>500ms)

代码实现:

请求体打印

import com.alibaba.fastjson.JSON;

import com.alibaba.fastjson.serializer.SerializerFeature;

import com.dtyunxi.util.DateUtil;

import com.dtyunxi.yundt.cube.center.gateway.service.RouterService;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;

import org.springframework.cloud.gateway.filter.GlobalFilter;

import org.springframework.cloud.gateway.route.Route;

import org.springframework.cloud.gateway.support.BodyInserterContext;

import org.springframework.cloud.gateway.support.CachedBodyOutputMessage;

import org.springframework.cloud.gateway.support.DefaultServerRequest;

import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;

import org.springframework.core.Ordered;

import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.http.HttpCookie;

import org.springframework.http.HttpHeaders;

import org.springframework.http.HttpMethod;

import org.springframework.http.codec.HttpMessageReader;

import org.springframework.http.server.reactive.ServerHttpRequest;

import org.springframework.http.server.reactive.ServerHttpRequestDecorator;

import org.springframework.stereotype.Component;

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 reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;

 

import java.util.HashMap;

import java.util.List;

import java.util.function.Function;

 

/**

* @author yue.na

* @date 2019/6/18

* @since 2.0.0

*/

@Component

@ConditionalOnProperty(name = "support-datagram-record", havingValue = "true")

public class RequestRecordFilter implements GlobalFilter, Ordered {

Class inClass = String.class;

Class outClass = String.class;

private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

private static Logger logger = LoggerFactory.getLogger(RequestRecordFilter.class);

 

@Autowired

private RouterService routerService;

 

@Override

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

// 请求报文打印

String requestId = "";

Function requestRead = body -> {

// 这个body就是请求体

return Mono.just(body);

};

ServerRequest serverRequest = new DefaultServerRequest(exchange, this.messageReaders);

Mono<?> modifiedBody = serverRequest.bodyToMono(inClass).flatMap(requestRead);

BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass);

HttpHeaders headers = new HttpHeaders();

headers.putAll(exchange.getRequest().getHeaders());

CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);

return bodyInserter.insert(outputMessage, new BodyInserterContext()).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 > 0L) {

httpHeaders.setContentLength(contentLength);

} else {

httpHeaders.set("Transfer-Encoding", "chunked");

}

 

return httpHeaders;

}

 

@Override

public Flux<DataBuffer> getBody() {

return outputMessage.getBody();

}

};

return chain.filter(exchange.mutate().request(decorator).build());

}));

}

 

@Override

public int getOrder() {

return Ordered.HIGHEST_PRECEDENCE;

}

}

 

 

响应体打印

import com.alibaba.fastjson.JSON;

import com.alibaba.fastjson.serializer.SerializerFeature;

import com.dtyunxi.util.DateUtil;

import com.dtyunxi.yundt.cube.center.gateway.service.RouterService;

import org.reactivestreams.Publisher;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;

import org.springframework.cloud.gateway.filter.GlobalFilter;

import org.springframework.cloud.gateway.route.Route;

import org.springframework.cloud.gateway.support.BodyInserterContext;

import org.springframework.cloud.gateway.support.CachedBodyOutputMessage;

import org.springframework.cloud.gateway.support.DefaultClientResponse;

import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;

import org.springframework.core.Ordered;

import org.springframework.core.codec.Decoder;

import org.springframework.core.codec.Encoder;

import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.http.HttpHeaders;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseCookie;

import org.springframework.http.client.reactive.ClientHttpResponse;

import org.springframework.http.codec.ClientCodecConfigurer;

import org.springframework.http.codec.json.Jackson2JsonDecoder;

import org.springframework.http.codec.json.Jackson2JsonEncoder;

import org.springframework.http.server.reactive.ServerHttpResponseDecorator;

import org.springframework.stereotype.Component;

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.client.ExchangeStrategies;

import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;

 

import java.util.HashMap;

import java.util.function.Consumer;

import java.util.function.Function;

 

/**

* @author yue.na

* @date 2019/6/18

* @since 2.0.0

*/

@Component

@ConditionalOnProperty(name = "support-datagram-record", havingValue = "true")

public class ReponseRecordFilter implements GlobalFilter, Ordered {

 

Class inClass = String.class;

Class outClass = String.class;

private static Logger logger = LoggerFactory.getLogger(ReponseRecordFilter.class);

 

private static Decoder decoder = new Jackson2JsonDecoder();

private static Encoder encoder = new Jackson2JsonEncoder();

 

@Autowired

private RouterService routerService;

 

private static final ExchangeStrategies STRATEGIES;

 

static {

STRATEGIES = ExchangeStrategies.builder().codecs(new Consumer<ClientCodecConfigurer>() {

@Override

public void accept(ClientCodecConfigurer clientCodecConfigurer) {

ClientCodecConfigurer.ClientDefaultCodecs clientDefaultCodecs = clientCodecConfigurer.defaultCodecs();

 

clientDefaultCodecs.jackson2JsonDecoder(decoder);

clientDefaultCodecs.jackson2JsonEncoder(encoder);

}

}).build();

}

 

@Override

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

// 响应报文打印

String requestId = "";

Function responseRead = body -> {

// 这个body就是请求体

return Mono.just(body);

};

ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {

@Override

public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {

HttpHeaders httpHeaders = new HttpHeaders();

httpHeaders.add("Content-Type", "application/json");

ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders);

// DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, ExchangeStrategies.withDefaults());

DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, STRATEGIES);

Mono modifiedBody = clientResponse.bodyToMono(inClass).flatMap(responseRead);

BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass);

CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getResponse().getHeaders());

return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {

Flux<DataBuffer> messageBody = outputMessage.getBody();

HttpHeaders headers = this.getDelegate().getHeaders();

if (!headers.containsKey("Transfer-Encoding")) {

messageBody = messageBody.doOnNext((data) -> {

headers.setContentLength((long) data.readableByteCount());

});

}

return this.getDelegate().writeWith(messageBody);

}));

}

 

@Override

public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {

return this.writeWith(Flux.from(body).flatMapSequential((p) -> {

return p;

}));

}

};

return chain.filter(exchange.mutate().response(responseDecorator).build());

}

 

@Override

public int getOrder() {

return Ordered.HIGHEST_PRECEDENCE;

}

 

public static class ResponseAdapter implements ClientHttpResponse {

private final Flux<DataBuffer> flux;

private final HttpHeaders headers;

 

public ResponseAdapter(Publisher<? extends DataBuffer> body, HttpHeaders headers) {

this.headers = headers;

if (body instanceof Flux) {

this.flux = (Flux) body;

} else {

this.flux = ((Mono) body).flux();

}

 

}

 

@Override

public Flux<DataBuffer> getBody() {

return this.flux;

}

 

@Override

public HttpHeaders getHeaders() {

return this.headers;

}

 

@Override

public HttpStatus getStatusCode() {

return null;

}

 

@Override

public int getRawStatusCode() {

return 0;

}

 

@Override

public MultiValueMap<String, ResponseCookie> getCookies() {

return null;

}

}

}

 

 

二、从Flux<DataBuffer> body = request.getBody()中获取

private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {

//获取请求体

Flux<DataBuffer> body = serverHttpRequest.getBody();

 

AtomicReference<String> bodyRef = new AtomicReference<>();

body.subscribe(buffer -> {

CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());

DataBufferUtils.release(buffer);

bodyRef.set(charBuffer.toString());

});

//获取request body

return bodyRef.get();

}

详细实现参考:https://my.oschina.net/zcqshine/blog/2875060

特点:请求体会被截断,为了解决截断问题需要额外配置。但并发吞吐量高与上面一样的配置下,能达到5000/s,RT(100ms~)。

如果你发现了请求体获取不完整,被截断,可以试试重写下面那个类的实现

重写后的HttpServer (看TODO部分)

/*

* Copyright (c) 2011-2019 Pivotal Software Inc, All Rights Reserved.

*

* Licensed under the Apache License, Version 2.0 (the "License");

* you may not use this file except in compliance with the License.

* You may obtain a copy of the License at

*

* http://www.apache.org/licenses/LICENSE-2.0

*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS,

* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

* See the License for the specific language governing permissions and

* limitations under the License.

*/

 

package reactor.ipc.netty.http.server;

 

import io.netty.channel.Channel;

import io.netty.channel.ChannelPipeline;

import io.netty.handler.codec.http.HttpHeaderNames;

import io.netty.handler.codec.http.HttpObjectAggregator;

import io.netty.handler.codec.http.HttpServerCodec;

import io.netty.handler.logging.LoggingHandler;

import io.netty.util.NetUtil;

import org.reactivestreams.Publisher;

import reactor.core.publisher.Mono;

import reactor.core.publisher.MonoSink;

import reactor.ipc.netty.*;

import reactor.ipc.netty.channel.ContextHandler;

import reactor.ipc.netty.http.HttpResources;

import reactor.ipc.netty.options.ServerOptions;

import reactor.ipc.netty.tcp.BlockingNettyContext;

import reactor.ipc.netty.tcp.TcpServer;

 

import java.net.InetSocketAddress;

import java.util.Objects;

import java.util.Properties;

import java.util.function.*;

 

/**

* Base functionality needed by all servers that communicate with clients over HTTP.

*

* @author Stephane Maldini

* @author Violeta Georgieva

*/

public final class HttpServer

implements NettyConnector<HttpServerRequest, HttpServerResponse> {

 

/**

* Build a simple Netty HTTP server listening on localhost (127.0.0.1) and

* port {@literal 8080}.

*

* @return a simple HTTP Server

*/

public static HttpServer create() {

return builder().build();

}

 

/**

* Build a simple Netty HTTP server listening over bind address and port passed

* through the {@link HttpServerOptions}.

* Use {@literal 0} to let the system assign a random port.

*

* @param options the options for the server, including bind address and port.

* @return a simple HTTP server

*/

public static HttpServer create(Consumer<? super HttpServerOptions.Builder> options) {

return builder().options(options).build();

}

 

/**

* Build a simple Netty HTTP server listening on localhost (127.0.0.1) and the provided

* port

* Use {@literal 0} to let the system assign a random port.

*

* @param port the port to listen to, or 0 to dynamically attribute one.

* @return a simple HTTP server

*/

public static HttpServer create(int port) {

return builder().listenAddress(new InetSocketAddress(port)).build();

}

 

/**

* Build a simple Netty HTTP server listening on the provided bind address and

* port {@literal 8080}.

*

* @param bindAddress address to listen for (e.g. 0.0.0.0 or 127.0.0.1)

* @return a simple HTTP server

*/

public static HttpServer create(String bindAddress) {

return builder().bindAddress(bindAddress).build();

}

 

/**

* Build a simple Netty HTTP server listening on the provided bind address and port.

*

* @param bindAddress address to listen for (e.g. 0.0.0.0 or 127.0.0.1)

* @param port the port to listen to, or 0 to dynamically attribute one.

* @return a simple HTTP server

*/

public static HttpServer create(String bindAddress, int port) {

return builder().bindAddress(bindAddress).port(port).build();

}

 

/**

* Creates a builder for {@link HttpServer HttpServer}

*

* @return a new HttpServer builder

*/

public static HttpServer.Builder builder() {

return new HttpServer.Builder();

}

 

private final TcpBridgeServer server;

final HttpServerOptions options;

 

private HttpServer(HttpServer.Builder builder) {

HttpServerOptions.Builder serverOptionsBuilder = HttpServerOptions.builder();

if (Objects.isNull(builder.options)) {

if (Objects.isNull(builder.bindAddress)) {

serverOptionsBuilder.listenAddress(builder.listenAddress.get());

} else {

serverOptionsBuilder.host(builder.bindAddress).port(builder.port);

}

} else {

builder.options.accept(serverOptionsBuilder);

}

if (!serverOptionsBuilder.isLoopAvailable()) {

serverOptionsBuilder.loopResources(HttpResources.get());

}

this.options = serverOptionsBuilder.build();

this.server = new TcpBridgeServer(this.options);

}

 

/**

* Get a copy of the {@link HttpServerOptions} currently in effect.

*

* @return the http server options

*/

public final HttpServerOptions options() {

return this.options.duplicate();

}

 

@Override

public String toString() {

return "HttpServer: " + options.asSimpleString();

}

 

@Override

@SuppressWarnings("unchecked")

public Mono<? extends NettyContext> newHandler(BiFunction<? super HttpServerRequest, ? super

HttpServerResponse, ? extends Publisher<Void>> handler) {

Objects.requireNonNull(handler, "handler");

return server.newHandler((BiFunction<NettyInbound, NettyOutbound, Publisher<Void>>) handler);

}

 

/**

* Define routes for the server through the provided {@link HttpServerRoutes} builder.

*

* @param routesBuilder provides a route builder to be mutated in order to define routes.

* @return a new {@link Mono} starting the router on subscribe

*/

public Mono<? extends NettyContext> newRouter(Consumer<? super HttpServerRoutes>

routesBuilder) {

Objects.requireNonNull(routesBuilder, "routeBuilder");

HttpServerRoutes routes = HttpServerRoutes.newRoutes();

routesBuilder.accept(routes);

return newHandler(routes);

}

 

/**

* Start an HttpServer with routes defined through the provided {@link HttpServerRoutes}

* builder, in a blocking fashion, and wait for it to finish initializing.

* The returned {@link BlockingNettyContext} class offers a simplified API around operating

* the client/server in a blocking fashion, including to {@link BlockingNettyContext#shutdown() shut it down}.

*

* @param routesBuilder provides a route builder to be mutated in order to define routes.

* @return a {@link BlockingNettyContext}

*/

public BlockingNettyContext startRouter(Consumer<? super HttpServerRoutes> routesBuilder) {

Objects.requireNonNull(routesBuilder, "routeBuilder");

HttpServerRoutes routes = HttpServerRoutes.newRoutes();

routesBuilder.accept(routes);

return start(routes);

}

 

/**

* Start an HttpServer with routes defined through the provided {@link HttpServerRoutes}

* builder, in a fully blocking fashion, not only waiting for it to

* initialize but also blocking during the full lifecycle of the server.

* Since most servers will be long-lived, this is more adapted to running a server

* out of a main method, only allowing shutdown of the servers through sigkill.

* <p>

* Note that a {@link Runtime#addShutdownHook(Thread) JVM shutdown hook} is added

* by this method in order to properly disconnect the client/server upon receiving

* a sigkill signal.

*

* @param routesBuilder provides a route builder to be mutated in order to define routes.

*/

public void startRouterAndAwait(Consumer<? super HttpServerRoutes> routesBuilder) {

startRouterAndAwait(routesBuilder, null);

}

 

/**

* Start an HttpServer with routes defined through the provided {@link HttpServerRoutes}

* builder, in a fully blocking fashion, not only waiting for it to

* initialize but also blocking during the full lifecycle of the server.

* Since most servers will be long-lived, this is more adapted to running a server

* out of a main method, only allowing shutdown of the servers through sigkill.

* <p>

* Note that a {@link Runtime#addShutdownHook(Thread) JVM shutdown hook} is added

* by this method in order to properly disconnect the client/server upon receiving

* a sigkill signal.

*

* @param routesBuilder provides a route builder to be mutated in order to define routes.

* @param onStart an optional callback to be invoked once the server has finished initializing.

*/

public void startRouterAndAwait(Consumer<? super HttpServerRoutes> routesBuilder,

Consumer<BlockingNettyContext> onStart) {

Objects.requireNonNull(routesBuilder, "routeBuilder");

HttpServerRoutes routes = HttpServerRoutes.newRoutes();

routesBuilder.accept(routes);

startAndAwait(routes, onStart);

}

 

static final LoggingHandler loggingHandler = new LoggingHandler(HttpServer.class);

 

static final boolean ACCESS_LOG_ENABLED =

Boolean.parseBoolean(System.getProperty("reactor.netty.http.server.accessLogEnabled", "false"));

 

static BiPredicate<HttpServerRequest, HttpServerResponse> compressPredicate(

HttpServerOptions options) {

 

int minResponseSize = options.minCompressionResponseSize();

 

if (minResponseSize < 0) {

return null;

}

 

if (minResponseSize == 0) {

return options.compressionPredicate();

}

 

BiPredicate<HttpServerRequest, HttpServerResponse> lengthPredicate =

(req, res) -> {

String length = res.responseHeaders()

.get(HttpHeaderNames.CONTENT_LENGTH);

 

if (length == null) {

return true;

}

 

try {

return Long.parseLong(length) >= minResponseSize;

} catch (NumberFormatException nfe) {

return true;

}

};

 

if (options.compressionPredicate() == null) {

return lengthPredicate;

}

 

return lengthPredicate.and(options.compressionPredicate());

}

 

final class TcpBridgeServer extends TcpServer

implements BiConsumer<ChannelPipeline, ContextHandler<Channel>> {

 

TcpBridgeServer(ServerOptions options) {

super(options);

}

 

@Override

protected ContextHandler<Channel> doHandler(

BiFunction<? super NettyInbound, ? super NettyOutbound, ? extends Publisher<Void>> handler,

MonoSink<NettyContext> sink) {

 

BiPredicate<HttpServerRequest, HttpServerResponse> compressPredicate =

compressPredicate(options);

 

boolean alwaysCompress = compressPredicate == null && options.minCompressionResponseSize() == 0;

 

return ContextHandler.newServerContext(sink,

options,

loggingHandler,

(ch, c, msg) -> {

 

HttpServerOperations ops = HttpServerOperations.bindHttp(ch,

handler,

c,

compressPredicate,

msg);

 

if (alwaysCompress) {

ops.compression(true);

}

 

return ops;

})

.onPipeline(this)

.autoCreateOperations(false);

}

 

@Override

public void accept(ChannelPipeline p, ContextHandler<Channel> c) {

p.addLast(NettyPipeline.HttpCodec, new HttpServerCodec(

options.httpCodecMaxInitialLineLength(),

options.httpCodecMaxHeaderSize(),

options.httpCodecMaxChunkSize(),

options.httpCodecValidateHeaders(),

options.httpCodecInitialBufferSize()));

// TODO 这里增加了HttpAggregator 100000 100k

Properties properties = System.getProperties();

Integer maxLength = Integer.valueOf(String.valueOf(properties.getOrDefault("max.httpcontent.length", "100000")));

p.addLast(NettyPipeline.HttpAggregator, new HttpObjectAggregator(maxLength));

 

if (ACCESS_LOG_ENABLED) {

p.addLast(NettyPipeline.AccessLogHandler, new AccessLogHandler());

}

 

p.addLast(NettyPipeline.HttpServerHandler, new HttpServerHandler(c));

}

 

@Override

protected LoggingHandler loggingHandler() {

return loggingHandler;

}

}

 

public static final class Builder {

private String bindAddress = null;

private int port = 8080;

private Supplier<InetSocketAddress> listenAddress = () -> new InetSocketAddress(NetUtil.LOCALHOST, port);

private Consumer<? super HttpServerOptions.Builder> options;

 

private Builder() {

}

 

/**

* The address to listen for (e.g. 0.0.0.0 or 127.0.0.1)

*

* @param bindAddress address to listen for (e.g. 0.0.0.0 or 127.0.0.1)

* @return {@code this}

*/

public final Builder bindAddress(String bindAddress) {

this.bindAddress = Objects.requireNonNull(bindAddress, "bindAddress");

return this;

}

 

/**

* The {@link InetSocketAddress} to listen on.

*

* @param listenAddress the listen address

* @return {@code this}

*/

public final Builder listenAddress(InetSocketAddress listenAddress) {

Objects.requireNonNull(listenAddress, "listenAddress");

this.listenAddress = () -> listenAddress;

return this;

}

 

/**

* The port to listen to, or 0 to dynamically attribute one.

*

* @param port the port to listen to, or 0 to dynamically attribute one.

* @return {@code this}

*/

public final Builder port(int port) {

this.port = port;

return this;

}

 

/**

* The options for the server, including bind address and port.

*

* @param options the options for the server, including bind address and port.

* @return {@code this}

*/

public final Builder options(Consumer<? super HttpServerOptions.Builder> options) {

this.options = Objects.requireNonNull(options, "options");

return this;

}

 

public HttpServer build() {

return new HttpServer(this);

}

}

}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值