What's the proper way to set the Location header for an HTTP 201 response in a Java Servlet application

66,839

Solution 1

Just send the absolute path. The restriction to an absolute URI is a known defect in RFC 2616 and will be fixed in HTTPbis (see http://trac.tools.ietf.org/wg/httpbis/trac/ticket/185).

Please note that RFC 7231 now includes relative URIs in the spec. See other answers for how to handle relative URIs.

Solution 2

Unfortunately, the servlet API does not provide a method which directly returns the absolute URL up to with the context root. For that I have several times had to use a combination of getRequestURL(), getRequestURI() and getContextPath().

String absoluteContextRootURL = request.getRequestURL().toString().replace(request.getRequestURI().substring(1), request.getContextPath());

Solution 3

In case you are using JAX RS, there is a method in javax.ws.rs.core.Response which automatically converts relative URLs:

public static Response.ResponseBuilder created(java.net.URI location)

Create a new ResponseBuilder for a created resource, set the location header using the supplied value.

Parameters:

  • location - the URI of the new resource. If a relative URI is supplied it will be converted into an absolute URI by resolving it relative to the request URI.

Note however that there is a bug in the JAX RS implementation CXF which leads to incorrect absolute URLs.

Solution 4

Decided to go with Julian Reschke's advice and violate the spec! At least I added the following comment:

        /* Note: strictly speaking (per section 14.30 of RFC 2616), the Location header 
         * requires an *absolute URI*. However, in practice, many web 
         * applications send an *absolute path* instead. This is interoperable, 
         * that is, works in popular web browsers  (according to 
         * http://en.wikipedia.org/wiki/HTTP_location).
         * 
         * As the information required to set the Location header to an absolute URI
         * is not generally available to a Servlet, we go with the flow and send
         * an absolute path instead (in violation of RFC 2616).
         * 
         * There is an issue filed with Hypertext Transfer Protocol Bis (httpbis) 
         * working group to resolve this problem here:
         * http://trac.tools.ietf.org/wg/httpbis/trac/ticket/185
         */
        response.setHeader("Location", url);

The reason I don't want to send the absolute URI myself is because I have seen problems with this when behind load-balancers and other production infrastructure. Although in dev mode "http://localhost:8080/foo" tends to work fine :))

Will accept Julian's answer now ...

Solution 5

You might try

new URL(new URL(request.getRequestURL().toString()), url).toString();

That will at least be smart about canonicalizing away any .. or other oddities. Other than that, I don't think it's much better than string manipulation.

Share:
66,839
les2
Author by

les2

Programmer who currently specializes in enterprise Java, that is, Java, Spring, Hibernate, Maven, ... I'd like to build some real apps with Grails or Scala/Lift, but no one seems to be paying for that around here. I also do some JavaScript hackery when forced. For example, I did the map on this site: https://givelocal.drivetoendhunger.org/stats (Google Maps, GeoXML3, Dojo).

Updated on January 07, 2020

Comments

  • les2
    les2 over 4 years

    Consider the following code sending an HTTP 201 "Created" response to the client:

        String url = "/app/things?id=42"; // example
        response.setStatus(HttpServletResponse.SC_CREATED);
        response.setContentType("text/plain");
        response.setHeader("Location", url);
        response.getWriter().print(url);
    

    It informs the client that a new "thing" was created and that it can be found at the URL /app/things?id=42. The problem is that this URL is relative. This would be perfect for a JSP, which might be written as follows:

    <img src="<c:url value="/things?id=42" />" />
    

    Which would produce the following HTML:

    <img src="/app/things?id=42" />
    

    Which is what we want for web apps.

    But I don't believe that is what we want for a 201 response Location header. The HTTP spec states:

    14.30 Location

    The Location response-header field is used to redirect the recipient to a location other than the Request-URI for completion of the request or identification of a new resource. For 201 (Created) responses, the Location is that of the new resource which was created by the request. For 3xx responses, the location SHOULD indicate the server's preferred URI for automatic redirection to the resource. The field value consists of a single absolute URI.

           Location = "Location" ":" absoluteURI
    

    An example is:

           Location: http://www.w3.org/pub/WWW/People.html
    

    My question is how do I translate that relative URL to the abosolute URL for the Location header in the proper way for servlets.

    I do NOT believe that using:

    request.getServerName() + ":" + request.getServerPort() + url;
    

    Is the correct solution. There should be a standard method that produces the correct output (so that URL rewriting, etc., can be applied). I don't want to create a hack.

  • Julian Reschke
    Julian Reschke about 13 years
    Once the HTTPbis specs are published, you won't be in violation of the applicable spec anymore.
  • Claudiu
    Claudiu over 8 years
    Note that RFC 7231 now includes relative URIs in the spec, see here: "The field value consists of a single URI-reference. When it has the form of a relative reference ([RFC3986], Section 4.2), the final value is computed by resolving it against the effective request URI ([RFC3986], Section 5)."