Jersey produces unexpected default media type when

2019-07-04 05:28发布

I am developing a web service using the JAX-RS API with Jersey 1.17 as my implementation.

I want clients to have the choice between JSON and XML which they specify using the Accept HTTP header. I want JSON to be the default when a client does not include the Accept header in the request. I have tried to achieve this by placing MediaType.APPLICATION_JSON before MediaType.APPLICATION_XML in the Produces annotation.

This seems to work in normal situations:

$ curl 'http://localhost:8080/webservice/Bob'
{"text":"Hello, Bob"}

$ curl -H'Accept: application/json' 'http://localhost:8080/webservice/Bob'
{"text":"Hello, Bob"}

$ curl -H'Accept: application/xml' 'http://localhost:8080/webservice/Bob'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Greeting text="Hello, Bob"/>

But if I throw a WebApplicationException from the constructor of my resource class, the response media type defaults to XML:

$ curl 'http://localhost:8080/webservice/Vader'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Error message="Illegal name"/>

If the client includes the Accept header the media type is correct:

$ curl -H'Accept: application/json' 'http://localhost:8080/webservice/Vader'
{"message":"Illegal name"}

How can I configure Jersey to use the default even for errors that are thrown from the resource class constructor?

Here is the code of my resource class (full example on GitHub):

package org.example.errorhandling;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.example.errorhandling.repr.Error;
import org.example.errorhandling.repr.Greeting;

@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Path("/{name}")
public class Greeter {
    private final String name;

    public Greeter(@PathParam("name") String name) {
        if ("Vader".equals(name)) {
            Error error = new Error();
            error.message = "Illegal name";
            Response errorResponse = Response.status(Status.BAD_REQUEST).entity(error).build();
            throw new WebApplicationException(errorResponse);
        } else {
            this.name = name;
        }
    }

    @GET
    public Response greet() {
        Greeting greeting = new Greeting();
        greeting.text = "Hello, " + name;
        return Response.ok(greeting).build();
    }
}

2条回答
SAY GOODBYE
2楼-- · 2019-07-04 05:56

This problem has a workaround due to a suggestion from usul_.

The workaround is to use the selectVariant() mechanism to programmatically choose a media type that matches the Accept header in the request. This makes it possible to enforce the default media type at the cost of repeating the preferred order.

Here is the constructor modified to use this technique (full code on GitHub):

public Greeter(@PathParam("name") String name, @Context Request request) {
    if ("Vader".equals(name)) {
        Error error = new Error();
        error.message = "Illegal name";
        List<Variant> variants = Variant.mediaTypes(MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE).add().build();
        Variant variant = request.selectVariant(variants);
        Response errorResponse = Response.status(Status.BAD_REQUEST).entity(error).variant(variant).build();
        throw new WebApplicationException(errorResponse);
    } else {
        this.name = name;
    }
}
查看更多
登录 后发表回答