Jax-RS consume ANY Content-Type - java

I have a REST channel with POST method for uploading a binary content. This can be anything.
The method body is as follows:
#POST
#Path("/")
public Response upload(#Context HttpServletRequest httpRequest) {
It has worked when the client has not set Content-Type header, however if he set it, for example, to application/xml, an error occured:
Unsupported Media Type</pre></p><hr>Powered by Jetty:// 9.3.15.v20161220
I've tried adding wildcard:
#Consumes(MediaType.WILDCARD)
however, it still produces the same error.
How can I make jax-rs to accept ANY content type? Or I'm simply using the wrong tool for my job? I've taken Jax-RS because it can consume beans instantiated by IoC frameworks like blueprint...

Related

Is it good practice to use #Produces("application/json") on all JSON producing endpoints?

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.

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 to Jersey from Laravel

I have a simple REST API created with Jersey on Glassfish server.
On the other hand, I have a Laravel application that uses this API. Now, I have several GET routes on Jersey app, and this works with no problem. I used it with curl and everything went well.
At the end, I have one post request that I should send. Form consists of about 50 fields, and I'm not sure how to triger it.
When I put the url to post method into form action attribute, I get this message
HTTP Status 415 - Unsupported Media Type
I have tried with
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
And with
#Consumes(MediaType.APPLICATION_JSON)
And I get the same error with both
I haven't done this but it, but be sure to include the form parameters in the method signature
#POST
#Consumes("application/x-www-form-urlencoded")
public void post(#FormParam("name") String name) {
// Store the message
}
as described here

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?

Why is my Spring service returning any content type requested by client?

I have a Spring rest service using Spring 3.1.0.RELEASE. Here is the relevant code for the service call in question:
#RequestMapping(value="/{var1}", method=RequestMethod.GET, produces="application/json")
#ResponseBody
public String getSomeStuff(#PathVariable final String var1) {
return myJsonString;
}
If I call this using the following curl command, it happily returns me my json string with a content-type of application/xml whereas I would expect a 406 based on the Spring 3.1 docs:
curl -v -H "Accept: application/xml" http://localhost:8080/MyServiceSite/myvalue
There is no extra configuration in my app for this service (no serialization), I am returning raw json with no post-processing for the service configured. I'm certain I have missed something, can anyone point out anything I may have missed?
Edit: Here is the documentation I was looking at when attempting to get this working. Specifically section 16.3.2.5. My code is very similar except that their code looks like it assumes config setup to let Spring handle serialization. Perhaps the produces does not work when bypassing the Spring serialization?
Edit: I changed my expectation for the response code. A 415 would indicate I was sending improper content in my request body whereas 406 is proper for having an accept header that doesn't jive with the content type of the server.
Anyway, I have changed this method do return a Map and added config for it to serialize to json and now if I send an invalid content type from the client I get the proper 406 response. It seems that maybe the "produces" setting is ignored when the output of the method is not being serialized.
The produces condition is new to Spring MVC 3.1 and is only supported with the RequestMappingHandlerMapping and related #MVC support classes, also new in Spring 3.1. My guess is that you're using the 3.0 #MVC support classes, which do not support the produces condition. Your code otherwise is correct and so are your expectations of what should happen.
The use of headers="Accept=application/json" is unnecessary in 3.1. That's exactly what the produces condition was introduced for.
What about the headers attribute for the #RequestMapping. You could set the Accept header in there. Something like:
#RequestMapping(value="/{var1}", method=RequestMethod.GET, produces="application/json", headers = "Accept=application/json")
#ResponseBody
public String getSomeStuff(#PathVariable final String var1) {
return myJsonString;
}
I don't know how Spring would handle a request to that path without a matching header. If it doesn't give what you want you might need to define a similar mapping without the headers and have it send back a ResponseEntity and set the response code or something, but I would hope it would handle it appropriately.

Categories