Spring MVC repsonsebody - java

Follwing the examples from Krams. What does the below return, will the Person object be converted to JSON or XML based on the header of submitted request ?
#RequestMapping(value = "/person",
method = RequestMethod.POST,
headers="Accept=application/xml, application/json")
public #ResponseBody Person addPerson(#RequestBody Person person) {
logger.debug("Provider has received request to add new person");
// Call service to here
return personService.add(person);
}
So when I submit data as json I get json back, and the same for xml. Or is something else going on ?

It depends on Accept http request header. If it's json, you get json, if it's xml you get xml.
That's exactly what this part of your code says.:
....
headers="Accept=application/xml, application/json")
...
You can send one mime type and receive another without problems.
edit
Both headers and produces/consumes parameters only say what can be produced/consumed by the requestmapping. They don't force any particular serialization. The type of request/response is decided entirely in request headers. If the dispatcher doesn't find mapping with produces and consumes (or headers) matching the request headers you'll get an error.

The 'headers', 'produces', and 'consumes' parameters to #RequestMapping are one piece to the puzzle. They help the framework route incoming requests, and differentiate between requests based on values in the header.
The other part to this puzzle is the set of MessageConverters that are configured in the application. The controller handler method in the example just returns an object, and the framework needs to know how to convert the object to some text representation (i.e. XML or JSON). MessageConverters are used on the inbound side (on parameters annotated with #RequestBody) and on the outbound side (when the method return value is annotated with #ResponseBody). The appropriate MessageConverter is chosen by the framework based on the media type of the incoming request.
When using the <mvc:annotation-driven> namespace in a Spring XML config file, or when using the #EnableMvc annotation on a Java Config class, MessageConverters supporting JSON and XML and configured automatically.

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.

MVC - Accept JSON when content-type is custom (not application/json)

I am trying to implement the Content-Security-Policy-Report-Only Header for my website.
In order to do that, i need a controller that will accept the browsers POST-Request - which will send data about the violation in form of JSON. This request, however, seems to specify the Content-Type as application/csp-report instead of application/json (side note: Why the hell??).
This apparently causes Spring to refuse the request - it seems like the usage of #RequestBody makes spring only accept requests that are of Content-Type "application/json".
Even when i specifically set the value consumes of the #RequestMapping annotation to application/csp-report it still does not accept the request.
I have gone as far as using a filter to wrap the request with HttpServletRequestWrapper - which seems to be the common way of modifying the behavior of a request.
I have overwritten all of these methods: getContentType(), getHeader(String), getHeaders(String) to return "application/json".
But the request still does not go through.
What am i missing here?
Are there any better solutions to this that
do not require me to do magic with the request?
I wouldn't even
mind parsing the JSON manually, can i somehow accept it as plain
String instead?
According documentation on #RequestBody annotation,
The body of the request is passed through an HttpMessageConverter to resolve the method argument depending on the content type of the request
What that means is Spring Framework defines a specialized API for defining how certain MediaTypes are converted into certain Java types when used as parameters to REST endpoints.
Here is a pretty good article showcasing these capabilities.
There are a great deal of builtin Spring converters that you may be able to just configure and use if your media format can be mapped to their respective media formats. Specifically for your case, you should look at one of the converters available in spring.converter.json package. In simplest case, making it work should be as simple as:
HttpMessageConverter converter = new <JsonConverterOfYourChoice>(JsonMapper);
converter.getSupportedMediaTypes().add(new MediaType("application", "csp-report"));
And then registering such converter into a spring's configuration as you do.
Other available converter types include:
StringHttpMessageConverter
ObjectToStringHttpMessageConverter (if you have already configured Spring's ConversionService to read your desired types from String)
ByteArrayHttpMessageConverter
FormHttpMessageConverter
Various XML-based converters living in spring.converter.xml package
AllEncompassingFormHttpMessageConverter (converter built on top of Form converter and capable of handling XML and JSON as well)
ProtobufHttpMessageConverter
Atom/RSS feed message converters.
Finally, if none of the above does not apply for you, you can make and register your own HttpMessageConverter implementation.
Building M. Prokhorov's answer into a complete snippet, this code worked for me to add application/csp-report as a JSON message converter (using Jackson) in Spring Boot:
#Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
super.extendMessageConverters(converters);
final List<MediaType> cspReportMediaTypes =
Collections.singletonList(new MediaType("application", "csp-report"));
final HttpMessageConverter<Object> cspReportConverter =
new MappingJackson2HttpMessageConverter() {
#Override
public List<MediaType> getSupportedMediaTypes() {
return cspReportMediaTypes;
}
};
converters.add(cspReportConverter);
}
};
}
The other answers helper me figuring out what was going on with #ResponseBody.
CSP reporting is still relatively new and not quite standardized among browsers:
The CSP2 spec states that CSP report requests need to use a
content-type header of application/csp-report. In my sample, I
observed four different content-type headers:
application/x-www-form-urlencoded
application/json
application/csp-report
application/json; charset=UTF-8
What to Expect When Expecting Content Security Policy Reports
So an alternative, to accept any kind of content type, is to read the input stream directly from the HttpServletRequest instead of using #ReponseBody with a message converter.
For example, in Kotlin:
#PostMapping(path = [CSP_REPORT_URL])
fun reportFrontendError(
request: HttpServletRequest
): ResponseEntity<Void> {
val body = request.inputStream.bufferedReader().use { it.readText() }
// ...
return ResponseEntity(HttpStatus.OK)
}

How to sign JSON message used by Spring Rest controller?

I have a Spring REST application that accepts JSON messages, written like
#RequestMapping(value = "/myhook", method = RequestMethod.POST,
produces = JSON, consumes = JSON)
public #ResponseBody MyResponse doIt
(#Valid #RequestBody(required = true) MyContractRequest request) {
MyResponse response;
...
return response;
}
This works really well with almost no code to support, but now I have a requirement to sign both response and request.
I started from simply computing the shared signature of all message fields at Java level and assigning it to the dedicated signature field. However this requires to have and maintain code for computing the signatures:
public void update(java.security.Signature sign) throws Exception {
sign.update(name);
sign.update(value);
sign.update(etc);
}
Some people around me expressed opinion that the need to write and maintain this signing code may not be the best design, and it may be better to sign the whole message as a single JSON string. I could fetch the request as a string manually, and then process JSON manually, but I really would like to preserve the Spring controller concepts.
Also, I cannot longer have the signature field in the message itself because the value of this field obviously also changes the signature of the JSON string.
Is there any way to compute the signature of the whole JSON message body on the message departure and arrival, and where to place the signature so it could be passed together with the message? One of the idea is to use the custom HTTP header for the signature. Anyway, how to compute it first?
You can use a servlet filter with Spring MVC and modified your content whatever you want in request and response as well
Example :
http://www.mkyong.com/spring-mvc/how-to-register-a-servlet-filter-in-spring-mvc/
or you can use Spring 3 MVC Interceptor
http://viralpatel.net/blogs/spring-mvc-interceptor-example/

Any simple way to test a #RequestBody method?

If I have a #Controller method whose parameter is a #RequestBody param, I usually have to write some jQuery script or something similar to perform an AJAX request with JSON object in order to call that method. If I tried calling that method via a web browser directly, it returns with a Error 415 Unsupported Media Type.
Is there any alternative to just quickly call such method using browser without having to write some jQuery code? Like perhaps a way to write the JSON object in the URL/address bar?
code:
#RequestMapping("testCall")
#ResponseBody
public List<TestObject> getTestCall (#RequestBody TestParams testParams) {
return stuff;
}
public class TestParams {
private Integer testNumber;
//getter/setter for testNumber
}
I thought maybe I could just do:
http://localhost/testCall?testNumber=1
maybe Spring would auto populate a new TestParams instance with that property set to 1 but that didnt work...
maybe I need to do something extra for that?
The whole point of a #RequestBody annotated parameters is for the Spring MVC stack to use the HTTP request body to produce an argument that will be bound to the parameter. As such, you need to provide a request body. Sending a request body is very atypical for a GET request. As such, browsers don't typically support it, at least not when simply entering an address in the address bar and submitting the request.
You'll need to use a different HTTP client, like jQuery. I typically have a small Java project in Eclipse that's setup with an Apache HTTP components client which can send HTTP requests to whatever server. It takes a few seconds/minutes to setup the correct request body and run.
I have spent the last year building a REST API, and by far the best way to exercise that API manually is using the Chrome Extension, Postman. I cannot recommend this tool enough.
https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm?hl=en
To test your simple example you'll need to invoke a POST (I assume that as you have a request body, but your controller method doesn't define a HTTP Verb) using POSTMAN to your Url (like the following example):
POST /contextRoot/testCall
{
"testNumber": 1
}
If you want to test your API automatically (which I recommend), you can use the excellent Spring Mvc Test project. This allows your to call your API via a rest-like DSL and assert that the response is in the shape you want. More details can be found here:
http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/testing.html#spring-mvc-test-framework
you can add request params to the getTestCall method:
#RequestParam(value = "testNumber", required = false, defaultValue = "") String testNumber
There is a chrome app called Advanced REST client. You can pass the data in form of json to your controller using this chrome app. For eg. json data is
id:1,
name:"xyz"
whereas the controller can have #RequestBody Person form.
The Person class would be a POJO having id and name as instance variables. The Spring would automatically map the json data to the form.
I think this is the easiest and simplest way of checking your spring controller.
Check the extension Advanced REST client here
From what I know You can send JSON object to the webbrowser and it will be displayed without further need of AJAX.
useful tutorial:
http://www.mkyong.com/spring-mvc/spring-3-mvc-and-json-example/

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