How does Jersey / Jax-RS picks the right method in a resource - java

I am using latest version of Jersey for an API server.
I defined the next resource:
#javax.ws.rs.Path("/myPath")
public class MyResource {
#GET
#Consumes({MediaType.WILDCARD, MediaType.TEXT_PLAIN, MediaType.TEXT_HTML})
#Produces(MediaType.TEXT_PLAIN)
public Response method1(#Context Request request) {
}
#GET
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public MyObject method2() {}
}
Now lets say I am calling this resource with the next header:
<header>
<name>Accept</name>
<value>text/html, application/xhtml+xml, */*</value>
</header>
How does Jersey knows which one to match to in this case where no method Producer annotation is matching?
I am asking because one time the server responded to the method2 and after restarting it, it responded to method1.

<header>
<name>Accept</name>
<value>text/html, application/xhtml+xml, */*</value>
</header>
I don't know what that it; headers aren't sent in XML, but assuming you sent the header correctly, here's how it's broken down.
#Produces handles the Accept header, and #Consumes handles the client Content-Type header, when the client sends data. So let's look at your two #Produces annotations, against the Accept header
#Produces(MediaType.TEXT_PLAIN)
public Response method1() {}
#Produces(MediaType.APPLICATION_JSON)
public MyObject method2() {}
text/html, application/xhtml+xml, */*
So neither of them have text/html, so cross that out. Neither of them have application/xhtml+xml, so cross that out. That only leaves */*, which means "send me whatever". So Jersey is free to choose which one. The results are unpredictable. You can't make any assumptions about it. And that's the client's fault. Not ours. The client should send the correct header. Or maybe we should do a better job documenting our API, so the client knows what types we can produce :-)

I ran into the same issue, where I have to methods with a specific #Produces() annotations. This trick works:
#Produces(MediaType.APPLICATION_JSON)
public Response method1() {}
#Produces({MediaType.TEXT_PLAIN, "*/*;q=0"})
public Response method2() {}
When using MIME types, you can add the q property, which indicates priority (0 to 1). The absence of the q property implies 1, but apparently the q=0 tricks Jersey to use the other function.
It's kind of a hack, so I don't know if it will remain working, but helped me out.

Related

Two methods in Rest resource file with same #Path but different mediaType output

I have 2 methods in my Java Rest resource file with same #Path uri but different #produces. The code below :
#GET
#Path("/messages")
#Produces(MediaType.APPLICATION_XML)
public List<Message> getAllMessages() {
return new ArrayList<Message>(service.getMessageMap().values());
}
#GET
#Path("/messages")
#Produces(MediaType.APPLICATION_JSON)
public List<Message> getAllMessagesJSON() {
return new ArrayList<Message>(service.getMessageMap().values());
}
when i test it with POSTMAN rest client i always get JSON output!!
Can some one explain why?? And if i want to get xml as well as json outputs, what to do??
I tried changing the content-type to application/xml..but i always get json!!
Content-Type is for the type of data being sent, either by the client as a request header or by the server as a response header. So you as a client sending the header is useless, as you are not sending any data. For the client, when it want to tell the server what type it wants, it uses the Accept: <media-type> header.
When there is no Accept header set, it usually defaults to */* leaving it up for grabs which method to pick in your case.

How to create RESTful web service client by Jersey2.0 or above

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.

POST request not working with JSON input using Restlet

I'm using Restlet framework to implement a POST request for a REST resource, which is supposed to accept JSON formatted data. Problem is, I keep getting the 415 Unsupported Media Type error.
The odd thing is that I've set a breakpoint right inside the function responsible for handling the POST request, and when the input is of application/json, debug does NOT stop at the breakpoint (meaning the function handling POST requests is not even called, and the error just comes beforehand). However if I change the input to multipart/form-data or application/x-www-form-urlencoded, debug DOES stop at the breakpoint. So why isn't the POST function called when input is of application/json type ?
Here is the request:
POST /res2 HTTP/1.1
Host: localhost:8888
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 121a2782-0b4e-f592-8d78-26f07862d5fd
{"id":3,"name":"John Smith","age":23,"gender":"Male"}
The output HTML message states:
The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method.
Main Application code:
package com.poc.hw11;
import xyz (trimming to save space)
public class JSON_POC extends Application
{
#Override
public Restlet createInboundRoot()
{
Router router = new Router(getContext());
router.attach("/res1", Resource1.class);
return router;
}
}
Resource1 Class:
package com.poc.hw11;
import xyz (trimming to save space);
public class Resource1 extends ServerResource
{
#Post
public void addPerson() {
Request request = getRequest(); // BREAKPOINT SET ON THIS LINE. DEBUG DOESN'T REACH THIS POINT WHEN INPUT IS OF application/json TYPE
Response response = getResponse();
//Rest of code here.
}
}
I have also tried changing #Post to #Post("json"), but the result is the same .. Any ideas ?
if you want manually handle the incoming representation, I would you the following syntax:
#Post
public void addPerson(Representation rep) {
System.out.println(rep.getMediaType());
Request request = getRequest();
Response response = getResponse();
}
But I would let automatic converter handle this:
Create a bean Contact having the structure of your json, then let the jackson converter deal with deserialization :
#Post
public void addPerson(Contact contact) {
System.out.println(contact.getName());
}
In order to add the Jackson converter, just complete the classpath of your application with the Jackson extension for Restlet Framework (org.restlet.ext.jackson.jar) and its dependencies (the jackson libraries com.fasterxml.*.jar shipped with Restlet)
Please fell free to ask for more details.
The default content type for post requests is url-encoded form. If you need to handle some other type, you need to find the way to specify that in your code. For example, if I were using Jersey, I would use the #Consumes annotation to specify the expected content type for this post request. You need to find out how to do the same in restlet framework.

How to deal with accept-parameters when developing a jax-rs application

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!

Jersey: #Consumes doesn't quite work when content-type is not set

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?

Categories