diff --git a/gateway/pom.xml b/gateway/pom.xml
index e58681e..26aa8a2 100644
--- a/gateway/pom.xml
+++ b/gateway/pom.xml
@@ -5,7 +5,7 @@
org.springframework.boot
spring-boot-starter-parent
- 2.1.8.RELEASE
+ 2.2.10.RELEASE
me.aboullaite
@@ -16,7 +16,7 @@
1.8
- Greenwich.RELEASE
+ Hoxton.SR8
diff --git a/gateway/src/main/java/me/aboullaite/gateway/NettyReactiveWebServerConfig.java b/gateway/src/main/java/me/aboullaite/gateway/NettyReactiveWebServerConfig.java
new file mode 100644
index 0000000..c0750c7
--- /dev/null
+++ b/gateway/src/main/java/me/aboullaite/gateway/NettyReactiveWebServerConfig.java
@@ -0,0 +1,67 @@
+package me.aboullaite.gateway;
+
+import io.netty.handler.codec.http.HttpRequest;
+import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
+import org.springframework.boot.web.server.WebServerFactoryCustomizer;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import reactor.netty.http.server.ConnectionInfo;
+import reactor.netty.tcp.InetSocketAddressUtil;
+
+import java.net.InetSocketAddress;
+import java.util.function.BiFunction;
+
+/**
+ * This configuration customizes the behavior of remote address resolver. As a result,
+ * {@link ServerHttpRequest#getRemoteAddress} will return IP address that is extracted from X-Forwarded-For header
+ * by our {@link CustomizedHttpForwardedHeaderHandler}.
+ */
+@Configuration
+public class NettyReactiveWebServerConfig implements WebServerFactoryCustomizer {
+
+ @Override
+ public void customize(NettyReactiveWebServerFactory factory) {
+ factory.addServerCustomizers(httpServer -> httpServer.forwarded(new CustomizedHttpForwardedHeaderHandler()));
+ }
+
+ static class CustomizedHttpForwardedHeaderHandler
+ implements BiFunction {
+
+ static final String X_FORWARDED_IP_HEADER = "X-Forwarded-For";
+
+ /**
+ * Attempt to extract the last IP address from X-Forwarded-For header. If the header is present, create a
+ * ConnectionInfo instance with the extracted IP address and then return it. Otherwise, return the
+ * connectionInfo passed in. On the contrary, DefaultHttpForwardedHeaderHandler extracts the first IP
+ * address from the header when the header is present.
+ *
+ * @param connectionInfo
+ * @param request
+ * @return
+ */
+ @Override
+ public ConnectionInfo apply(ConnectionInfo connectionInfo, HttpRequest request) {
+ String ipHeader = request.headers().get(X_FORWARDED_IP_HEADER);
+
+ if (ipHeader != null) {
+ String[] ips = ipHeader.split(",");
+ String ip = ips[ips.length - 1].trim();
+
+ // If the IP address is internal, we will skip it and extract the previous one.
+ if (ips.length >= 2 && isInternal(ip)) {
+ ip = ips[ips.length - 2].trim();
+ }
+
+ InetSocketAddress remoteAddress = InetSocketAddressUtil.parseAddress(ip,
+ connectionInfo.getRemoteAddress().getPort());
+ connectionInfo = connectionInfo.withRemoteAddress(remoteAddress);
+ }
+
+ return connectionInfo;
+ }
+
+ private boolean isInternal(String ip) {
+ return ip.startsWith("10.");
+ }
+ }
+}
diff --git a/gateway/src/main/java/me/aboullaite/gateway/controller/RemoteAddressController.java b/gateway/src/main/java/me/aboullaite/gateway/controller/RemoteAddressController.java
new file mode 100644
index 0000000..45d34a2
--- /dev/null
+++ b/gateway/src/main/java/me/aboullaite/gateway/controller/RemoteAddressController.java
@@ -0,0 +1,27 @@
+package me.aboullaite.gateway.controller;
+
+import me.aboullaite.gateway.NettyReactiveWebServerConfig;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * A demo controller that shows the customized behavior of {@link ServerHttpRequest#getRemoteAddress}. The
+ * customization class is CustomizedHttpForwardedHeaderHandler in {@link NettyReactiveWebServerConfig}.
+ */
+@RestController
+public class RemoteAddressController {
+
+ /**
+ * @param request
+ * @return Value of X-Forwarded-For header, as well as result of {@link ServerHttpRequest#getRemoteAddress}
+ */
+ @GetMapping("/remote_address")
+ public String getRemoteAddress(ServerHttpRequest request) {
+ return String.format("X-Forwarded-For: %s\n" +
+ "getRemoteAddress() returns %s",
+ request.getHeaders().get("X-Forwarded-For"),
+ request.getRemoteAddress()
+ );
+ }
+}