30

I'm writing a Java RESTful service hosted on Heroku based on an example -> https://api.heroku.com/myapps/template-java-jaxrs/clone

My sample service is:

package com.example.services;

import com.example.models.Time;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/time")
@Produces(MediaType.APPLICATION_JSON)
public class TimeService {

    @GET
    public Time get() {
        return new Time();
    }

}

My main is:

public class Main {

    public static final String BASE_URI = getBaseURI();

    /**
     * @param args
     */
    public static void main(String[] args) throws Exception{
        final Map<String, String> initParams = new HashMap<String, String>();
        initParams.put("com.sun.jersey.config.property.packages","services.contracts"); 

        System.out.println("Starting grizzly...");
        SelectorThread threadSelector = GrizzlyWebContainerFactory.create(BASE_URI, initParams);
        System.out.println(String.format("Jersey started with WADL available at %sapplication.wadl.",BASE_URI, BASE_URI));
    }

    private static String getBaseURI() 
    {
        return "http://localhost:"+(System.getenv("PORT")!=null?System.getenv("PORT"):"9998")+"/";      
    }

}

My question is how can I find out in my service the IP address and port combination the request is coming from? I've read stuff on @Context which injects javax.ws.rs.core.HttpHeaders, javax.ws.rs.core.Request, etc. However, no incoming IP or port info is present.

I know if you implement com.sun.grizzly.tcp.Adapter, you can do something like:

public static void main(String[] args) {
    SelectorThread st = new SelectorThread();
    st.setPort(8282);
    st.setAdapter(new EmbeddedServer());
    try {
        st.initEndpoint();
        st.startEndpoint();
    } catch (Exception e) {
        System.out.println("Exception in SelectorThread: " + e);
    } finally {
        if (st.isRunning()) {
            st.stopEndpoint();
        }
    }
}

public void service(Request request, Response response)
        throws Exception {
    String requestURI = request.requestURI().toString();

    System.out.println("New incoming request with URI: " + requestURI);
    System.out.println("Request Method is: " + request.method());

    if (request.method().toString().equalsIgnoreCase("GET")) {
        response.setStatus(HttpURLConnection.HTTP_OK);
        byte[] bytes = "Here is my response text".getBytes();

        ByteChunk chunk = new ByteChunk();
        response.setContentLength(bytes.length);
        response.setContentType("text/plain");
        chunk.append(bytes, 0, bytes.length);
        OutputBuffer buffer = response.getOutputBuffer();
        buffer.doWrite(chunk, response);
        response.finish();
    }
}

public void afterService(Request request, Response response)
        throws Exception {
    request.recycle();
    response.recycle();
}

and access

    request.remoteAddr()

But I'd really like to separate my RESTful API in a more structured way like in my first implementation.

Any help would be greatly appreciated. Thanks!

Luke
  • 1,053
  • 2
  • 16
  • 26

3 Answers3

59

You can inject HttpServletRequest:

@GET
@Produces(MediaType.TEXT_PLAIN)
public Response getIp(@Context HttpServletRequest req) {
    String remoteHost = req.getRemoteHost();
    String remoteAddr = req.getRemoteAddr();
    int remotePort = req.getRemotePort();
    String msg = remoteHost + " (" + remoteAddr + ":" + remotePort + ")";
    return Response.ok(msg).build();
}
  • Thanks for the info, however when I run this, I get an IP that's different than the public IP of the client that made the request. I'm running the service on Heroku, and I'm afraid it might be some Heroku intermediary's IP (the one that does the routing/load balancing). How would I go about fixing that? – Luke Nov 02 '12 at 03:22
  • 2
    I did a host name look up, and the req.getRemoteHost() is indeed a private IP `ip-10-42-223-182.ec2.internal`, which seems to be a EC2 IP. – Luke Nov 02 '12 at 03:40
  • 11
    I figured it out, you'll have to extract `X-Forwarded-For` header from the Http Header. – Luke Nov 02 '12 at 09:11
  • 5
    Just wanted to mention to anyone getting here for maven configuration using JAX-RS for the problems stated in the original poster's question. You have to include the `javax-servlet` for `servlet-api` in your `pom.xml` ( http://mvnrepository.com/artifact/javax.servlet/servlet-api/2.5) and then `import javax.servlet.http.HttpServletRequest`. `Context` class is already in the JAX-RS: `import javax.ws.rs.core.Context`. – Sonny Apr 03 '14 at 14:51
  • Hi @Sonny Thanks for your comment. Do you know any way to get remote IP in JAX RS way(without servlet-api)? – Nilesh Jul 10 '17 at 05:43
12

As Luke said, when using Heroku, the remote host is the AWS application tier, therefore the EC2 ip address.

"X-Forwarded-For" header is the answer:

String ip = "0.0.0.0";
try {
    ip = req.getHeader("X-Forwarded-For").split(",")[0];
} catch (Exception ignored){}
Eitan
  • 367
  • 3
  • 8
8

Based on @user647772 and @Ethan fusion. Thanks to them ;)

inject HttpServletRequest:

@GET
@Produces(MediaType.TEXT_PLAIN)
public Response getFromp(@Context HttpServletRequest req) {
    String from = _getUserFrom(req);
    return Response.ok(from).build();
}

private String _getUserFrom(HttpServletRequest req) {
    String xForwardedFor = req.getHeader("X-Forwarded-For");
    xForwardedFor = xForwardedFor != null && xForwardedFor.contains(",") ? xForwardedFor.split(",")[0]:xForwardedFor;
    String remoteHost = req.getRemoteHost();
    String remoteAddr = req.getRemoteAddr();
    int remotePort = req.getRemotePort();
    StringBuffer sb = new StringBuffer();
    if (remoteHost != null 
    && !"".equals(remoteHost)
    && !remoteHost.equals(remoteAddr)) {
        sb.append(remoteHost).append(" ");
    }
    if (xForwardedFor != null 
    && !"".equals(xForwardedFor)) {
        sb.append(xForwardedFor).append("(fwd)=>");
    }
    if (remoteAddr != null || !"".equals(remoteAddr)) {
        sb.append(remoteAddr).append(":").append(remotePort);
    }
    return sb.toString();
}
boly38
  • 1,806
  • 24
  • 29