Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTTP content negotiation conflicts in JAX-RS/Jersey?

I am enjoying the auto HTTP content negotiation of JAX-RS (specifically Jersey), i.e. its ability to route my resources by "Accept" and/or "Content-Type" headers. But I'm finding that sometimes it doesn't give me enough control when there is a conflict.

For example, consider the following endpoints:

@Path("/order")
public class OrderController {

    @GET
    @Path("{orderID: \\d+}")
    @Produces("text/html")
    public View getOrderView(@PathParam("orderID") long id) {
        Order order = this.getOrderData(id);
        return new OrderView(order);
    }

    @GET
    @Path("{orderID: \\d+}")
    @Produces({"application/json", "application/xml"})
    public Order getOrderData(@PathParam("orderID") long id) {
        return new OrderService.findOrder(id);
    }
}

I will get different results between Firefox and Chrome. Firefox will map to the HTML endpoint while Chrome will trigger the XML endpoint when I navigate each to the endpoint URL. The difference between them is the order of the listed MIME types in their Accept headers. Chrome sends the following:

User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.107 Safari/534.13
Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5

Versus in Firefox it lists HTML first:

User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Seems logical that it would match the first entry when all are weighted the same. But in my case I am getting the different results than I want so it would be nice to determine a better method for tie-breaking.

My question: short of injecting header information into these methods and performing the media type processing myself, is there a way to "tweak the weights" so to speak in the event of a tie? For instance, can I tell it to always trump XML with HTML? My RESTful clients are very explicit about what type they want back but browsers are notoriously sloppy with Accept headers. (Personally I think they should weight HTML slightly above XML as this is what users expect but it's a little late for that.)

Alternatively, can I perform my own custom content negotiation only once in some centralized location? I'm not opposed to writing this logic out manually but not if it means applying it to every single instance of my resources. Does JAX-RS have some concept of adding a filter to the pipeline to tweak requests before they are routed?

like image 417
mckamey Avatar asked Mar 09 '11 19:03

mckamey


2 Answers

There is a mechanism in Jersey to override the relative degree of preference from the HTTP Accept header. Just add a parameter "qs" to the @Produces annotation that you want to take precedence. In your case: @Produces("text/html;qs=2") Note that the http "q" values range from 0-1 and the Jersey "qs" values should be >= 1 (1 is default).

(I learned about this from this source, and I wrote a little note for myself here)

like image 183
Erik Avatar answered Nov 11 '22 13:11

Erik


As the Jersey User Guide states:

If both are equally acceptable then the former will be chosen because it occurs first.

From what I know, this leaves you with two possibilities/hacks:

  1. Append the file extension to your URIs to override the Accept header

  2. Write a servlet filter that overwrites the Accept header for those User Agents

like image 22
Christoph Metzendorf Avatar answered Nov 11 '22 13:11

Christoph Metzendorf