如何在SpringBoot中获取Rsocket连接的远程IP地址。

huangapple go评论71阅读模式
英文:

How do I get the remote IP address for an Rsocket connection in SpringBoot

问题

我正在尝试获取连接到RSocket + SpringBoot Web服务器的浏览器的远程IP。连接是通过WebSocket进行的。

Web服务器使用Java 8、SpringBoot 2,在WebSocket上使用RSocket,并向浏览器发送RequestStreams。我在服务器端利用了SpringBoot的自动配置来进行RSocket的设置,所以代码非常简洁 - 见下文。

在下面的代码中,@Headers和MessageHeader只是为了查看它们是否有任何可能导致远程IP的信息,除此之外没有其他原因它们在那里。

我已经在网络上搜索了答案 - 对于HTTP有很多,对于WebSocket有一些,但是对于RSocket几乎没有

这个链接 - https://github.com/rsocket/rsocket-java/issues/735 - 看起来很有希望,但是无法获取到DuplexConnection的句柄,所以没有成功。

有什么想法吗?谢谢!

application.yml:

spring.rsocket.server:
  mapping-path: /rsocket-test
  transport: websocket

server.port: 8080

TestController.java

 /**
     * TODO:获取远程IP地址并记录。
     * 选项:
     * 1. @see <a href="https://github.com/rsocket/rsocket-java/issues/735">Ability to intercept requests and access channel information such as remote address</a>.
     * 2. 通过nginx在标头中注入IP。看看是否会显示在这里的@Headers参数中。
     * 3. 浏览器获取其公共IP并将其添加到请求对象中。可行,但不是很好的方法。
     * 4. (不太可能)找到一种方法通过这一系列私有成员进行访问:headers.req.rsocket().source.connection.source.connection.connection.channel.remoteAddress
     */
    @MessageMapping("subscribe-topic")
    public Flux<StreamingEvent> subscribeToEventStream(
                @Headers Map<String,Object> hdrs,
                MessageHeaders mh,
                testRequest request) {
        return testService.subscribeTopic(request.getRequestId(), request.getTopic());
    }
英文:

I'm trying to get the remote IP of the browser that connects to a RSocket+SpringBoot webserver. Connection is RSocket over WebSocket.

The webserver is Java-8, SpringBoot-2, using RSocket over WebSocket and sending RequestStreams to the browser. I'm leveraging SpringBoot autoconfig for the RSocket setup, so really minimal code on the server side - see below.

@Headers and MessageHeader in the code below was just to see if they had anything that could lead to the remote IP, no other reason they're there.

I've scoured the web looking for an answer - lots for http, a few for websockets, zero for RSocket.
This - https://github.com/rsocket/rsocket-java/issues/735 - looked promising, but was unable to get a handle to DuplexConnection, so no cigar there.

Any ideas ? Thx !

application.yml:

spring.rsocket.server:
  mapping-path: /rsocket-test
  transport: websocket

server.port: 8080

TestController.java

 /**
     * TODO: get the remote IP address and log.
     * Options:
     * 1. @see <a href="https://github.com/rsocket/rsocket-java/issues/735">Ability to intercept requests and access channel information such as remote address</a>.
     * 2. Inject IP in header by nginx. See if it shows up in the @Headers param here.
     * 3. Browser gets its public IP and adds it to the request object. Doable, but lame
     * 4. (Unlikely) Find a way to access thru this chain of private members: headers.req.rsocket().source.connection.source.connection.connection.channel.remoteAddress
     */
    @MessageMapping("subscribe-topic")
    public Flux<StreamingEvent> subscribeToEventStream(
                @Headers Map<String,Object> hdrs,
                MessageHeaders mh,
                testRequest request) {
        return testService.subscribeTopic(request.getRequestId(), request.getTopic());
    }

答案1

得分: 3

客户端的IP地址可以在DuplexConnection类中获取。您可以像这样向RSocketServer添加拦截器:

@Bean
public RSocketServerCustomizer ipCustomizer() {
    return rSocketServer -> rSocketServer.interceptors(registry -> registry.forConnection(new ConnectionInterceptor()));
}

其中ConnectionInterceptor如下:

static class ConnectionInterceptor implements DuplexConnectionInterceptor {
    @Override
    public DuplexConnection apply(Type type, DuplexConnection duplexConnection) {
        SocketAddress socketAddress = duplexConnection.remoteAddress();
        if (socketAddress instanceof InetSocketAddress) {
            InetSocketAddress iso = (InetSocketAddress) socketAddress;
            // 这里是IP地址:iso.getHostString()
        }
        return duplexConnection;
    }
}

请注意,代码部分并未进行翻译,仅返回翻译后的内容。

英文:

Client's ip address is available in the DuplexConnection class. You can add an interceptor to the RSocketServer like this:

@Bean
public RSocketServerCustomizer ipCustomizer() {
    return rSocketServer -> rSocketServer.interceptors(registry -> registry.forConnection(new ConnectionInterceptor()));
}

where ConnectionInterceptor is:

static class ConnectionInterceptor implements DuplexConnectionInterceptor {
    @Override
    public DuplexConnection apply(Type type, DuplexConnection duplexConnection) {
        SocketAddress socketAddress = duplexConnection.remoteAddress();
        if (socketAddress instanceof InetSocketAddress) {
            InetSocketAddress iso = (InetSocketAddress) socketAddress;
            // Here is the ip: iso.getHostString()
        }
        return duplexConnection;
    }
}

答案2

得分: 0

在我的情景中,我希望将远程主机和原始的HTTP请求头传递给我的 @MessageMapping 处理程序。我想到的最佳方法是创建自己的 NettyRouteProvider 并在订阅上下文中添加所需的数据:

@Component
public class MyRouteProvider implements NettyRouteProvider {

    private final String mappingPath;
    private final SocketAcceptor socketAcceptor;
    private final List<RSocketServerCustomizer> customizers;

    @Autowired
    MyRouteProvider(RSocketProperties properties, RSocketMessageHandler messageHandler,
                    ObjectProvider<RSocketServerCustomizer> customizers) {
        this.mappingPath = properties.getServer().getMappingPath();
        this.socketAcceptor = messageHandler.responder();
        this.customizers = customizers.orderedStream().collect(Collectors.toList());
    }

    @Override
    public HttpServerRoutes apply(HttpServerRoutes httpServerRoutes) {
        RSocketServer server = RSocketServer.create(this.socketAcceptor);
        this.customizers.forEach((customizer) -> customizer.customize(server));
        ServerTransport.ConnectionAcceptor connectionAcceptor = server.asConnectionAcceptor();

        return httpServerRoutes.ws(this.mappingPath, (in, out) ->
                connectionAcceptor.apply(new WebsocketDuplexConnection((Connection) in)).then(out.neverComplete())
                        .contextWrite(context -> {
                            String ip = in.headers().get("x-forwarded-for");
                            SocketAddress remoteAddress = ((Connection) in).channel().remoteAddress();

                            if (in.headers().contains("x-forwarded-for")) {
                                return context
                                        .put("remoteHost", remoteAddress)
                                        .put(HttpHeaders.class, in.headers())
                                        .put("client-ip", ip);
                            }
                            return context
                                    .put("remoteHost", remoteAddress)
                                    .put(HttpHeaders.class, in.headers());
                        }));
    }

}

在完成这一步之后,您可以在任何 @MessageMapping 方法中从上下文中获取数据,例如:

@MessageMapping("remote-host")
fun remoteHost(): Mono<SocketAddress> {
    return Mono.deferContextual { context ->
        Mono.just(context.get<SocketAddress>("remoteHost"))
    }
}
英文:

In my scenario, I wanted to pass the remote host and the original HTTP request headers to my @MessageMapping handlers. The best way I came up with is to create my own NettyRouteProvider and enrich the subscription context with desired data:

@Component
public class MyRouteProvider implements NettyRouteProvider {
private final String mappingPath;
private final SocketAcceptor socketAcceptor;
private final List&lt;RSocketServerCustomizer&gt; customizers;
@Autowired
MyRouteProvider(RSocketProperties properties, RSocketMessageHandler messageHandler,
ObjectProvider&lt;RSocketServerCustomizer&gt; customizers) {
this.mappingPath = properties.getServer().getMappingPath();
this.socketAcceptor = messageHandler.responder();
this.customizers = customizers.orderedStream().collect(Collectors.toList());
}
@Override
public HttpServerRoutes apply(HttpServerRoutes httpServerRoutes) {
RSocketServer server = RSocketServer.create(this.socketAcceptor);
this.customizers.forEach((customizer) -&gt; customizer.customize(server));
ServerTransport.ConnectionAcceptor connectionAcceptor = server.asConnectionAcceptor();
return httpServerRoutes.ws(this.mappingPath, (in, out) -&gt;
connectionAcceptor.apply(new WebsocketDuplexConnection((Connection) in)).then(out.neverComplete())
.contextWrite(context -&gt; {
String ip = in.headers().get(&quot;x-forwarded-for&quot;);
SocketAddress remoteAddress = ((Connection) in).channel().remoteAddress();
if(in.headers().contains(&quot;x-forwarded-for&quot;)){
return context
.put(&quot;remoteHost&quot;,remoteAddress)
.put(HttpHeaders.class, in.headers())
.put(&quot;client-ip&quot;, ip);
}
return context
.put(&quot;remoteHost&quot;,remoteAddress)
.put(HttpHeaders.class, in.headers());
}));
}
}

After this you will be able to get the data from the context in any of your @MessageMapping methods, for example:

@MessageMapping(&quot;remote-host&quot;)
fun remoteHost(): Mono&lt;SocketAddress&gt; {
return Mono.deferContextual { context -&gt;
Mono.just(context.get&lt;SocketAddress&gt;(&quot;remoteHost&quot;))
}
}

答案3

得分: 0

以下是翻译好的内容:

我与这个问题战斗了相当长的时间。
我不喜欢被接受的答案,因为没有办法将信息传递给处理程序。Nokina 的答案有效,但仅适用于初始请求。在随后的请求中,上下文将不包含地址。花了几个小时后,我回归到了老牌的反射方式,并想出了这个:

import io.rsocket.DuplexConnection;
import io.rsocket.RSocket;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.net.SocketAddress;

public class RSocketUtil {

    public static DuplexConnection getConnectionFromRequester(RSocketRequester requester) {
        RSocket rsocket = requester.rsocket();
        Field connectionField = ReflectionUtils.findField(rsocket.getClass(), "connection");
        connectionField.setAccessible(true);
        return (DuplexConnection) ReflectionUtils.getField(connectionField, rsocket);
    }

    public static SocketAddress getRemoteAddressFromRequester(RSocketRequester requester) {
        DuplexConnection connection = getConnectionFromRequester(requester);
        return connection.remoteAddress();
    }

}
英文:

I fought with this problem for quite a while.
I did not like the accepted answer, since there is no way to pass the information to the handlers. The answer from Nokina worked, but only on the initial request. On subsequent requests, the context would not contain the address. After spending hours, I just went back to good ol' reflection, and came up with this:

import io.rsocket.DuplexConnection;
import io.rsocket.RSocket;
import org.springframework.messaging.rsocket.RSocketRequester;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.net.SocketAddress;
public class RSocketUtil {
public static DuplexConnection getConnectionFromRequester(RSocketRequester requester) {
RSocket rsocket = requester.rsocket();
Field connectionField = ReflectionUtils.findField(rsocket.getClass(), &quot;connection&quot;);
connectionField.setAccessible(true);
return (DuplexConnection) ReflectionUtils.getField(connectionField, rsocket);
}
public static SocketAddress getRemoteAddressFromRequester(RSocketRequester requester) {
DuplexConnection connection = getConnectionFromRequester(requester);
return connection.remoteAddress();
}
}

huangapple
  • 本文由 发表于 2020年5月5日 10:41:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/61604826.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定