├── README.md ├── .gitignore ├── gateway-test-http ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── stonie │ │ │ └── springnotes │ │ │ ├── HelloWorldController.java │ │ │ └── HttpApplication.java │ └── test │ │ └── java │ │ └── com │ │ └── stonie │ │ └── springnotes │ │ └── HttpApplicationTest.java └── pom.xml ├── gateway-test-websocket ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── stonie │ │ │ └── springnotes │ │ │ ├── WebsocketApplication.java │ │ │ ├── handler │ │ │ └── EchoHandler.java │ │ │ └── config │ │ │ └── WebSocketConfiguration.java │ └── test │ │ └── java │ │ └── com │ │ └── stonie │ │ └── springnotes │ │ └── WebsocketApplicationTest.java └── pom.xml ├── gateway-basic ├── src │ └── main │ │ ├── resources │ │ └── application.properties │ │ └── java │ │ └── com │ │ └── stonie │ │ └── springnotes │ │ └── GatewayApp.java └── pom.xml ├── pom.xml ├── gateway-request-logging-3 ├── src │ └── main │ │ └── java │ │ └── com │ │ └── stonie │ │ └── springnotes │ │ ├── util │ │ ├── DataBufferWrapper.java │ │ ├── DataBufferUtilFix.java │ │ └── GatewayLogUtil.java │ │ ├── GatewayRequestLoggingApp.java │ │ ├── RecorderServerHttpRequestDecorator.java │ │ ├── RecorderServerHttpResponseDecorator.java │ │ ├── HigherRequestRecorderGlobalFilter.java │ │ └── LowerRequestRecorderGlobalFilter.java └── pom.xml ├── gateway-request-logging-2 ├── src │ └── main │ │ └── java │ │ └── com │ │ └── stonie │ │ └── springnotes │ │ ├── GatewayRequestLoggingApp.java │ │ ├── RecorderServerHttpRequestDecorator.java │ │ └── AccessLogGlobalFilter.java └── pom.xml ├── gateway-request-logging-4 ├── src │ └── main │ │ └── java │ │ └── com │ │ └── stonie │ │ └── springnotes │ │ ├── GatewayRequestLoggingApp.java │ │ ├── GatewayUtils.java │ │ ├── GatewayContext.java │ │ ├── RequestLogFilter.java │ │ ├── ResponseLogFilter.java │ │ └── GatewayContextFilter.java └── pom.xml └── gateway-request-logging ├── src └── main │ └── java │ └── com │ └── stonie │ └── springnotes │ ├── GatewayRequestLoggingApp.java │ └── DetailedRequestResponseLogFilter.java └── pom.xml /README.md: -------------------------------------------------------------------------------- 1 | # spring-cloud-gateway-in-action -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # IDEA files 26 | .idea/ 27 | *.iml 28 | 29 | target/ -------------------------------------------------------------------------------- /gateway-test-http/src/main/java/com/stonie/springnotes/HelloWorldController.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class HelloWorldController { 8 | 9 | @RequestMapping("/") 10 | public String index() { 11 | return "Hello World!"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /gateway-test-http/src/main/java/com/stonie/springnotes/HttpApplication.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class HttpApplication { 8 | 9 | public static void main( String[] args ) { 10 | SpringApplication.run(HttpApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /gateway-test-websocket/src/main/java/com/stonie/springnotes/WebsocketApplication.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class WebsocketApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(WebsocketApplication.class,args); 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /gateway-test-http/src/test/java/com/stonie/springnotes/HttpApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.springframework.web.client.RestTemplate; 4 | 5 | public class HttpApplicationTest { 6 | public static void main(String[] args) { 7 | RestTemplate restTemplate = new RestTemplate(); 8 | String responseBody = restTemplate.getForObject("http://localhost:8080", String.class); 9 | System.out.println(responseBody); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /gateway-basic/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8080 2 | 3 | # ----------------------------------------------- 4 | # https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#troubleshooting 5 | # ----------------------------------------------- 6 | 7 | logging.level.org.springframework.core.codec.StringDecoder=TRACE 8 | logging.level.org.springframework.cloud.gateway=TRACE 9 | logging.level.org.springframework.http.server.reactive=TRACE 10 | logging.level.org.springframework.web.reactive=TRACE 11 | logging.level.org.springframework.boot.autoconfigure.web=TRACE 12 | logging.level.reactor.netty=TRACE 13 | 14 | spring.cloud.gateway.httpserver.wiretap=true 15 | spring.cloud.gateway.httpclient.wiretap=true 16 | -------------------------------------------------------------------------------- /gateway-test-websocket/src/main/java/com/stonie/springnotes/handler/EchoHandler.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes.handler; 2 | 3 | import org.springframework.stereotype.Component; 4 | import org.springframework.web.reactive.socket.WebSocketHandler; 5 | import org.springframework.web.reactive.socket.WebSocketSession; 6 | import reactor.core.publisher.Mono; 7 | 8 | @Component 9 | public class EchoHandler implements WebSocketHandler { 10 | @Override 11 | public Mono handle(final WebSocketSession session) { 12 | return session.send( 13 | session.receive() 14 | .map(msg -> { 15 | return session.textMessage("Hello, " + msg.getPayloadAsText()); 16 | })); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.stonie.springnotes 6 | spring-cloud-gateway-in-action 7 | 1.0-SNAPSHOT 8 | pom 9 | 10 | gateway-basic 11 | gateway-test-http 12 | gateway-test-websocket 13 | gateway-request-logging 14 | gateway-request-logging-2 15 | gateway-request-logging-3 16 | gateway-request-logging-4 17 | 18 | 19 | -------------------------------------------------------------------------------- /gateway-request-logging-3/src/main/java/com/stonie/springnotes/util/DataBufferWrapper.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes.util; 2 | 3 | import org.springframework.core.io.buffer.DataBuffer; 4 | import org.springframework.core.io.buffer.DataBufferFactory; 5 | 6 | public class DataBufferWrapper { 7 | private byte[] data; 8 | private DataBufferFactory factory; 9 | 10 | public DataBufferWrapper() { 11 | } 12 | 13 | public DataBufferWrapper(byte[] data, DataBufferFactory factory) { 14 | this.data = data; 15 | this.factory = factory; 16 | } 17 | 18 | public byte[] getData() { 19 | return data; 20 | } 21 | 22 | public DataBufferFactory getFactory() { 23 | return factory; 24 | } 25 | 26 | public DataBuffer newDataBuffer() { 27 | if (factory == null) 28 | return null; 29 | 30 | return factory.wrap(data); 31 | } 32 | 33 | public Boolean clear() { 34 | data = null; 35 | factory = null; 36 | 37 | return Boolean.TRUE; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /gateway-basic/src/main/java/com/stonie/springnotes/GatewayApp.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.gateway.route.RouteLocator; 6 | import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | @SpringBootApplication 10 | public class GatewayApp { 11 | 12 | public static void main( String[] args ) { 13 | SpringApplication.run(GatewayApp.class, args); 14 | } 15 | 16 | /** 17 | * curl -H Content-Type:application/json -X POST --data '{"hello":"world"}' http://127.0.0.1:8080/post 18 | */ 19 | @Bean 20 | public RouteLocator myRoutes(RouteLocatorBuilder builder) { 21 | return builder.routes() 22 | .route(p -> p 23 | .path("/post") 24 | .uri("http://httpbin.org:80")) 25 | .build(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /gateway-test-websocket/src/test/java/com/stonie/springnotes/WebsocketApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.springframework.web.reactive.socket.WebSocketMessage; 4 | import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; 5 | import org.springframework.web.reactive.socket.client.WebSocketClient; 6 | import reactor.core.publisher.Flux; 7 | 8 | import java.net.URI; 9 | import java.time.Duration; 10 | 11 | public class WebsocketApplicationTest { 12 | 13 | public static void main(final String[] args) { 14 | final WebSocketClient client = new ReactorNettyWebSocketClient(); 15 | final String url = "ws://localhost:8080/echo"; 16 | client.execute(URI.create(url), session -> 17 | session.send(Flux.just(session.textMessage("World"))) 18 | .thenMany(session.receive().take(1).map(WebSocketMessage::getPayloadAsText)) 19 | .doOnNext(System.out::println) 20 | .then()) 21 | .block(Duration.ofMillis(5000)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /gateway-request-logging-2/src/main/java/com/stonie/springnotes/GatewayRequestLoggingApp.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.gateway.route.RouteLocator; 6 | import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.util.StringUtils; 9 | 10 | @SpringBootApplication 11 | public class GatewayRequestLoggingApp { 12 | 13 | public static void main( String[] args ) { 14 | SpringApplication.run(GatewayRequestLoggingApp.class, args); 15 | } 16 | 17 | /** 18 | * curl -H Content-Type:application/json -X POST --data '{"hello":"world"}' http://127.0.0.1:8080/post 19 | */ 20 | @Bean 21 | public RouteLocator myRoutes(RouteLocatorBuilder builder) { 22 | return builder.routes() 23 | .route(p -> p 24 | .path("/post") 25 | .uri("http://httpbin.org:80")) 26 | .build(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gateway-request-logging-3/src/main/java/com/stonie/springnotes/GatewayRequestLoggingApp.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.gateway.route.RouteLocator; 6 | import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.util.StringUtils; 9 | 10 | @SpringBootApplication 11 | public class GatewayRequestLoggingApp { 12 | 13 | public static void main( String[] args ) { 14 | SpringApplication.run(GatewayRequestLoggingApp.class, args); 15 | } 16 | 17 | /** 18 | * curl -H Content-Type:application/json -X POST --data '{"hello":"world"}' http://127.0.0.1:8080/post 19 | */ 20 | @Bean 21 | public RouteLocator myRoutes(RouteLocatorBuilder builder) { 22 | return builder.routes() 23 | .route(p -> p 24 | .path("/post") 25 | .uri("http://httpbin.org:80")) 26 | .build(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gateway-request-logging-4/src/main/java/com/stonie/springnotes/GatewayRequestLoggingApp.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.gateway.route.RouteLocator; 6 | import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.util.StringUtils; 9 | 10 | @SpringBootApplication 11 | public class GatewayRequestLoggingApp { 12 | 13 | public static void main( String[] args ) { 14 | SpringApplication.run(GatewayRequestLoggingApp.class, args); 15 | } 16 | 17 | /** 18 | * curl -H Content-Type:application/json -X POST --data '{"hello":"world"}' http://127.0.0.1:8080/post 19 | */ 20 | @Bean 21 | public RouteLocator myRoutes(RouteLocatorBuilder builder) { 22 | return builder.routes() 23 | .route(p -> p 24 | .path("/post") 25 | .uri("http://httpbin.org:80")) 26 | .build(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gateway-request-logging-2/src/main/java/com/stonie/springnotes/RecorderServerHttpRequestDecorator.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.springframework.core.io.buffer.DataBuffer; 4 | import org.springframework.http.server.reactive.ServerHttpRequest; 5 | import org.springframework.http.server.reactive.ServerHttpRequestDecorator; 6 | import reactor.core.publisher.Flux; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class RecorderServerHttpRequestDecorator extends ServerHttpRequestDecorator { 12 | 13 | private final List dataBuffers = new ArrayList<>(); 14 | 15 | public RecorderServerHttpRequestDecorator(ServerHttpRequest delegate) { 16 | super(delegate); 17 | super.getBody().map(dataBuffer -> { 18 | dataBuffers.add(dataBuffer); 19 | return dataBuffer; 20 | }).subscribe(); 21 | } 22 | 23 | @Override 24 | public Flux getBody() { 25 | return copy(); 26 | } 27 | 28 | private Flux copy() { 29 | return Flux.fromIterable(dataBuffers) 30 | .map(buf -> buf.factory().wrap(buf.asByteBuffer())); 31 | } 32 | } -------------------------------------------------------------------------------- /gateway-request-logging/src/main/java/com/stonie/springnotes/GatewayRequestLoggingApp.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.gateway.route.RouteLocator; 6 | import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.util.StringUtils; 9 | 10 | @SpringBootApplication 11 | public class GatewayRequestLoggingApp { 12 | 13 | public static void main( String[] args ) { 14 | SpringApplication.run(GatewayRequestLoggingApp.class, args); 15 | } 16 | 17 | /** 18 | * curl -H Content-Type:application/json -H x-debug=true -X POST --data '{"hello":"world"}' http://127.0.0.1:8080/post 19 | */ 20 | @Bean 21 | public RouteLocator myRoutes(RouteLocatorBuilder builder) { 22 | return builder.routes() 23 | .route(p -> p 24 | .path("/post") 25 | .and() 26 | .readBody(String.class, s -> !StringUtils.isEmpty(s)) 27 | .uri("http://httpbin.org:80")) 28 | .build(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /gateway-request-logging-4/src/main/java/com/stonie/springnotes/GatewayUtils.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.springframework.http.HttpHeaders; 4 | import org.springframework.http.server.reactive.ServerHttpRequest; 5 | 6 | public class GatewayUtils { 7 | 8 | /** 9 | * get Real Ip Address 10 | * @param request ServerHttpRequest 11 | * @return 12 | * @author Evans 13 | */ 14 | public static String getIpAddress(ServerHttpRequest request) { 15 | HttpHeaders headers = request.getHeaders(); 16 | String ip = headers.getFirst("x-forwarded-for"); 17 | if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 18 | ip = headers.getFirst("Proxy-Client-IP"); 19 | } 20 | if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 21 | ip = headers.getFirst("WL-Proxy-Client-IP"); 22 | } 23 | if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 24 | ip = headers.getFirst("X-Real-IP"); 25 | } 26 | if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 27 | ip = request.getRemoteAddress().getAddress().getHostAddress(); 28 | } 29 | if(ip != null && ip.length() > 15 && ip.contains(",")){ 30 | ip = ip.substring(0,ip.indexOf(",")); 31 | } 32 | return ip; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /gateway-request-logging-3/src/main/java/com/stonie/springnotes/RecorderServerHttpRequestDecorator.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import com.stonie.springnotes.util.DataBufferUtilFix; 4 | import com.stonie.springnotes.util.DataBufferWrapper; 5 | import org.springframework.core.io.buffer.DataBuffer; 6 | import org.springframework.http.server.reactive.ServerHttpRequest; 7 | import org.springframework.http.server.reactive.ServerHttpRequestDecorator; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.publisher.Mono; 10 | 11 | public class RecorderServerHttpRequestDecorator extends ServerHttpRequestDecorator { 12 | private DataBufferWrapper data = null; 13 | 14 | public RecorderServerHttpRequestDecorator(ServerHttpRequest delegate) { 15 | super(delegate); 16 | } 17 | 18 | @Override 19 | public Flux getBody() { 20 | synchronized (this) { 21 | Mono mono = null; 22 | if (data == null) { 23 | mono = DataBufferUtilFix.join(super.getBody()) 24 | .doOnNext(d -> this.data = d) 25 | .filter(d -> d.getFactory() != null) 26 | .map(DataBufferWrapper::newDataBuffer); 27 | } else { 28 | mono = Mono.justOrEmpty(data.newDataBuffer()); 29 | } 30 | 31 | return Flux.from(mono); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /gateway-test-http/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.stonie.springnotes 6 | gateway-test-http 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | gateway-test-http 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-parent 20 | 2.2.5.RELEASE 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-maven-plugin 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /gateway-test-websocket/src/main/java/com/stonie/springnotes/config/WebSocketConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes.config; 2 | 3 | import com.stonie.springnotes.handler.EchoHandler; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.core.Ordered; 8 | import org.springframework.web.reactive.HandlerMapping; 9 | import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; 10 | import org.springframework.web.reactive.socket.WebSocketHandler; 11 | import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter; 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | @Configuration 17 | public class WebSocketConfiguration { 18 | 19 | @Autowired 20 | @Bean 21 | public HandlerMapping webSocketMapping(final EchoHandler echoHandler) { 22 | final Map map = new HashMap<>(); 23 | map.put("/echo", echoHandler); 24 | 25 | final SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); 26 | mapping.setOrder(Ordered.HIGHEST_PRECEDENCE); 27 | mapping.setUrlMap(map); 28 | return mapping; 29 | } 30 | 31 | @Bean 32 | public WebSocketHandlerAdapter handlerAdapter() { 33 | return new WebSocketHandlerAdapter(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /gateway-request-logging-3/src/main/java/com/stonie/springnotes/RecorderServerHttpResponseDecorator.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import com.stonie.springnotes.util.DataBufferUtilFix; 4 | import com.stonie.springnotes.util.DataBufferWrapper; 5 | import org.reactivestreams.Publisher; 6 | import org.springframework.core.io.buffer.DataBuffer; 7 | import org.springframework.http.server.reactive.ServerHttpResponse; 8 | import org.springframework.http.server.reactive.ServerHttpResponseDecorator; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | public class RecorderServerHttpResponseDecorator extends ServerHttpResponseDecorator { 13 | private DataBufferWrapper data = null; 14 | 15 | public RecorderServerHttpResponseDecorator(ServerHttpResponse delegate) { 16 | super(delegate); 17 | } 18 | 19 | @Override 20 | public Mono writeWith(Publisher body) { 21 | return DataBufferUtilFix.join(Flux.from(body)) 22 | .doOnNext(d -> this.data = d) 23 | .flatMap(d -> super.writeWith(copy())); 24 | } 25 | 26 | @Override 27 | public Mono writeAndFlushWith(Publisher> body) { 28 | return writeWith(Flux.from(body) 29 | .flatMapSequential(p -> p)); 30 | } 31 | 32 | public Flux copy() { 33 | //如果data为null 就出错了 正好可以调试 34 | DataBuffer buffer = this.data.newDataBuffer(); 35 | if (buffer == null) 36 | return Flux.empty(); 37 | 38 | return Flux.just(buffer); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /gateway-request-logging-3/src/main/java/com/stonie/springnotes/util/DataBufferUtilFix.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes.util; 2 | 3 | import org.springframework.core.io.buffer.DataBuffer; 4 | import org.springframework.core.io.buffer.DataBufferUtils; 5 | import reactor.core.publisher.Flux; 6 | import reactor.core.publisher.Mono; 7 | 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | 12 | //当前版本的spring的DataBufferUtil有bug,官方已经在后续版本修复,这里是参考的后续版本的GIT的代码 13 | public class DataBufferUtilFix { 14 | public static Mono join(Flux dataBuffers) { 15 | return dataBuffers.collectList() 16 | .filter(list -> !list.isEmpty()) 17 | .map(list -> list.get(0).factory().join(list)) 18 | .map(buf -> { 19 | InputStream source = buf.asInputStream(); 20 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 21 | byte[] buff = new byte[4096]; 22 | 23 | try { 24 | int n = 0; 25 | while ((n = source.read(buff)) != -1) { 26 | stream.write(buff, 0, n); 27 | } 28 | } catch (IOException e) { 29 | // 30 | } 31 | 32 | DataBufferWrapper wrapper = new DataBufferWrapper(stream.toByteArray(), buf.factory()); 33 | DataBufferUtils.release(buf); //当前版本的 DataBufferUtils::join 没有这一句 34 | 35 | return wrapper; 36 | }) 37 | .defaultIfEmpty(new DataBufferWrapper()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /gateway-test-websocket/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.stonie.springnotes 7 | gateway-test-websocket 8 | 1.0-SNAPSHOT 9 | jar 10 | 11 | gateway-test-websocket 12 | http://maven.apache.org 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.2.5.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-webflux 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-test 36 | test 37 | 38 | 39 | 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-maven-plugin 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /gateway-basic/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.stonie.springnotes 6 | gateway-basic 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | gateway-basic 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-parent 20 | 2.2.5.RELEASE 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-dependencies 28 | Hoxton.SR1 29 | pom 30 | import 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.cloud 38 | spring-cloud-starter-gateway 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-maven-plugin 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /gateway-request-logging/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.stonie.springnotes 6 | gateway-request-logging 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | gateway-request-logging 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-parent 20 | 2.2.5.RELEASE 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-dependencies 28 | Hoxton.SR1 29 | pom 30 | import 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.cloud 38 | spring-cloud-starter-gateway 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-maven-plugin 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /gateway-request-logging-2/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.stonie.springnotes 6 | gateway-request-logging-2 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | gateway-request-logging-2 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-parent 20 | 2.2.5.RELEASE 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-dependencies 28 | Hoxton.SR1 29 | pom 30 | import 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.cloud 38 | spring-cloud-starter-gateway 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-maven-plugin 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /gateway-request-logging-3/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.stonie.springnotes 6 | gateway-request-logging-3 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | gateway-request-logging-3 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-parent 20 | 2.2.5.RELEASE 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-dependencies 28 | Hoxton.SR1 29 | pom 30 | import 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.cloud 38 | spring-cloud-starter-gateway 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-maven-plugin 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /gateway-request-logging-4/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.stonie.springnotes 6 | gateway-request-logging-4 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | gateway-request-logging-4 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-parent 20 | 2.2.5.RELEASE 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.cloud 27 | spring-cloud-dependencies 28 | Hoxton.SR1 29 | pom 30 | import 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.cloud 38 | spring-cloud-starter-gateway 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-maven-plugin 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /gateway-request-logging-3/src/main/java/com/stonie/springnotes/HigherRequestRecorderGlobalFilter.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import com.stonie.springnotes.util.GatewayLogUtil; 4 | import org.springframework.cloud.gateway.filter.GatewayFilterChain; 5 | import org.springframework.cloud.gateway.filter.GlobalFilter; 6 | import org.springframework.core.Ordered; 7 | import org.springframework.http.server.reactive.ServerHttpRequest; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.web.server.ServerWebExchange; 10 | import reactor.core.publisher.Mono; 11 | 12 | import java.net.URI; 13 | 14 | 15 | @Component 16 | public class HigherRequestRecorderGlobalFilter implements GlobalFilter, Ordered { 17 | @Override 18 | public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { 19 | ServerHttpRequest originalRequest = exchange.getRequest(); 20 | URI originalRequestUrl = originalRequest.getURI(); 21 | 22 | //只记录http的请求 23 | String scheme = originalRequestUrl.getScheme(); 24 | if ((!"http".equals(scheme) && !"https".equals(scheme))) { 25 | return chain.filter(exchange); 26 | } 27 | 28 | String upgrade = originalRequest.getHeaders().getUpgrade(); 29 | if ("websocket".equalsIgnoreCase(upgrade)) { 30 | return chain.filter(exchange); 31 | } 32 | 33 | //在 NettyRoutingFilter 之前执行, 基本上属于倒数第二个过滤器了 34 | //此时的request是 经过各种转换、转发之后的request 35 | //对应日志中的 代理请求 部分 36 | RecorderServerHttpRequestDecorator request = new RecorderServerHttpRequestDecorator(originalRequest); 37 | ServerWebExchange ex = exchange.mutate() 38 | .request(request) 39 | .build(); 40 | 41 | return GatewayLogUtil.recorderRouteRequest(ex) 42 | .then(Mono.defer(() -> chain.filter(ex))); 43 | } 44 | 45 | @Override 46 | public int getOrder() { 47 | //在向业务服务转发前执行 NettyRoutingFilter 或 WebClientHttpRoutingFilter 48 | return Ordered.LOWEST_PRECEDENCE - 10; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /gateway-request-logging-4/src/main/java/com/stonie/springnotes/GatewayContext.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.springframework.http.HttpHeaders; 4 | import org.springframework.util.LinkedMultiValueMap; 5 | import org.springframework.util.MultiValueMap; 6 | 7 | public class GatewayContext { 8 | 9 | public static final String CACHE_GATEWAY_CONTEXT = "cacheGatewayContext"; 10 | 11 | /** 12 | * cache json body 13 | */ 14 | protected String requestBody; 15 | /** 16 | * cache Response Body 17 | */ 18 | protected Object responseBody; 19 | /** 20 | * request headers 21 | */ 22 | protected HttpHeaders requestHeaders; 23 | /** 24 | * cache form data 25 | */ 26 | protected MultiValueMap formData; 27 | /** 28 | * cache all request data include:form data and query param 29 | */ 30 | protected MultiValueMap allRequestData = new LinkedMultiValueMap<>(0); 31 | 32 | public String getRequestBody() { 33 | return requestBody; 34 | } 35 | 36 | public void setRequestBody(String requestBody) { 37 | this.requestBody = requestBody; 38 | } 39 | 40 | public Object getResponseBody() { 41 | return responseBody; 42 | } 43 | 44 | public void setResponseBody(Object responseBody) { 45 | this.responseBody = responseBody; 46 | } 47 | 48 | public HttpHeaders getRequestHeaders() { 49 | return requestHeaders; 50 | } 51 | 52 | public void setRequestHeaders(HttpHeaders requestHeaders) { 53 | this.requestHeaders = requestHeaders; 54 | } 55 | 56 | public MultiValueMap getFormData() { 57 | return formData; 58 | } 59 | 60 | public void setFormData(MultiValueMap formData) { 61 | this.formData = formData; 62 | } 63 | 64 | public MultiValueMap getAllRequestData() { 65 | return allRequestData; 66 | } 67 | 68 | public void setAllRequestData(MultiValueMap allRequestData) { 69 | this.allRequestData = allRequestData; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /gateway-request-logging-3/src/main/java/com/stonie/springnotes/LowerRequestRecorderGlobalFilter.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import com.stonie.springnotes.util.GatewayLogUtil; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.cloud.gateway.filter.GatewayFilterChain; 7 | import org.springframework.cloud.gateway.filter.GlobalFilter; 8 | import org.springframework.core.Ordered; 9 | import org.springframework.http.server.reactive.ServerHttpRequest; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.web.server.ServerWebExchange; 12 | import reactor.core.publisher.Mono; 13 | 14 | import java.net.URI; 15 | 16 | /** 17 | * https://www.jianshu.com/p/350d26dea23f 18 | * https://github.com/giafei/gateway-request-recorder-starter 19 | */ 20 | @Component 21 | public class LowerRequestRecorderGlobalFilter implements GlobalFilter, Ordered { 22 | private Logger logger = LoggerFactory.getLogger(LowerRequestRecorderGlobalFilter.class); 23 | 24 | @Override 25 | public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { 26 | ServerHttpRequest originalRequest = exchange.getRequest(); 27 | URI originalRequestUrl = originalRequest.getURI(); 28 | 29 | //只记录http的请求 30 | String scheme = originalRequestUrl.getScheme(); 31 | if ((!"http".equals(scheme) && !"https".equals(scheme))) { 32 | return chain.filter(exchange); 33 | } 34 | 35 | String upgrade = originalRequest.getHeaders().getUpgrade(); 36 | if ("websocket".equalsIgnoreCase(upgrade)) { 37 | return chain.filter(exchange); 38 | } 39 | 40 | // 在 GatewayFilter 之前执行, 此时的request时最初的request 41 | RecorderServerHttpRequestDecorator request = new RecorderServerHttpRequestDecorator(exchange.getRequest()); 42 | 43 | // 此时的response时 发送回客户端的 response 44 | RecorderServerHttpResponseDecorator response = new RecorderServerHttpResponseDecorator(exchange.getResponse()); 45 | 46 | ServerWebExchange ex = exchange.mutate() 47 | .request(request) 48 | .response(response) 49 | .build(); 50 | 51 | return GatewayLogUtil.recorderOriginalRequest(ex) 52 | .then(Mono.defer(() -> chain.filter(ex))) 53 | .then(Mono.defer(() -> finishLog(ex))); 54 | } 55 | 56 | private Mono finishLog(ServerWebExchange ex) { 57 | return GatewayLogUtil.recorderResponse(ex) 58 | .doOnSuccess(x -> logger.info(GatewayLogUtil.getLogData(ex) + "\n\n\n")); 59 | } 60 | 61 | @Override 62 | public int getOrder() { 63 | //在GatewayFilter之前执行 64 | return - 1; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /gateway-request-logging-2/src/main/java/com/stonie/springnotes/AccessLogGlobalFilter.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.reactivestreams.Publisher; 4 | import org.springframework.cloud.gateway.filter.GatewayFilterChain; 5 | import org.springframework.cloud.gateway.filter.GlobalFilter; 6 | import org.springframework.core.Ordered; 7 | import org.springframework.core.io.buffer.DataBuffer; 8 | import org.springframework.core.io.buffer.DataBufferFactory; 9 | import org.springframework.http.HttpHeaders; 10 | import org.springframework.http.HttpMethod; 11 | import org.springframework.http.server.reactive.ServerHttpRequest; 12 | import org.springframework.http.server.reactive.ServerHttpResponse; 13 | import org.springframework.http.server.reactive.ServerHttpResponseDecorator; 14 | import org.springframework.stereotype.Component; 15 | import org.springframework.web.server.ServerWebExchange; 16 | import reactor.core.publisher.Flux; 17 | import reactor.core.publisher.Mono; 18 | 19 | import java.net.InetSocketAddress; 20 | import java.net.URI; 21 | import java.nio.CharBuffer; 22 | import java.nio.charset.Charset; 23 | import java.nio.charset.StandardCharsets; 24 | import java.util.concurrent.atomic.AtomicReference; 25 | 26 | /** 27 | * https://juejin.im/post/6844903799178428423 28 | */ 29 | @Component 30 | public class AccessLogGlobalFilter implements GlobalFilter, Ordered { 31 | 32 | private static final String REQUEST_PREFIX = "Request Info [ "; 33 | 34 | private static final String REQUEST_TAIL = " ]\r\n"; 35 | 36 | private static final String RESPONSE_PREFIX = "Response Info [ "; 37 | 38 | private static final String RESPONSE_TAIL = " ]"; 39 | 40 | private StringBuilder normalMsg = new StringBuilder(); 41 | 42 | @Override 43 | public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { 44 | ServerHttpRequest request = exchange.getRequest(); 45 | RecorderServerHttpRequestDecorator requestDecorator = new RecorderServerHttpRequestDecorator(request); 46 | InetSocketAddress address = requestDecorator.getRemoteAddress(); 47 | HttpMethod method = requestDecorator.getMethod(); 48 | URI url = requestDecorator.getURI(); 49 | HttpHeaders headers = requestDecorator.getHeaders(); 50 | Flux body = requestDecorator.getBody(); 51 | //读取requestBody传参 52 | AtomicReference requestBody = new AtomicReference<>(""); 53 | body.subscribe(buffer -> { 54 | CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); 55 | requestBody.set(charBuffer.toString()); 56 | }); 57 | String requestParams = requestBody.get(); 58 | normalMsg.append(REQUEST_PREFIX); 59 | normalMsg.append(";header=").append(headers); 60 | normalMsg.append(";params=").append(requestParams); 61 | normalMsg.append(";address=").append(address.getHostName() + address.getPort()); 62 | normalMsg.append(";method=").append(method.name()); 63 | normalMsg.append(";url=").append(url.getPath()); 64 | normalMsg.append(REQUEST_TAIL); 65 | 66 | ServerHttpResponse response = exchange.getResponse(); 67 | 68 | DataBufferFactory bufferFactory = response.bufferFactory(); 69 | normalMsg.append(RESPONSE_PREFIX); 70 | ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) { 71 | @Override 72 | public Mono writeWith(Publisher body) { 73 | if (body instanceof Flux) { 74 | Flux fluxBody = (Flux) body; 75 | return super.writeWith(fluxBody.map(dataBuffer -> { 76 | // probably should reuse buffers 77 | byte[] content = new byte[dataBuffer.readableByteCount()]; 78 | dataBuffer.read(content); 79 | String responseResult = new String(content, Charset.forName("UTF-8")); 80 | normalMsg.append("status=").append(this.getStatusCode()); 81 | normalMsg.append(";header=").append(this.getHeaders()); 82 | normalMsg.append(";responseResult=").append(responseResult); 83 | normalMsg.append(RESPONSE_TAIL); 84 | System.out.println(normalMsg.toString()); 85 | return bufferFactory.wrap(content); 86 | })); 87 | } 88 | return super.writeWith(body); // if body is not a flux. never got there. 89 | } 90 | }; 91 | 92 | return chain.filter(exchange.mutate().request(requestDecorator).response(decoratedResponse).build()); 93 | } 94 | 95 | @Override 96 | public int getOrder() { 97 | return -2; 98 | } 99 | 100 | } -------------------------------------------------------------------------------- /gateway-request-logging-4/src/main/java/com/stonie/springnotes/RequestLogFilter.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.cloud.gateway.filter.GatewayFilterChain; 6 | import org.springframework.cloud.gateway.filter.GlobalFilter; 7 | import org.springframework.core.Ordered; 8 | import org.springframework.http.HttpHeaders; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.http.server.reactive.ServerHttpRequest; 11 | import org.springframework.http.server.reactive.ServerHttpResponse; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.util.MultiValueMap; 14 | import org.springframework.web.server.ServerWebExchange; 15 | import reactor.core.publisher.Mono; 16 | 17 | import java.net.URI; 18 | 19 | @Component 20 | public class RequestLogFilter implements GlobalFilter, Ordered { 21 | 22 | private Logger log = LoggerFactory.getLogger(RequestLogFilter.class); 23 | 24 | private static final String START_TIME = "startTime"; 25 | 26 | private static final String HTTP_SCHEME = "http"; 27 | 28 | private static final String HTTPS_SCHEME = "https"; 29 | 30 | @Override 31 | public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { 32 | ServerHttpRequest request = exchange.getRequest(); 33 | URI requestURI = request.getURI(); 34 | String scheme = requestURI.getScheme(); 35 | /* 36 | * not http or https scheme 37 | */ 38 | if ((!HTTP_SCHEME.equalsIgnoreCase(scheme) && !HTTPS_SCHEME.equals(scheme))) { 39 | return chain.filter(exchange); 40 | } 41 | logRequest(exchange); 42 | return chain.filter(exchange).then(Mono.fromRunnable(()->logResponse(exchange))); 43 | } 44 | 45 | @Override 46 | public int getOrder() { 47 | return Integer.MIN_VALUE+2; 48 | } 49 | 50 | /** 51 | * log request 52 | * @param exchange 53 | */ 54 | private void logRequest(ServerWebExchange exchange){ 55 | ServerHttpRequest request = exchange.getRequest(); 56 | URI requestURI = request.getURI(); 57 | String scheme = requestURI.getScheme(); 58 | HttpHeaders headers = request.getHeaders(); 59 | long startTime = System.currentTimeMillis(); 60 | exchange.getAttributes().put(START_TIME, startTime); 61 | log.info("[RequestLogFilter](Request)Start Timestamp:{}",startTime); 62 | log.info("[RequestLogFilter](Request)Scheme:{},Path:{}",scheme,requestURI.getPath()); 63 | log.info("[RequestLogFilter](Request)Method:{},IP:{},Host:{}",request.getMethod(), GatewayUtils.getIpAddress(request),requestURI.getHost()); 64 | headers.forEach((key,value)-> log.debug("[RequestLogFilter](Request)Headers:Key->{},Value->{}",key,value)); 65 | GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT); 66 | MultiValueMap queryParams = request.getQueryParams(); 67 | if(!queryParams.isEmpty()){ 68 | queryParams.forEach((key,value)-> log.info("[RequestLogFilter](Request)Query Param :Key->({}),Value->({})",key,value)); 69 | } 70 | MediaType contentType = headers.getContentType(); 71 | long length = headers.getContentLength(); 72 | log.info("[RequestLogFilter](Request)ContentType:{},Content Length:{}",contentType,length); 73 | if(length>0 && null != contentType && (contentType.includes(MediaType.APPLICATION_JSON) 74 | ||contentType.includes(MediaType.APPLICATION_JSON_UTF8))){ 75 | log.info("[RequestLogFilter](Request)JsonBody:{}",gatewayContext.getRequestBody()); 76 | } 77 | if(length>0 && null != contentType && contentType.includes(MediaType.APPLICATION_FORM_URLENCODED)){ 78 | log.info("[RequestLogFilter](Request)FormData:{}",gatewayContext.getFormData()); 79 | } 80 | } 81 | 82 | /** 83 | * log response exclude response body 84 | * @param exchange 85 | */ 86 | private Mono logResponse(ServerWebExchange exchange){ 87 | Long startTime = exchange.getAttribute(START_TIME); 88 | Long executeTime = (System.currentTimeMillis() - startTime); 89 | ServerHttpResponse response = exchange.getResponse(); 90 | log.info("[RequestLogFilter](Response)HttpStatus:{}",response.getStatusCode()); 91 | HttpHeaders headers = response.getHeaders(); 92 | headers.forEach((key,value)-> log.debug("[RequestLogFilter]Headers:Key->{},Value->{}",key,value)); 93 | MediaType contentType = headers.getContentType(); 94 | long length = headers.getContentLength(); 95 | log.info("[RequestLogFilter](Response)ContentType:{},Content Length:{}",contentType,length); 96 | GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT); 97 | log.info("[RequestLogFilter](Response)Response Body:{}",gatewayContext.getResponseBody()); 98 | log.info("[RequestLogFilter](Response)Original Path:{},Cost:{} ms", exchange.getRequest().getURI().getPath(),executeTime); 99 | return Mono.empty(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /gateway-request-logging/src/main/java/com/stonie/springnotes/DetailedRequestResponseLogFilter.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.reactivestreams.Publisher; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.cloud.gateway.filter.GatewayFilterChain; 7 | import org.springframework.cloud.gateway.filter.GlobalFilter; 8 | import org.springframework.core.Ordered; 9 | import org.springframework.core.io.buffer.DataBuffer; 10 | import org.springframework.core.io.buffer.DataBufferFactory; 11 | import org.springframework.core.io.buffer.DataBufferUtils; 12 | import org.springframework.http.HttpHeaders; 13 | import org.springframework.http.MediaType; 14 | import org.springframework.http.server.reactive.ServerHttpRequest; 15 | import org.springframework.http.server.reactive.ServerHttpResponse; 16 | import org.springframework.http.server.reactive.ServerHttpResponseDecorator; 17 | import org.springframework.stereotype.Component; 18 | import org.springframework.util.MultiValueMap; 19 | import org.springframework.web.server.ServerWebExchange; 20 | import reactor.core.publisher.Flux; 21 | import reactor.core.publisher.Mono; 22 | 23 | import java.net.URI; 24 | import java.nio.charset.StandardCharsets; 25 | import java.util.List; 26 | 27 | /** 28 | * https://github.com/spring-cloud/spring-cloud-gateway/issues/1003 29 | * https://gist.github.com/matzegebbe/bf631b2d3ab6d55f58f4b6c1d3511189 30 | * 31 | */ 32 | @Component 33 | public class DetailedRequestResponseLogFilter implements GlobalFilter, Ordered { 34 | 35 | private static final String MAGIC_HEADER = "x-debug"; 36 | 37 | private static final Logger LOGGER = LoggerFactory.getLogger(DetailedRequestResponseLogFilter.class); 38 | 39 | private static final String START_TIME = "startTime"; 40 | 41 | private static final String HTTP_SCHEME = "http"; 42 | 43 | private static final String HTTPS_SCHEME = "https"; 44 | 45 | @Override 46 | public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { 47 | List debugHeader = exchange.getRequest().getHeaders().get(MAGIC_HEADER); 48 | if (!LOGGER.isDebugEnabled() && debugHeader == null) { 49 | // DO NOTHING 50 | return chain.filter(exchange); 51 | } 52 | ServerHttpRequest request = exchange.getRequest(); 53 | URI requestURI = request.getURI(); 54 | String scheme = requestURI.getScheme(); 55 | if (debugHeader != null) { 56 | String debugHeaderContent = debugHeader.get(0); 57 | if (!debugHeaderContent.equalsIgnoreCase("true") && !requestURI.getPath().toLowerCase().contains(debugHeaderContent.toLowerCase())) { 58 | return chain.filter(exchange); 59 | } 60 | } 61 | if ((!HTTP_SCHEME.equalsIgnoreCase(scheme) && !HTTPS_SCHEME.equals(scheme))) { 62 | return chain.filter(exchange); 63 | } 64 | long startTime = System.currentTimeMillis(); 65 | exchange.getAttributes().put(START_TIME, startTime); 66 | logRequest(request, exchange.getAttribute("cachedRequestBodyObject")); 67 | return chain.filter(exchange.mutate().response(logResponse(exchange)).build()); 68 | } 69 | 70 | @Override 71 | public int getOrder() { 72 | return Integer.MIN_VALUE; 73 | } 74 | 75 | private void logRequest(ServerHttpRequest request, String body) { 76 | URI requestURI = request.getURI(); 77 | String scheme = requestURI.getScheme(); 78 | HttpHeaders headers = request.getHeaders(); 79 | LOGGER.info("Request Scheme:{},Path:{}", scheme, requestURI.getPath()); 80 | LOGGER.info("Request Method:{},IP:{},Host:{}", request.getMethod(), request.getRemoteAddress(), requestURI.getHost()); 81 | headers.forEach((key, value) -> LOGGER.debug("Request Headers:Key->{},Value->{}", key, value)); 82 | MultiValueMap queryParams = request.getQueryParams(); 83 | if (!queryParams.isEmpty()) { 84 | queryParams.forEach((key, value) -> LOGGER.info("Request Query Param :Key->({}),Value->({})", key, value)); 85 | } 86 | MediaType contentType = headers.getContentType(); 87 | long length = headers.getContentLength(); 88 | LOGGER.info("Request ContentType:{},Content Length:{}", contentType, length); 89 | if (body != null) { 90 | LOGGER.info("Request Body:{}", body); 91 | } 92 | } 93 | 94 | private ServerHttpResponseDecorator logResponse(ServerWebExchange exchange) { 95 | ServerHttpResponse origResponse = exchange.getResponse(); 96 | Long startTime = exchange.getAttribute(START_TIME); 97 | LOGGER.info("Response HttpStatus:{}", origResponse.getStatusCode()); 98 | HttpHeaders headers = origResponse.getHeaders(); 99 | headers.forEach((key, value) -> LOGGER.debug("[RequestLogFilter]Headers:Key->{},Value->{}", key, value)); 100 | MediaType contentType = headers.getContentType(); 101 | long length = headers.getContentLength(); 102 | LOGGER.info("Response ContentType:{},Content Length:{}", contentType, length); 103 | Long executeTime = (System.currentTimeMillis() - startTime); 104 | LOGGER.info("Response Original Path:{},Cost:{} ms", exchange.getRequest().getURI().getPath(), executeTime); 105 | DataBufferFactory bufferFactory = origResponse.bufferFactory(); 106 | 107 | return new ServerHttpResponseDecorator(origResponse) { 108 | @Override 109 | public Mono writeWith(Publisher body) { 110 | if (body instanceof Flux) { 111 | Flux fluxBody = (Flux) body; 112 | 113 | return super.writeWith(fluxBody.map(dataBuffer -> { 114 | try { 115 | byte[] content = new byte[dataBuffer.readableByteCount()]; 116 | dataBuffer.read(content); 117 | String bodyContent = new String(content, StandardCharsets.UTF_8); 118 | LOGGER.info("Response:{}", bodyContent); 119 | return bufferFactory.wrap(content); 120 | } finally { 121 | DataBufferUtils.release(dataBuffer); 122 | } 123 | })); 124 | 125 | } 126 | return super.writeWith(body); 127 | 128 | } 129 | }; 130 | 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /gateway-request-logging-3/src/main/java/com/stonie/springnotes/util/GatewayLogUtil.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes.util; 2 | 3 | import com.stonie.springnotes.RecorderServerHttpResponseDecorator; 4 | import org.springframework.core.io.buffer.DataBuffer; 5 | import org.springframework.http.HttpHeaders; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.http.server.reactive.ServerHttpRequest; 10 | import org.springframework.lang.Nullable; 11 | import org.springframework.web.server.ServerWebExchange; 12 | import reactor.core.publisher.Flux; 13 | import reactor.core.publisher.Mono; 14 | 15 | import java.net.URI; 16 | import java.nio.charset.Charset; 17 | import java.nio.charset.StandardCharsets; 18 | 19 | import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; 20 | 21 | 22 | public class GatewayLogUtil { 23 | private final static String REQUEST_RECORDER_LOG_BUFFER = "RequestRecorderGlobalFilter.request_recorder_log_buffer"; 24 | 25 | private static boolean hasBody(HttpMethod method) { 26 | //只记录这3种谓词的body 27 | if (method == HttpMethod.POST || method == HttpMethod.PUT || method == HttpMethod.PATCH) 28 | return true; 29 | 30 | return false; 31 | } 32 | 33 | private static boolean shouldRecordBody(MediaType contentType) { 34 | String type = contentType.getType(); 35 | String subType = contentType.getSubtype(); 36 | 37 | if ("application".equals(type)) { 38 | return "json".equals(subType) || "x-www-form-urlencoded".equals(subType) || "xml".equals(subType) || "atom+xml".equals(subType) || "rss+xml".equals(subType); 39 | } else if ("text".equals(type)) { 40 | return true; 41 | } 42 | 43 | //form没有记录 44 | return false; 45 | } 46 | 47 | private static Mono doRecordBody(StringBuffer logBuffer, Flux body, Charset charset) { 48 | return DataBufferUtilFix.join(body) 49 | .doOnNext(wrapper -> { 50 | logBuffer.append(new String(wrapper.getData(), charset)); 51 | logBuffer.append("\n------------ end ------------\n\n"); 52 | wrapper.clear(); 53 | }).then(); 54 | } 55 | 56 | private static Charset getMediaTypeCharset(@Nullable MediaType mediaType) { 57 | if (mediaType != null && mediaType.getCharset() != null) { 58 | return mediaType.getCharset(); 59 | } 60 | else { 61 | return StandardCharsets.UTF_8; 62 | } 63 | } 64 | 65 | public static Mono recorderOriginalRequest(ServerWebExchange exchange) { 66 | StringBuffer logBuffer = new StringBuffer("\n---------------------------"); 67 | exchange.getAttributes().put(REQUEST_RECORDER_LOG_BUFFER, logBuffer); 68 | 69 | ServerHttpRequest request = exchange.getRequest(); 70 | return recorderRequest(request, request.getURI(), logBuffer.append("\n原始请求:\n")); 71 | } 72 | 73 | public static Mono recorderRouteRequest(ServerWebExchange exchange) { 74 | URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); 75 | StringBuffer logBuffer = exchange.getAttribute(REQUEST_RECORDER_LOG_BUFFER); 76 | 77 | return recorderRequest(exchange.getRequest(), requestUrl, logBuffer.append("代理请求:\n")); 78 | } 79 | 80 | private static Mono recorderRequest(ServerHttpRequest request, URI uri, StringBuffer logBuffer) { 81 | if (uri == null) { 82 | uri = request.getURI(); 83 | } 84 | 85 | HttpMethod method = request.getMethod(); 86 | HttpHeaders headers = request.getHeaders(); 87 | 88 | logBuffer 89 | .append(method.toString()).append(' ') 90 | .append(uri.toString()).append('\n'); 91 | 92 | logBuffer.append("------------请求头------------\n"); 93 | headers.forEach((name, values) -> { 94 | values.forEach(value -> { 95 | logBuffer.append(name).append(":").append(value).append('\n'); 96 | }); 97 | }); 98 | 99 | Charset bodyCharset = null; 100 | if (hasBody(method)) { 101 | long length = headers.getContentLength(); 102 | if (length <= 0) { 103 | logBuffer.append("------------无body------------\n"); 104 | } else { 105 | logBuffer.append("------------body 长度:").append(length).append(" contentType:"); 106 | MediaType contentType = headers.getContentType(); 107 | if (contentType == null) { 108 | logBuffer.append("null,不记录body------------\n"); 109 | } else if (!shouldRecordBody(contentType)) { 110 | logBuffer.append(contentType.toString()).append(",不记录body------------\n"); 111 | } else { 112 | bodyCharset = getMediaTypeCharset(contentType); 113 | logBuffer.append(contentType.toString()).append("------------\n"); 114 | } 115 | } 116 | } 117 | 118 | 119 | if (bodyCharset != null) { 120 | return doRecordBody(logBuffer, request.getBody(), bodyCharset); 121 | } else { 122 | logBuffer.append("------------ end ------------\n\n"); 123 | return Mono.empty(); 124 | } 125 | } 126 | 127 | public static Mono recorderResponse(ServerWebExchange exchange) { 128 | RecorderServerHttpResponseDecorator response = (RecorderServerHttpResponseDecorator)exchange.getResponse(); 129 | StringBuffer logBuffer = exchange.getAttribute(REQUEST_RECORDER_LOG_BUFFER); 130 | 131 | HttpStatus code = response.getStatusCode(); 132 | if (code == null) { 133 | logBuffer.append("返回异常").append("\n------------ end ------------\n\n"); 134 | return Mono.empty(); 135 | } 136 | 137 | logBuffer.append("响应:").append(code.value()).append(" ").append(code.getReasonPhrase()).append('\n'); 138 | 139 | HttpHeaders headers = response.getHeaders(); 140 | logBuffer.append("------------响应头------------\n"); 141 | headers.forEach((name, values) -> { 142 | values.forEach(value -> { 143 | logBuffer.append(name).append(":").append(value).append('\n'); 144 | }); 145 | }); 146 | 147 | Charset bodyCharset = null; 148 | MediaType contentType = headers.getContentType(); 149 | if (contentType == null) { 150 | logBuffer.append("------------ contentType = null,不记录body------------\n"); 151 | } else if (!shouldRecordBody(contentType)) { 152 | logBuffer.append("------------不记录body------------\n"); 153 | } else { 154 | bodyCharset = getMediaTypeCharset(contentType); 155 | logBuffer.append("------------body------------\n"); 156 | } 157 | 158 | if (bodyCharset != null) { 159 | return doRecordBody(logBuffer, response.copy(), bodyCharset); 160 | } else { 161 | logBuffer.append("\n------------ end ------------\n\n"); 162 | return Mono.empty(); 163 | } 164 | } 165 | 166 | public static String getLogData(ServerWebExchange exchange) { 167 | StringBuffer logBuffer = exchange.getAttribute(REQUEST_RECORDER_LOG_BUFFER); 168 | return logBuffer.toString(); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /gateway-request-logging-4/src/main/java/com/stonie/springnotes/ResponseLogFilter.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.reactivestreams.Publisher; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.cloud.gateway.filter.GatewayFilterChain; 7 | import org.springframework.cloud.gateway.filter.GlobalFilter; 8 | import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter; 9 | import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage; 10 | import org.springframework.cloud.gateway.support.BodyInserterContext; 11 | import org.springframework.cloud.gateway.support.DefaultClientResponse; 12 | import org.springframework.core.Ordered; 13 | import org.springframework.core.io.buffer.DataBuffer; 14 | import org.springframework.core.io.buffer.DataBufferUtils; 15 | import org.springframework.http.*; 16 | import org.springframework.http.client.reactive.ClientHttpResponse; 17 | import org.springframework.http.server.reactive.ServerHttpResponseDecorator; 18 | import org.springframework.stereotype.Component; 19 | import org.springframework.util.MultiValueMap; 20 | import org.springframework.web.reactive.function.BodyInserter; 21 | import org.springframework.web.reactive.function.BodyInserters; 22 | import org.springframework.web.reactive.function.client.ExchangeStrategies; 23 | import org.springframework.web.server.ServerWebExchange; 24 | import reactor.core.publisher.Flux; 25 | import reactor.core.publisher.Mono; 26 | 27 | import java.util.Optional; 28 | 29 | @Component 30 | public class ResponseLogFilter implements GlobalFilter, Ordered { 31 | 32 | private Logger log = LoggerFactory.getLogger(ResponseLogFilter.class); 33 | 34 | @Override 35 | public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { 36 | ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) { 37 | @Override 38 | public Mono writeWith(Publisher body) { 39 | return DataBufferUtils.join(Flux.from(body)) 40 | .flatMap(dataBuffer -> { 41 | byte[] bytes = new byte[dataBuffer.readableByteCount()]; 42 | dataBuffer.read(bytes); 43 | DataBufferUtils.release(dataBuffer); 44 | Flux cachedFlux = Flux.defer(() -> { 45 | DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes); 46 | DataBufferUtils.retain(buffer); 47 | return Mono.just(buffer); 48 | }); 49 | BodyInserter, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromDataBuffers(cachedFlux); 50 | CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getResponse().getHeaders()); 51 | DefaultClientResponse clientResponse = new DefaultClientResponse(new ResponseAdapter(cachedFlux, exchange.getResponse().getHeaders()), ExchangeStrategies.withDefaults()); 52 | Optional optionalMediaType = clientResponse.headers().contentType(); 53 | if(!optionalMediaType.isPresent()){ 54 | log.debug("[ResponseLogFilter]Response ContentType Is Not Exist"); 55 | return Mono.defer(()-> bodyInserter.insert(outputMessage, new BodyInserterContext()) 56 | .then(Mono.defer(() -> { 57 | Flux messageBody = cachedFlux; 58 | HttpHeaders headers = getDelegate().getHeaders(); 59 | if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) { 60 | messageBody = messageBody.doOnNext(data -> headers.setContentLength(data.readableByteCount())); 61 | } 62 | return getDelegate().writeWith(messageBody); 63 | }))); 64 | } 65 | MediaType contentType = optionalMediaType.get(); 66 | if(!contentType.equals(MediaType.APPLICATION_JSON) && !contentType.equals(MediaType.APPLICATION_JSON_UTF8)){ 67 | log.debug("[ResponseLogFilter]Response ContentType Is Not APPLICATION_JSON Or APPLICATION_JSON_UTF8"); 68 | return Mono.defer(()-> bodyInserter.insert(outputMessage, new BodyInserterContext()) 69 | .then(Mono.defer(() -> { 70 | Flux messageBody = cachedFlux; 71 | HttpHeaders headers = getDelegate().getHeaders(); 72 | if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) { 73 | messageBody = messageBody.doOnNext(data -> headers.setContentLength(data.readableByteCount())); 74 | } 75 | return getDelegate().writeWith(messageBody); 76 | }))); 77 | } 78 | return clientResponse.bodyToMono(Object.class) 79 | .doOnNext(originalBody -> { 80 | GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT); 81 | gatewayContext.setResponseBody(originalBody); 82 | log.debug("[ResponseLogFilter]Read Response Data To Gateway Context Success"); 83 | }) 84 | .then(Mono.defer(()-> bodyInserter.insert(outputMessage, new BodyInserterContext()) 85 | .then(Mono.defer(() -> { 86 | Flux messageBody = cachedFlux; 87 | HttpHeaders headers = getDelegate().getHeaders(); 88 | if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) { 89 | messageBody = messageBody.doOnNext(data -> headers.setContentLength(data.readableByteCount())); 90 | } 91 | return getDelegate().writeWith(messageBody); 92 | })))); 93 | }); 94 | 95 | } 96 | 97 | @Override 98 | public Mono writeAndFlushWith(Publisher> body) { 99 | return writeWith(Flux.from(body) 100 | .flatMapSequential(p -> p)); 101 | } 102 | }; 103 | return chain.filter(exchange.mutate().response(responseDecorator).build()); 104 | } 105 | 106 | @Override 107 | public int getOrder() { 108 | return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1; 109 | } 110 | 111 | public class ResponseAdapter implements ClientHttpResponse { 112 | 113 | private final Flux flux; 114 | private final HttpHeaders headers; 115 | 116 | public ResponseAdapter(Publisher body, HttpHeaders headers) { 117 | this.headers = headers; 118 | if (body instanceof Flux) { 119 | flux = (Flux) body; 120 | } else { 121 | flux = ((Mono)body).flux(); 122 | } 123 | } 124 | 125 | @Override 126 | public Flux getBody() { 127 | return flux; 128 | } 129 | 130 | @Override 131 | public HttpHeaders getHeaders() { 132 | return headers; 133 | } 134 | 135 | @Override 136 | public HttpStatus getStatusCode() { 137 | return null; 138 | } 139 | 140 | @Override 141 | public int getRawStatusCode() { 142 | return 0; 143 | } 144 | 145 | @Override 146 | public MultiValueMap getCookies() { 147 | return null; 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /gateway-request-logging-4/src/main/java/com/stonie/springnotes/GatewayContextFilter.java: -------------------------------------------------------------------------------- 1 | package com.stonie.springnotes; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.cloud.gateway.filter.GatewayFilterChain; 6 | import org.springframework.cloud.gateway.filter.GlobalFilter; 7 | import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage; 8 | import org.springframework.cloud.gateway.support.BodyInserterContext; 9 | import org.springframework.core.Ordered; 10 | import org.springframework.core.io.buffer.DataBuffer; 11 | import org.springframework.core.io.buffer.DataBufferUtils; 12 | import org.springframework.http.HttpHeaders; 13 | import org.springframework.http.MediaType; 14 | import org.springframework.http.ReactiveHttpOutputMessage; 15 | import org.springframework.http.codec.HttpMessageReader; 16 | import org.springframework.http.server.reactive.ServerHttpRequest; 17 | import org.springframework.http.server.reactive.ServerHttpRequestDecorator; 18 | import org.springframework.stereotype.Component; 19 | import org.springframework.util.MultiValueMap; 20 | import org.springframework.web.reactive.function.BodyInserter; 21 | import org.springframework.web.reactive.function.BodyInserters; 22 | import org.springframework.web.reactive.function.server.HandlerStrategies; 23 | import org.springframework.web.reactive.function.server.ServerRequest; 24 | import org.springframework.web.server.ServerWebExchange; 25 | import reactor.core.publisher.Flux; 26 | import reactor.core.publisher.Mono; 27 | 28 | import java.io.UnsupportedEncodingException; 29 | import java.net.URLEncoder; 30 | import java.nio.charset.Charset; 31 | import java.nio.charset.StandardCharsets; 32 | import java.util.List; 33 | import java.util.Map; 34 | 35 | /** 36 | * https://segmentfault.com/a/1190000017898354 37 | * https://github.com/chenggangpro/spring-cloud-gateway-plugin 38 | */ 39 | @Component 40 | public class GatewayContextFilter implements GlobalFilter, Ordered { 41 | 42 | private Logger log = LoggerFactory.getLogger(GatewayContextFilter.class); 43 | 44 | /** 45 | * default HttpMessageReader 46 | */ 47 | private static final List> MESSAGE_READERS = HandlerStrategies.withDefaults().messageReaders(); 48 | 49 | @Override 50 | public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { 51 | ServerHttpRequest request = exchange.getRequest(); 52 | GatewayContext gatewayContext = new GatewayContext(); 53 | HttpHeaders headers = request.getHeaders(); 54 | gatewayContext.setRequestHeaders(headers); 55 | gatewayContext.getAllRequestData().addAll(request.getQueryParams()); 56 | /* 57 | * save gateway context into exchange 58 | */ 59 | exchange.getAttributes().put(GatewayContext.CACHE_GATEWAY_CONTEXT,gatewayContext); 60 | MediaType contentType = headers.getContentType(); 61 | if(headers.getContentLength()>0){ 62 | if(MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)){ 63 | return readBody(exchange, chain,gatewayContext); 64 | } 65 | if(MediaType.APPLICATION_FORM_URLENCODED.equals(contentType)){ 66 | return readFormData(exchange, chain,gatewayContext); 67 | } 68 | } 69 | log.debug("[GatewayContext]ContentType:{},Gateway context is set with {}",contentType, gatewayContext); 70 | return chain.filter(exchange); 71 | 72 | } 73 | 74 | 75 | @Override 76 | public int getOrder() { 77 | return Integer.MIN_VALUE; 78 | } 79 | 80 | 81 | /** 82 | * ReadFormData 83 | * @param exchange 84 | * @param chain 85 | * @return 86 | */ 87 | private Mono readFormData(ServerWebExchange exchange,GatewayFilterChain chain,GatewayContext gatewayContext){ 88 | HttpHeaders headers = exchange.getRequest().getHeaders(); 89 | return exchange.getFormData() 90 | .doOnNext(multiValueMap -> { 91 | gatewayContext.setFormData(multiValueMap); 92 | gatewayContext.getAllRequestData().addAll(multiValueMap); 93 | log.debug("[GatewayContext]Read FormData Success"); 94 | }) 95 | .then(Mono.defer(() -> { 96 | Charset charset = headers.getContentType().getCharset(); 97 | charset = charset == null? StandardCharsets.UTF_8:charset; 98 | String charsetName = charset.name(); 99 | MultiValueMap formData = gatewayContext.getFormData(); 100 | /* 101 | * formData is empty just return 102 | */ 103 | if(null == formData || formData.isEmpty()){ 104 | return chain.filter(exchange); 105 | } 106 | StringBuilder formDataBodyBuilder = new StringBuilder(); 107 | String entryKey; 108 | List entryValue; 109 | try { 110 | /* 111 | * repackage form data 112 | */ 113 | for (Map.Entry> entry : formData.entrySet()) { 114 | entryKey = entry.getKey(); 115 | entryValue = entry.getValue(); 116 | if (entryValue.size() > 1) { 117 | for(String value : entryValue){ 118 | formDataBodyBuilder.append(entryKey).append("=").append(URLEncoder.encode(value, charsetName)).append("&"); 119 | } 120 | } else { 121 | formDataBodyBuilder.append(entryKey).append("=").append(URLEncoder.encode(entryValue.get(0), charsetName)).append("&"); 122 | } 123 | } 124 | }catch (UnsupportedEncodingException e){} 125 | /* 126 | * substring with the last char '&' 127 | */ 128 | String formDataBodyString = ""; 129 | if(formDataBodyBuilder.length()>0){ 130 | formDataBodyString = formDataBodyBuilder.substring(0, formDataBodyBuilder.length() - 1); 131 | } 132 | /* 133 | * get data bytes 134 | */ 135 | byte[] bodyBytes = formDataBodyString.getBytes(charset); 136 | int contentLength = bodyBytes.length; 137 | HttpHeaders httpHeaders = new HttpHeaders(); 138 | httpHeaders.putAll(exchange.getRequest().getHeaders()); 139 | httpHeaders.remove(HttpHeaders.CONTENT_LENGTH); 140 | /* 141 | * in case of content-length not matched 142 | */ 143 | httpHeaders.setContentLength(contentLength); 144 | /* 145 | * use BodyInserter to InsertFormData Body 146 | */ 147 | BodyInserter bodyInserter = BodyInserters.fromObject(formDataBodyString); 148 | CachedBodyOutputMessage cachedBodyOutputMessage = new CachedBodyOutputMessage(exchange, httpHeaders); 149 | log.debug("[GatewayContext]Rewrite Form Data :{}",formDataBodyString); 150 | return bodyInserter.insert(cachedBodyOutputMessage, new BodyInserterContext()) 151 | .then(Mono.defer(() -> { 152 | ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator( 153 | exchange.getRequest()) { 154 | @Override 155 | public HttpHeaders getHeaders() { 156 | return httpHeaders; 157 | } 158 | @Override 159 | public Flux getBody() { 160 | return cachedBodyOutputMessage.getBody(); 161 | } 162 | }; 163 | return chain.filter(exchange.mutate().request(decorator).build()); 164 | })); 165 | })); 166 | } 167 | 168 | /** 169 | * ReadJsonBody 170 | * @param exchange 171 | * @param chain 172 | * @return 173 | */ 174 | private Mono readBody(ServerWebExchange exchange, GatewayFilterChain chain, GatewayContext gatewayContext){ 175 | return DataBufferUtils.join(exchange.getRequest().getBody()) 176 | .flatMap(dataBuffer -> { 177 | /* 178 | * read the body Flux, and release the buffer 179 | * //TODO when SpringCloudGateway Version Release To G.SR2,this can be update with the new version's feature 180 | * see PR https://github.com/spring-cloud/spring-cloud-gateway/pull/1095 181 | */ 182 | byte[] bytes = new byte[dataBuffer.readableByteCount()]; 183 | dataBuffer.read(bytes); 184 | DataBufferUtils.release(dataBuffer); 185 | Flux cachedFlux = Flux.defer(() -> { 186 | DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes); 187 | DataBufferUtils.retain(buffer); 188 | return Mono.just(buffer); 189 | }); 190 | /* 191 | * repackage ServerHttpRequest 192 | */ 193 | ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) { 194 | @Override 195 | public Flux getBody() { 196 | return cachedFlux; 197 | } 198 | }; 199 | ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build(); 200 | return ServerRequest.create(mutatedExchange, MESSAGE_READERS) 201 | .bodyToMono(String.class) 202 | .doOnNext(objectValue -> { 203 | gatewayContext.setRequestBody(objectValue); 204 | log.debug("[GatewayContext]Read JsonBody Success"); 205 | }).then(chain.filter(mutatedExchange)); 206 | }); 207 | } 208 | } 209 | --------------------------------------------------------------------------------