How to get remote address from request of Netty HTTP Server

获取 remoteAddress 的逻辑

通过HTTP请求获取 remote address 时,最终是调用 ConnectionInfo 的 getRemoteAddress() 方法。

reactor.netty.http.server.HttpServerOperations#remoteAddress

@Override
public InetSocketAddress remoteAddress() {
	return this.connectionInfo.getRemoteAddress();
}

ConnectionInfo 实例的创建过程如下:

从 “Forwarded” 或 "X-Forwarded-" HTTP请求头中获取连接信息,如果不存在则直接从当前连接获取连接信息。*

请注意:如果 Nginx 和 Netty HTTP Server 位于同一服务器内,且未启动 use-forward-headers,则获取的 remoteAddress 为 127.0.0.1 。

reactor.netty.http.server.ConnectionInfo#from

static ConnectionInfo from(Channel channel, boolean headers, HttpRequest request, boolean secured) {
	if (headers) {
		return ConnectionInfo.newForwardedConnectionInfo(request, channel, secured);
	}
	else {
		return ConnectionInfo.newConnectionInfo(channel, secured);
	}
}

static ConnectionInfo newForwardedConnectionInfo(HttpRequest request, Channel channel, boolean secured) {
	if (request.headers().contains(FORWARDED_HEADER)) {
		return parseForwardedInfo(request, (SocketChannel)channel, secured);
	}
	else {
		return parseXForwardedInfo(request, (SocketChannel)channel, secured);
	}
}

static ConnectionInfo parseXForwardedInfo(HttpRequest request, SocketChannel channel, boolean secured) {
	InetSocketAddress hostAddress = channel.localAddress();
	InetSocketAddress remoteAddress = getRemoteAddress(channel);
	String scheme =  secured ? "https" : "http";
	if (request.headers().contains(XFORWARDED_IP_HEADER)) {
		String remoteIpValue = request.headers().get(XFORWARDED_IP_HEADER).split(",")[0];
		remoteAddress = parseAddress(remoteIpValue, remoteAddress.getPort());
	}
	if(request.headers().contains(XFORWARDED_HOST_HEADER)) {
		if(request.headers().contains(XFORWARDED_PORT_HEADER)) {
			hostAddress = InetSocketAddressUtil.createUnresolved(
					request.headers().get(XFORWARDED_HOST_HEADER).split(",")[0].trim(),
					Integer.parseInt(request.headers().get(XFORWARDED_PORT_HEADER).split(",")[0].trim()));
		}
		else {
			hostAddress = InetSocketAddressUtil.createUnresolved(
					request.headers().get(XFORWARDED_HOST_HEADER).split(",")[0].trim(),
					channel.localAddress().getPort());
		}
	}
	if (request.headers().contains(XFORWARDED_PROTO_HEADER)) {
		scheme = request.headers().get(XFORWARDED_PROTO_HEADER).trim();
	}
	return new ConnectionInfo(hostAddress, remoteAddress, scheme);
}

通过配置文件启用 use-forward-headers

要想通过 request header 获取连接信息,需要配置 use-forward-headers = true。(默认为 false)

server:
  port: 9090
  use-forward-headers: true

通过 NettyWebServerFactoryCustomizer 配置

也可以通过NettyWebServerFactoryCustomizer启动,示例代码如下:

@Configuration
public class NettyWebServerConfiguration {
    @Bean
    public NettyWebServerFactoryCustomizer nettyServerWiretapCustomizer(
            Environment environment, ServerProperties serverProperties) {
        return new NettyWebServerFactoryCustomizer(environment, serverProperties) {
            @Override
            public void customize(NettyReactiveWebServerFactory factory) {
                factory.addServerCustomizers(httpServer -> httpServer.forwarded(true));
                super.customize(factory);
            }
        };
    }
}

set X-Forwarded-For header from nginx

1.通过“proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for” 把从真实客户端IP和反向代理IP通过逗号分隔,添加到请求头中;
2.可以在第一个反向代理上配置“proxy_set_header X-Real-IP $remote_addr” 获取真实客户端IP;
3.配合 realip 模块从 X-Forwarded-For 也可以获取到真实客户端IP。

在整个反向代理链条的第一个反向代理可以不配置“proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for”,否则客户端可以伪造 X-Forwarded-For 从而伪造客户端真实 IP,如果服务端使用 X-Forwarded-For 第一个IP作为真实客户端 IP,则就出问题了。如果通过配置 X-Real-IP 请求头或者配合 realip 模块也不会出现该问题。如果自己解析 X-Forwarded-For 的话,记得使用 realip 算法解析,而不是取第一个。
当我们进行限流时一定注意限制的是真实客户端IP,而不是反向代理IP,我遇到过很多同事在这块出问题的。

location / {
    proxy_next_upstream		http_500 http_502 http_503 http_504 error timeout invalid_header;
    proxy_set_header		Host  $host;
    proxy_set_header		X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass				http://tomcat_microservice;
    expires					0;
}

参考

反向代理与 Real-IP 和 X-Forwarded-For

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty是一个基于Java的异步事件驱动的网络应用框架,它提供了高性能、可扩展的网络编程能力。Netty的核心是NIO(非阻塞I/O)模型,它允许在单个线程中处理多个并发连接,从而提高了网络应用的吞吐量和性能。 Netty提供了许多开箱即用的组件,其中包括HTTP服务器。通过使用NettyHTTP服务器组件,您可以轻松地构建和部署自己的HTTP服务器。 下面是一个简单的示例,演示如何使用Netty构建一个简单的HTTP服务器: ```java import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; public class HttpServerExample { public static void main(String[] args) throws Exception { // 创建两个EventLoopGroup,一个用于接收连接,一个用于处理连接的I/O操作 EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { // 创建ServerBootstrap实例,用于引导和绑定服务器 ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { // 添加HTTP编解码器 ch.pipeline().addLast(new HttpServerCodec()); // 添加自定义的HTTP请求处理器 ch.pipeline().addLast(new HttpServerHandler()); } }); // 绑定端口并启动服务器 ChannelFuture future = serverBootstrap.bind(8080).sync(); System.out.println("Netty HTTP Server started on port 8080."); // 等待服务器关闭 future.channel().closeFuture().sync(); } finally { // 关闭EventLoopGroup bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } ``` 在上面的示例中,我们创建了一个`ServerBootstrap`实例,并配置了两个`EventLoopGroup`,一个用于接收连接,一个用于处理连接的I/O操作。然后,我们指定了服务器的通道类型为`NioServerSocketChannel`,并添加了一个`LoggingHandler`用于打印日志。接下来,我们创建了一个`ChannelInitializer`,并在其中添加了一个`HttpServerCodec`用于处理HTTP编解码,以及一个自定义的`HttpServerHandler`用于处理HTTP请求。最后,我们绑定了服务器的端口并启动服务器。 请注意,上述示例中的`HttpServerHandler`是一个自定义的处理器,您可以根据自己的需求来实现它。它负责处理接收到的HTTP请求,并返回相应的HTTP响应。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值