jakartaee / rest

Jakarta RESTful Web Services
Other
351 stars 114 forks source link

Obtaining client address without HttpServletRequest #1258

Open mkarg opened 2 months ago

mkarg commented 2 months ago

There are several use cases where it makes sense to obtain the client's IP address (and possibly port). For that sake, HttpServletRequest provides the methods getRemotePort() and getRemoteAddr().

While this should be sufficient for most scenarios, it binds the application to the Servlet API, just for calling a single method.

As JAX-RS's binding to the Servlet API isn't mandatory (e. g. a Java SE standalone standalone server might implement JAX-RS directly without relying on the Servlet API), this use case should be possible without the use of the Server API. Therefore I suggest to enhance the Request class by getRemotePort() and getRemoteAddr().

mkarg commented 2 months ago

I thought we discussed this topic already months back, but I could not find the issue / PR anymore. If I am right and this is a duplicate, please post a reference to the original discussion here. Thanks. 🤔

jamezp commented 2 months ago

One thing I want to mention is I've heard rumblings, I don't know if there is anything concrete as I've not been involved, that there may end up being a Jakarta HTTP API specification at some point.

I'm only mentioning this so we don't end up in another scenario like the @Context and CDI situation we find ourselves in now. I do realize this specific case would likely be less of an issue though as it would just delegate.

mkarg commented 2 months ago

One thing I want to mention is I've heard rumblings, I don't know if there is anything concrete as I've not been involved, that there may end up being a Jakarta HTTP API specification at some point.

I'm only mentioning this so we don't end up in another scenario like the @Context and CDI situation we find ourselves in now. I do realize this specific case would likely be less of an issue though as it would just delegate.

I do not see any relation to this issue at all. If we add the proposed methods, it is up to the implementation to get the address and port somewhere. Whether it internally refers to Servlet API, HTTP API, a proprietary API, or simply solves the request on its own, does in no way bring us into such a situation like @Context and CDI. Maybe I missed something?

jamezp commented 2 months ago

My only thought was if this new API had something like HttpRequest.remotePort() and HttpRequest.remoteAddress() and you could inject the HttpRequest we now have two ways of getting the remote address/port.

In this case likely not a big deal to have it duplicated in two places. We don't even know if that new API will come to fruition. I just wanted to put it out there that there is potential for duplication.

jansupol commented 2 months ago

The Server the implementation is hooked in does not need to provide the information. For instance, I can see Grizzly and Jetty does so, whereas in Netty I cannot find such information.

mkarg commented 2 months ago

My only thought was if this new API had something like HttpRequest.remotePort() and HttpRequest.remoteAddress() and you could inject the HttpRequest we now have two ways of getting the remote address/port.

In this case likely not a big deal to have it duplicated in two places. We don't even know if that new API will come to fruition. I just wanted to put it out there that there is potential for duplication.

I do not see a problem in duplication.

mkarg commented 2 months ago

The Server the implementation is hooked in does not need to provide the information. For instance, I can see Grizzly and Jetty does so, whereas in Netty I cannot find such information.

ChatGPT told me Netty will do it like this:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class NettyHttpServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new HttpRequestDecoder());
                        ch.pipeline().addLast(new HttpResponseEncoder());
                        ch.pipeline().addLast(new SimpleChannelInboundHandler<FullHttpRequest>() {
                            @Override
                            protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) {
                                System.out.println("Remote Address: " + ctx.channel().remoteAddress());
                                System.out.println("Remote Port: " + ((InetSocketAddress) ctx.channel().remoteAddress()).getPort());
                            }
                        });
                    }
                })
                .option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture f = b.bind(8080).sync();
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}