In order to deal with different versions of a content-type i am trying to use the accept-parameters of the "Accept*" headers (RFC 2616).
Accept: application/vnd.mycompany.mytype;version=2 , application/vnd.mycompany.mytype;version=1;q=0.1
The problem is that Jax-RS annotations do not support Accept-parameters...
#GET
#Produces("application/vnd.test;version=1")
public Response test1() {
return Response.ok("Version 1", "application/vnd.test").build();
}
#GET
#Produces("application/vnd.test;version=2")
public Response test2() {
return Response.ok("Version 2", "application/vnd.test").build();
}
Results in a media type conflict exception:
Producing media type conflict. The resource methods public javax.ws.rs.core.Response test.resources.TestResource.test2() and public javax.ws.rs.core.Response test.resources.TestResource.test1() can produce the same media type
Maybe, this exception only related to my JAX-RS framework (Jersey), but i'm afraid this is due to the JSR311 which is not explicit about accept-parameters.
By now, i'm using content-types which holds the version within their names, but i found this solution pretty uggly.
#GET
#Produces("application/vnd.test-v1")
public Response test() {
return Response.ok("Version 1", "application/vnd.test-v1").build();
}
Do you have any ideas about how to deal with accept-parameters ?
EDIT
I think i wasn't clear enough.
I want to automatically route the request to specific methods.
These methods are versioned and correspond to a specific version of the returned content-type.
JAX-RS current implementation prevents me to use accept-parameters to route the request (to the corresponding method).
greenkode suggest that i manage the version accept-parameter within a dispatching method (using #HeaderParam("Accept")).
This solution would end-up in re-writing the content negociation logic which is embeded in the framework (and described in JSR 311).
What can i do to use both accept-parameter and content-negociation logic from JAX-RS ?
Maybe a solution is to use another framework (I only worked with Jersey by Now). But i don't know which one.
The JAX-RS specification does not explicitly state anything about ignoring Accept header parameters. But the only parameter for which handling is definitely defined is quality (q). This is a possible area for improvement as it seems to have lead to ambiguity (or outright bugginess) in the Jersey implementation. The current version of Jersey (1.17) does not take Accept header parameters into consideration when matching incoming requests to resource methods, which is why you are getting the error:
SEVERE: Producing media type conflict. The resource methods ...
For the resource:
#GET
#Produces("application/vnd.test;version=1")
public Response test1() {
return Response.ok("Version 1", "application/vnd.test").build();
}
#GET
#Produces("application/vnd.test;version=2")
public Response test2() {
return Response.ok("Version 2", "application/vnd.test").build();
}
It would appear that Jersey performs a 'uniqueness' check based on the Accept header 'type/subtype', totally omitting any parameters. This can be confirmed by testing with various pairs of headers on the 'matching' resource methods:
Resource 1 Resource 2
----------------------------------------
text/html;q=0.4 text/html;q=0.8
text/html text/html;q=0.2
text/html text/html;qs=1.4
text/html;qs=1.4 text/html;qs=1.8
text/html;level=1 text/html;level=2
text/html;foo=bleh text/html;bar=23
All fail with the same error. If the assumption were made that only the quality parameter is ever sent, then it would make sense to only match on 'type/subtype', because this kind of request is nonsensical:
Accept: text/html;q=0.8, text/html;q=0.4, text/html
Aka, quality parameters only make sense when you are dealing with a mix of possible content types. However, this sort of limited matching fails when non-quality parameters or additional parameters are being sent:
Accept: text/html;version=4.0;q=0.8, text/html;version=3.2;q=0.4
So what are the possible solutions?
Intercept the high level request based off 'type/subtype', then route to more appropriate method (you've indicated you do not want to do this)
Modify your expected headers. For example 'application/vnd.mycompany.mytype+v2' and 'application/vnd.mycompany.mytype+v1'. No other changes would be required and you could keep on using Jersey
Switch frameworks. RESTEasy happens to handle your scenario with ease.
With RESTEasy, and resource:
#Path("/content/version")
public class ContentVersionResource {
#GET
#Produces("application/vnd.test;version=1")
public Response test1() {
return Response.ok("Version 1", "application/vnd.test").build();
}
#GET
#Produces("application/vnd.test;version=2")
public Response test2() {
return Response.ok("Version 2", "application/vnd.test").build();
}
}
A successful match is made with the following Accept header:
Accept: application/vnd.test;version=1;q=0.3, application/vnd.test;version=2;q=0.5
Response: Version 2
And this:
Accept: application/vnd.test;version=1;q=0.5, application/vnd.test;version=2;q=0.3
Response: Version 1
You can download and test with this sample project. Git, Maven and JBoss 7.x required
Unless I'm missing something. JAX-RS does support Accept parameters. look at the #Consumes("*/*") annotation. Also, The exception you're getting with media type conflict occurs because you have two GET methods at the same url. annotate the test2() method with #Path("test2"), then send your GET request to url/test2 instead. that should get rid of that error.
EDIT
You can inject the Value of the Accept header using #HeaderParams. Here's a sample of what I did.
#Path("/conneg")
public class ConnNeg {
#GET
#Produces("application/vnd.test;version=1")
public Response test1(#HeaderParam("Accept") String header) {
System.out.println(header);
return Response.ok("Version 1", "application/vnd.test").build();
}
}
passing the request
Accept: application/vnd.test;version=2 , application/vnd.test;version=1;q=0.1
this will print
application/vnd.test;version=2 , application/vnd.test;version=1;q=0.1
You can then handle it manually. Is this what you're looking for?
With Jersey framework, the Accept header of the HTTP request declared what is most acceptable. If a resource class is capable of producing more that one MIME media type then the resource method chosen will correspond to the most acceptable media type as declared by the client.
In your case, if the accept header is
Accept: application/vnd.mycompany.mytype;version=2
then the method test1() will be invoked.
If it is
Accept: application/vnd.mycompany.mytype;q=0.9 version=2, application/vnd.mycompany.mytype;version=1
the latter one will be called.
More than one media type may be declared in the same #Produces declaration, for example:
#GET
#Produces({"application/vnd.mycompany.mytype; version=2", "application/vnd.mycompany.mytype; version=1"})
public Response test() {
return Response.ok("").build();
}
the test(9 method will be called if either of the 2 mediatypes is acceptable. if both are acceptable, the first will be invoked.
Hope it helps!
Related
We started using Jersey/JAX-RS for internal REST endpoints that get used by our front-end code. Endpoints that have to return a result, always send JSON objects.
For debugging purposes, we are using the firefox restclient extension. Until recently, I would just enter the URL and hit send, and would get back content displayed as JSON.
But when I did that this morning, the FF extension comes back and tells me that I have to change the response type to binary (BLOB). Doing so results in displaying an encoded string instead of JSON.
I could resolve that by setting a request header (Accept: to be application/json).
Doing some more research, I came across this question. My conclusion is: probably we should add #Produces("application/json") to all these endpoints.
Question: is it really that simple, or are there good technical reasons to not do that?
You should always declare the #Produces and #Consumes annotations (either at the class level or method level) for the purpose of Content Negotiation and HTTP protocol correctness. Without these annotations, the result will be dependent on the client request and the default behavior of the server (which may be different across implementations), which leads to unpredictable and ambiguous results.
With these annotations, we advertise what media types we can produce and consume. On Retrieve (GET) requests, the client should send an Accept header with the media type of the resource they expect back. And on Create requests (PUT, POST), the client should send a Content-Type header telling the server what media type the data is that they are sending. If these headers don't match what the server is advertised to handle, then the client will get error responses back telling them what the problem is; with a Retrieve request and a non-matching Accept header, the response will be a 406 Not Acceptable. With a Create request and a non-matching Content-Type header, the response will be a 415 Unsupported Media Type.
This is how content negotiation works. And to make sure our server behaves as the clients expect, we should declare what we can handle on the server. The annotations do just this.
As you mentioned, when you left off the #Produces, the client told you you needed to change the response type. This is because the result was that the Content-Type response header was set to application/octet-stream, which is what the answers here conclude. Clients use the Content-Type header to determine how to handle the response.
That last example was for a Retrieve request. If we left off the #Consumes on a Create endpoint, a lot of different things can go wrong. Take for example we have an endpoint that we want to accept JSON, so we create a POJO to map the JSON to.
#POST
public Response create(Customer customer) {}
For this to work, it is dependent on the client setting the Content-Type header on the request to application/json. But without the #Consumes annotation, we are basically advertising this endpoint to be able to accept any media type, which is just ridiculous. The #Consumes annotation acts like a guard saying "If you don't send the right type of data, you cannot pass". But since we don't have the guard, all data is allowed through, and the result is unpredictable, because depending on what the client sets the Content-Type to, we don't know what MessageBodyReader1 will handle the conversion from the entity body to Customer. If the correct MessageBodyReader is not chosen (the one that converts JSON to POPJOs), then most likely it will lead to an exception, and the client will get back a 500 Internal Server Error, which is not as specific as getting a 415 Unsupported Media Type.
1. See chapter 8 and 9 of the Jersey docs. It will explain how entity bodies are converted to Java objects (and vice versa) using MessageBodyReader and MessageBodyWriter, respectively.
Is it good practice to use #Produces("application/json") on all JSON producing endpoints?
If your resource methods produce JSON as representation of your resources, they should be annotated with #Produces(MediaType.APPLICATION_JSON). As a result, the response will have a Content-Type header indicating the media type of the payload.
The #Produces annotation is also used for request matching: The JAX-RS runtime matches the media type sent in the Accept header with the media type defined in the #Produces annotation.
If you don't want to annotate every resource method in your application, you can annotate the resource classes instead. It will indicate that all methods defined in such class must produce JSON as representation of your resources.
The media type defined in the #Produces annotation indicates the media type that will be produced by the MessageBodyWriter instances registered in the application. Consider the following example:
#GET
#Produces(MediaType.APPLICATION_JSON)
public Foo getFoo() {
Foo foo = new Foo();
return Response.ok(foo).build();
}
Once the getFoo() method is annotated with #Produces(MediaType.APPLICATION_JSON), JAX-RS will write the Foo instance as a JSON document. It's done in a MessageBodyWriter implementation. If your application uses Jackson, for example, the JacksonJsonProvider will be used to convert Java objects to JSON documents.
I am trying to understand the MIME types in RESTful services.If there is a resource method (sayHello) as in below
#Path("/customers")
public class CustomerResource {
#GET
public String sayHello(){
return "Hello World";
}
}
I didn't put any #Produces or #Consumes annotation on the above method. I have below questions.
Is there any default MIME type above method uses to send the response ?
Or Does it produce response content type based on "accept-type" from incoming request ?
Can it happen that request does not specify what content type does it accept ? If that is the case, what response content type does above resource method will produce ?
Thanks in advance.
Often it will default to application/octet-stream, unless the client set the Accept header, in which case, that's the type that will be used.
One thing you need to be careful about is that there is a not a MessageBodyWriter to handle application/octet-stream and most types. For instance say you have
#GET
public SomeModel get() {}
If the client doesn't set a Accept: application/json header, then the runtime will look for a MessageBodyWriter to that can serialize SomeModel to application/octet-stream, and it will fail and you will get an exception saying something like "No MessageBodyWriter found for mediatype application/octet-stream and type SomeModel".
So it is always a good idea to put the #Produces. Another benefit is that you determine what types the endpoint can produce. For instance if you have #Produces("application/json"), and the client set the Accept: application/xml header, then they will get a 406 Not Acceptable error code, which is what should happen.
Notice the first paragraph, I used the word Often. It is not always the case. For some instances, the the runtime will make assumptions based on the return type. For instance if you return a String it is likely the runtime will choose text/plain.
But like I said, it's always better to have the #Produces and #Consumes annotations. These are hints not just for the developer but for the runtime to make decisions.
There seems to be many examples about creating RESTful clients by Jersey 1.x, but not Jersey 2.0 or above.
I referred to other questions and the Jersey's web site, but I still cannot create a client for REST due to the differences between Jersey 2.0 and the previous one.
So I'd like to ask some advice.
So far, my coding is like this.
ClientConfig config = new ClientConfig();
Client client = ClientBuilder.newClient(config);
WebTarget target = client.target("http://localhost:8080/CustomerBack2211/webresources/entities.customer");
Invocation.Builder invocationBuilder = target.request(MediaType.TEXT_XML_TYPE);
Response response = invocationBuilder.get();
System.out.println(response.getStatus());
System.out.println(response.readEntity(String.class));
This produces 406 error.
However, when I tried to test RESTful service by Glassfish server, the test works properly, and the server side class has its #GET methods having #Produces({"application/xml", "application/json"}).
So I don't see why the coding above produces 406 error on a Java application.
(i.e. the client side has #GET methods in the following way)
#GET
#Path("{id}")
#Produces({"application/xml", "application/json"})
public Customer find(#PathParam("id") Integer id) {
return super.find(id);
}
#GET
#Override
#Produces({ "application/xml"})
public List<Customer> findAll() {
return super.findAll();
}
Does any of you see what I'm doing wrong, or could you please suggest an example of a RESTful client?
Any advice will be helpful...thanks in advance!
In addition, I'd appreciate if you would offer information about how to invoke methods like GET, PUT and DELETE with appropriate parameters.
I just needed to put an ID number (i.e. integer values) when I was testing the server side class on Glassfish RESTful test. However, it seems that I need to set "Class" and/or "Entity" values as arguments, but I cannot see any information associated with them on the Jersey website.
For the first block of code:
406 means Not Acceptable.
Look at your request() method target.request(MediaType.TEXT_XML_TYPE). From the Javadoc of request() if states
Invocation.Builder request(MediaType... acceptedResponseTypes)
Start building a request to the targeted web resource and define the accepted response media types.
Invoking this method is identical to:
webTarget.request().accept(types);
So basically, in your request, you are saying that you will only Accept: text/plain. Now look at your resource methods. Look at the #Produces. None of them "produce" text/plain. It's all json or xml. That's why you get the exception. Change the accept to application/xml (or MediaType.APPLICATION_XML) on the client side, and you should no longer get this error.
For the second question: I'm assuming you mean why does it work when you test it from the browser.
If you send a request from the browser by simply typing in the url, it will send out the request with many Accept types. If you have firebug (for FireFox) or the developer tools (for Chrome), if you send out a request, you will see a header similar to
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
You can see application/xml in there. Even if application/xml wasn't there, the wild card */* is there, so basically almost all media types are acceptable as a return type when working in the browser.
For your last question:
Look at the API for SyncInvoker, which Invocation.Builder extends from. You will see different overrloaded put and post methods, most of which, as you mentioned accept an Entity.
There are a few different ways to build an Entity, all of which use one of the static methods. Here are some
Entity.entity( body, mediaType )
Entity.json( body )
Entity.xml( body )
And many more (see the Entity link above). But all of these static method return an Entity. So we could do something like
// resource method
#POST
#Consumes(MediaType.APPLICATION_XML)
public Response getResponse(Customer customer) { ... }
// some model class
#XmlRootElement
public class Customer { ... }
// client request
Customer customer = new Customer();
Response response = target.request().post(Entity.xml(customer));
Internally, the Customer will get converted to XML. If you used Entity.json is would get converted to JSON, BUT you need to make sure you have a JSON provider dependency. Jersey will not come with one by default. See more at Support for Common Media Type Representations
Also note, with your method find, when you try and make a request to the method, the request should end with an integer value, as that's the type specified for the {id} path parameter.
Currently I am designing how to deal with routing in a versioned rest API where the version is not part of the url, but sent via a header variable.
I have considered/seen things like:
Re-writing the url based on the header variable i.e. the request /orders {X-Media-Type: v1} would rewrite to /v1/orders and then we could have a #Path("/v1/orders") OrderV1Resource {} and thus making v2 would be trivial #Path("/v2/orders") OrderV2Resource {}. (my first preference) How to use a servlet filter in Java to change an incoming servlet request url?
Having a Single #Path("/orders") OrderResource {} where each of it's methods have an injected HeaderParam and I could check the header variable and then decide which Order API implementation I wanted to use (which seems very messy to me)
// pseudo-java code
#Path("/orders")
OrderResource {
OrderV1Impl v1Impl;
OrderV2Impl v2Impl;
#GET
public List<Order> findAll(#HeaderParam header) {
version = header.get("accepts")
if(version.equals("v1")) { return v1Impl.findAll() }
else if(version.equals("v2")) { return v2Impl.findAll() }
return error
}
}
Or just bundling them up in seperated JARS and having a service look at the header and route to the correct version. (seems to make sense if the app grows to be really large)
I ended up passing the version in via a header then used this to route the desired resource via a javax.servlet.Filter.
Example Accept header:
Accept: application/vnd.datarank.v1+json
I'm trying to figure out how #Consumes works here.
I have a simplified resource that looks like the below, and I only want this resource to consume "application/vnd.myApp+xml".
#Path("/app")
#Consumes("application/vnd.myApp+xml")
#Produces("application/vnd.myApp+xml")
public class AppResource {
#POST
public Response postStuff() {
...
}
}
I have the following testcases:-
public class AppResourceTest extends JerseyTest {
#Test
public void testApp() {
// #1: Works fine
ClientResponse response = resource().path("app")
.accept("application/vnd.myApp+xml")
.post(ClientResponse.class);
...
// #2: Throws a 415 Unsupported Media Type
ClientResponse response = resource().path("app")
.accept("application/vnd.myApp+xml")
.type("text/plain")
.post(ClientResponse.class);
...
// #3: Works fine
ClientResponse response = resource().path("app")
.accept("application/vnd.myApp+xml")
.type("application/vnd.myApp+xml")
.post(ClientResponse.class);
...
}
}
From the 3 tests above, #2 and #3 work as expected.
As for #1, if I don't set the content-type, why doesn't it throw a 415 too?
Based on #Consumes api (http://jsr311.java.net/nonav/releases/1.0/javax/ws/rs/Consumes.html) and the HTTP type spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1) coupled with the behavior you are seeing I think it is safe to conclude the following Jersey implementation:
If the Content-Type is NOT set by the client Jersey does NOT default but allows it to pass through any/all #Consumes annotions.
When multiple #Consumes {different types} are set for the URI and the client has NOT set the Content-Type then Jersey will default to the first #Consumes annotation or first type in a list of acceptable types.
When the Accepts header value is set Jersey will find the best fitting method to execute. If multiple methods are a best fit it will default to the first defined.
In conclusion the #Consumes only acts as a filter if and ONLY if the client sets the Content-Type otherwise Jersey will attempt to find the best fitting match. This does match the HTTP spec:
Any HTTP/1.1 message containing an entity-body SHOULD include a Content-Type header field defining the media type of that body. If and only if the media type is not given by a Content-Type field, the recipient MAY attempt to guess the media type via inspection of its content and/or the name extension(s) of the URI used to identify the resource. If the media type remains unknown, the recipient SHOULD treat it as type "application/octet-stream".
If the goal is to have the #Consumes to act as a white list then a servlet filter could be used to default the Content-Type on requests where none is set.
You should specify the type- for example:
ClientResponse res =
service.path("accounts")
.type("application/vnd.dsu.account+json")
.post(ClientResponse.class,ent);
Based on the docs it seems using #Consumes at the class level does not explicitly override method level definitions (which default to */*), so it stands to reason it may be working in an additive manner...
Have you tried applying the same #Consumes on the method definition?