I have to create a rest web service where in user access data using url. Url has a query parameter named format which can be either text or file. If user chooses text as format then I have to return text data to browser or if user chooses file as format then return a file for user to download. How can I achieve this?
What I have tried so far (not working) :
#GET
#Produces({MediaType.TEXT_PLAIN, MediaType.APPLICATION_OCTET_STREAM})
#Path("/some_path")
public Response some_path (#Context HttpServletRequest request) {
String format = null;
if(request.getParameterValues("format") != null && request.getParameterValues("format")[0] != null) {
format = request.getParameterValues("format")[0].toString();
}
else {
format = "text";
}
File file = new File("/some/file/path.txt");
if(format.equals("text")) {
return Response.status(200).entity("sending some text").build();
} else {
return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM).header("content-disposition","attachment; filename = result.txt").build();
}
}
With above code format=text works properly but format=file throws HTTP Status 406 null error.
Thanks In Advance
The HTTP status you get is
406 Not Acceptable
To quote Wikipedia:
The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request.
So you client doesn't send a HTTP header of
Accept: text/plain
or
Accept: application/octet-stream
or any combination. Check your client and the header it sends.
You are mixing to approaches. The first, quite popular but not standardized, by providing the hint of the expected result in request's path by using the format parameter (BTW you may use the #QueryParam("format") String format rather than extracting it from ServletRequest). The second approach is the HTTP content negotiation mechanism, in this case using Accept/Content-Type headers. This mechanism is handled by the JAX-RS implementation based on #Produces annotation and providers' classes.
So now, your user is not only required to set the format but also Accept header. It seems that you are using the client which sets the Accept and one of its value is text/plain. That is why it the first case is working, but there is no application/octet-stream or */* (all), so you the JAX-RS expects that the client is not able to process such a content and instead sends him an error 406 Not Acceptable.
The solution here is to remove the #Produces annotation (you are the one who is taking care of the format of the response), or drop the format parameter and let the JAX-RS do his work (probably you will need to register your own provider). If you however stay with your solution then make sure that the correct or no Accept header is send in request (no header means: "I would accept whatever you send").
Related
When I usually make controller endpoints with Java Spring that return JSON data, my return type is usually String; I'll toString() my JSON objects and parse them in my Javascript. Recently, I read somewhere that that wasn't the "correct" way to do it (trying to follow best practices and guidelines). I am starting a new project where my return type is a JSON object (dependency is org.json).
When I try to hit the endpoint in the browser, I'm getting a 406 error with a description of "The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers.".
Here is what my endpoint looks like now:
#RequestMapping(value = "/api/get-items", method = {RequestMethod.GET, RequestMethod.POST}, headers = "Accept=application/json")
#ResponseBody
public JSONObject doGetItems(HttpServletRequest request, HttpServletResponse response) {
JSONObject data = new JSONObject();
JSONArray items = new JSONArray();
JSONObject item1 = new JSONObject().put("id", 123);
JSONObject item2 = new JSONObject().put("id", 456);
JSONObject item3 = new JSONObject().put("id", 789);
items.put(item1).put(item2).put(item3);
data.put("data", items);
return data;
}
How can I get this to work and return JSON? Or am I supposed to be returning a stringified version of my JSON object and I was doing this correctly all along?
Update
First, I tried taking out the headers parameter in the #RequestMapping annotation. I still got a 406 error.
I then tried changing/adding the parameters in the #RequestMapping annotation, with no luck as well. This is what I tried changing it to:
#RequestMapping(value = "/api/get-items",
method = {RequestMethod.GET, RequestMethod.POST},
headers = "Accept=*/*",
produces = MediaType.APPLICATION_JSON_VALUE)
I'm still getting a 406 error.
The HyperText Transfer Protocol (HTTP) 406 Not Acceptable client error response code indicates that the server cannot produce a response matching the list of acceptable values defined in the request's proactive content negotiation headers, and that the server is unwilling to supply a default representation.
Proactive content negotiation headers include:
A client (e.g. your Web browser or our CheckUpDown robot) can indicate to the Web server (running the Web site) the characteristics of the data it will accept back from the Web server. This is done using 'accept headers' of the following types:
Accept: The MIME types accepted by the client. For example, a browser may only accept back types of data (HTML files, GIF files etc.) it knows how to process.
Accept-Charset: The character sets accepted by the client.
Accept-Encoding: The data encoding accepted by the client e.g. the file formats it understands.
Accept-Language: The natural languages (English, German etc.) accepted by the client.
Accept-Ranges: Whether the client accepts ranges of bytes from the resource i.e. a portion of the resource.
By changing or removing header content at client side it should work.
When you specify headers = "Accept=application/json", then you're saying that an Accept header must be present and have exactly that value.
The client didn't send exactly that value, and there is not reason it should be required to do so, because the Accept header value is a comma-separated list of acceptable values, so it just needs to list application/json, it doesn't have to only specify that value.
To fix, replace headers = "Accept=application/json" with produces = "application/json".
Of course, the client still needs to specify that application/json, application/*, or */* is acceptable.
Below is the example I tried:
Service method declaration:
#POST
//#Produces(MediaType.APPLICATION_JSON)
//#Consumes({"application/xml", MediaType.TEXT_PLAIN})
#Path("/agentLogout")
public String agentLogout(String ext) {
JSONObject obj = new JSONObject();
obj.put("status", "LoggedOut");
return obj.toString();
}
Client side code:
WebClient client = WebClient.create(REST_URI);
client.path("agentLogout").accept(MediaType.APPLICATION_JSON);
Response agentLogoutResponse = client.post("3101");
String responseStr = agentLogoutResponse.readEntity(String.class);
try {
JSONObject json = (JSONObject)new JSONParser().parse(responseStr);
System.out.println("3101 DN--->"+json.get("status"));
} catch (ParseException e) {
e.printStackTrace();
}
Above example worked fine and produced the output as below:
3101 DN--->LoggedOut
My questions:
I haven't configured any type in produces and consumes in service method (as you can see I have commented it), however it executed well and produced the output - May I know how it is posiible?
Do we have any default type for produces and consumes?
Default is */*
At Server Side
At server side if you have not specified Content-Type it can accept any content-type provided by client. However if you have specific format JSON, XML etc, you need to specify the Content-Type so that CXF can invoke corresponding providers. In some cases where you have same REST path with different content-Type then it can select corresponding method based on Content-Type. In GET method if Content-Type is mismatched it will be ignored, but not in POST, it will throw 415 error.
At Client Side
Same applies to the client side as well, however generally we specify the accept type in case server can send multiple content-type, in this case we specify what content-type we can accept. so that server can send the data in specified content-type, generally when we expose REST we expose with JSON and xml type, during this case we need to send accept type to server, so that server can send either JSON or XML.
I have a REST API published with Jersey and documented with Swagger, I also have a Swagger UI installation consuming that API.
Almost all my operations produce application/json and work as expected, except for one GET operation that produces: 'text/plain;charset=utf-8'
When I try to call the service from the Swagger UI, the server logs a javax.ws.rs.NotAcceptableException and returns a 406 response. If I call the same service from a REST client it works as expected.
#GET
#Path("/text")
#Produces(MediaType.TEXT_PLAIN + ";charset=utf-8")
#ApiOperation(value= "Return text")
public Response getText(#QueryParam("user") String user) {
return Response.ok(textService.getTextForUser(user)).build();
}
If I change to #Produces(MediaType.APPLICATION_JSON + ";charset=utf-8") then it works fine, but I don't want to set a wrong content type.
The problem seems to be that Swagger UI is wrongly setting the Accept headers to application/json as can be seen by observing the request:
GET /supertext/text?user=1
...
Accept: application/json
When using the rest client the Accept header are:
GET /supertext/text?user=1
...
Accept: */*
Why is Swagger UI not setting the Accept headers properly?
Can this be configured?
It seems that swagger ui sets the accept header to application/json when it finds that the #Produces annotation contains a single value, otherwise it renders a drop-down list in the ui to choose from the available content types.
In swagger-ui.js:
opts.responseContentType = $("div select[name=responseContentType]", $(this.el)).val();
When the drop-down list doesn't exist, the property becomes undefined.
Later in the code, the response content type is set to application/json if the property is null or undefined:
In swagger.js:
if (this.type === "POST" || this.type === "GET" || this.type === "PATCH") {
if (this.opts.responseContentType) {
responseContentType = this.opts.responseContentType;
} else {
responseContentType = "application/json";
}
}
So my solution was to modify the code in swagger-ui.js to make sure that the correct content-type was set, by exploring the produces array and choosing the first element as the response content type:
In swagger-ui.js replace the line:
opts.responseContentType = $("div select[name=responseContentType]", $(this.el)).val();
With:
if($("div select[name=responseContentType]", $(this.el)).val() === undefined) {
opts.responseContentType = opts.parent.model.produces[0];
}
else {
opts.responseContentType = $("div select[name=responseContentType]", $(this.el)).val();
}
I was also getting the same issue, but the solution was something different.
The problem lied in an unrelated controller, whose mapping was not defined correctly.
On starting the spring-boot application, I was able to see a log something like as follows :
([]) Mapping with class UnrelatedController
Whenever I loaded the swagger UI, a request swagger's API was being made, however response for this API was being displayed, which did not match what swagger was expecting.
Hence the error 406
The solution was to correct the mapping of the UnrelatedController, and everything worked as before. Thanks to Git History!
I'm learning how to build RESTful web services using Spring 4, and one thing I'm not clear on is in #RequestMapping. I've seen examples where one uses headers = "Accept=application/xml" and other examples using consumes (or produces) = "application/xml".
For instance, in my own #RestController class, I have this function...
// POST
#RequestMapping(method = RequestMethod.POST, headers = "Accept=application/xml")
public User create(#RequestBody User user) {
LOG.info("User = " + user.toString());
return userService.create(user);
}
What is the difference between using headers = "Accept=application/xml" vs. using consumes = "application/xml"? Or even using headers = "content-type=application/xml"?
Could someone explain the differences between headers and consumes/produces, and when each is used?
SHORT ANSWER
In the example you have above, using headers = "Accept=application/xml" or produces = "application/xml" will both respond to the client the same way i.e. send a response to the client with XML representation.
LONGER ANSWER
i. Headers
For RESTful web services, the client (e.g. your browser) sends a request (e.g. GET, POST, etc.) to a server, and the server will send a response back. This is an HTTP Transaction. Both the request and response have HTTP header fields ("headers"), which define the operating parameters of an HTTP transaction (I will refer to the headers for client request as "request headers", and these differ from headers from server response "response headers").
As part of the request your browser sends to server, there are different request headers and some examples include Accept, Connection, Content-Length etc. and each of these headers have their own function (see a full list of headers here: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields).
Using your code example, if a client does a POST request, Spring will check the request header(s) and if it finds a header Accept with a value of application/xml, it will map the request to the create method you have above (and in your case the server will return an XML response representation to the client).
Let me modify the headers element in the code you provided:
#RequestMapping(method = RequestMethod.POST, headers = "Connection=keep-alive")
public User create(#RequestBody User user) {
...
}
Notice the headers element now has a value of Connection=keep-alive. If a client does a POST request, Spring will check the request header(s) and if it finds a header Connection with a value of keep-alive, it will map that client request to the create method above.
ii. Produces and Consumes
If you used produces="application/xml" for the create method, this means a client request is only mapped to the create method if the client's Accept header matches application/xml. This essentially is the client saying, "Hey server, I prefer to accept your response in XML representation, so send your response to me in XML". Effectively, the produces="application/xml" is also the server saying, "Hey client, I can only produce responses for you in XML representation, so I will send you that format".
Link to Spring documentation reference.
If you used consumes="application/xml" for the create method, this means a client request is only mapped to the create method if the client's Content-Type header matches application/xml (the Content-Type request header describes the representation the client request is coming in). This essentially is the server saying, "Hey client, I can only consume requests in XML representation, so send that format to me".
SUMMARY
The headers element within the #RequestMapping annotation can take different request headers (Accept, Connection, Cache-Control etc.), but the produces element is only concerned with the Accept request header and the consumes element is only concerned with the Content-Type request header.
As the javadoc of HeadersRequestCondition (which handles the value provided in the headers attribute of a #RequestMapping annotation) states
Expressions passed to the constructor with header names 'Accept' or
'Content-Type' are ignored. See ConsumesRequestCondition and
ProducesRequestCondition for those.
So don't use those headers in headers. Use the produces and consumes attributes for Accept and Content-Type.
As to how to use them, the documentation gives examples: for consumes and for produces.
I am studying on the Spring MVC showcase example dowlodable from the STS dashboard.
In this time I am studying on the Converters section of this example and I have some question for you.
To start, in my view I have the following two links:
<li>
<a id="writeJsonAccept" class="writeJsonLink" href="<c:url value="/messageconverters/json" />">Write JSON via Accept=application/json</a>
</li>
<li>
<a id="writeJsonExt" class="writeJsonLink" href="<c:url value="messageconverters/json" />">Write JSON via ".json"</a>
</li>
The first link generate an HTTP Request towards the URL: messageconverters/json
The second link generate an HTTP Request towards the URL: /messageconverters/json.json (differently from the first URL this one end with .json extension
Ok, both these links have class="writeJsonLink" and related to the click event of these links the following JQuery callback function is called:
$("a.writeJsonLink").click(function() {
var link = $(this);
$.ajax({
url: this.href,
beforeSend: function(req) {
if (!this.url.match(/\.json$/)) {
req.setRequestHeader("Accept", "application/json");
}
},
success: function(json) {
MvcUtil.showSuccessResponse(JSON.stringify(json), link);
},
error: function(xhr) {
MvcUtil.showErrorResponse(xhr.responseText, link);
}});
return false;
});
This function only execute an AJAX call and wait for an HTTP Response passing its content to an handler that will show the output in the view...ok...
Before sending the request, the function check if the URL don't end with .json extension
If this request don't end with .json extension the following header is added to my HTTP Request:
Accept = application/json
From what I know the Accept Header say which specific mediatype is considerable acceptable for the HTTP Response, in this case say that the only acceptable media type is a JavaScript object having JSON format, ok...
This Request is handled from the following method of my controller class that return a valorized object that will be converted in JSON forma using Jaxb2RootElementHttpMessageConverter
#RequestMapping(value="/json", method=RequestMethod.GET)
public ResponseEntity<JavaBean> writeJson() {
// Oggetto che rappresenta gli HTTP Header dell'HTTP Response
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
return new ResponseEntity<JavaBean>(new JavaBean("bar", "apple"), headers , HttpStatus.OK);
// return new JavaBean("bar", "apple");
}
Now, my question is about the differences from the two links.
The first one don't end with .json extension, so the Accept header is added and it is setted on application/json sayng that the browsers expects to receive a JSON object
The second one end with .json extension, so the Jquery method don't set the Accept Header
But, this thing what mean? that when an URL end with .json the Accept header is automatically setted? Or more generally, when I have an URL that end with some kind extension (for example like .xml) the relative Accept header is automatically setted?
Or simply in this second case, don't set the Accept Header mean don't handle the media type that I can recive in the body of the HTTP Response?
Ok, your english is not-so-hot, so let me try to help you the best I can.
In my understanding, which may very well be wrong, is that you want to know if the browser will set the Accept: header to be json when the URL ends in json? I do not believe this is the case. I may be greatly mistaken on this, but you can test this by using something like Firebug or Chrome's Developer Tools, or if you like IE get Fiddler, and see exactly what headers get sent from the browser.
Now, if you are asking if Spring will magically put the headers there, then again I think the answer is "no". The HTTP headers on the request come from the browser, and although you could put in a Servlet Filter or something to set the request filters, I think this would be dangerous to assume all browsers handle these request headers the same way.
No, if the question is "how are my requests all getting to my Controller's writeJson() method?", then the answer has nothing to do with the "Accept" header at all. Your method is matching on any URI pattern that ends in /json, and in both cases your URL ends in /json. If you want to filter on things that have an "Accept" header of JSON, then I think you want to do something like this:
#RequestMapping(value="/someUriPattern", headers = {"Accept=application/json"})
Please understand I typed the above from memory, so you may need to tweak it a tad.