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/
Related
Background
We are consuming the API from a 3rd-party vendor
Problem Statement:
I am building a wrapper API around another API. The same exact JSON payload that I will be receiving from the client to the wrapper API will also be used to make an HTTP request to the original API.
Currently I'm converting JSON which deserializes to a String. Is this the right approach if the payload is just passing through the wrapper API to the original API? In other words, is the #RequestBody type String okay for my use case or do I still need to deserialize to a Java Object?
Use Case for wrapper
If multiple teams consumed the API from the 3rd-party vendor, all teams would have to make changes if we were to switch vendors. If we create a wrapper, only one team would have to make the changes. There is no processing in this wrapper.
Controller Code:
#RestController
#RequestMapping(value = FolderController.PATH, produces = MediaType.APPLICATION_JSON_VALUE)
public class PersonController(){
static final String PATH = "/person";
private final PersonService personService;
#Autowired
public PersonController(PersonService personService){
this.personService = personService
}
#PostMapping
#ResponseBody
public String createPerson(#RequestBody String requestBody){
return personService.createPerson(requestBody);
}
Whether you need to deserialize depends on what processing is necessary for your wrapper. If you just want to pass the request further without changing it, this should work.
However, if you just need to proxy the request, consider using Smiley's HTTP Proxy Servlet for this task.
Or if you are wrapping the API to implement security around it, then consider using Spring cloud gateway
It is always better to follow coding practices and convert the request body to a java POJO class instead of a string. Converting to POJO has several advantages :
You could provide additional validations for the request body.
Incase the request body changes or if there are any issues it could be identified easily.
Provides better control over the request structure.In future , if the API that you are consuming changes the request body structure, you could change it from your API instead of adapting it at every API that consumes your API.
In a controller class,
I have one method
#RequestMapping(value="test", method={RequestMethod.POST,RequestMethod.PUT})
#ResponseBody
public String testApp(#RequestParam String priceA, #RequestBody String valueOfProduct) throws Exception {
}
My client is sending prices to my App as POST requests for processing.
Now client (which is not under my control) is planning to send the price value as a request parameter and another client is planning to send price in requestheader.
The trick is:
If it is present in requestheader it wont be present in requestparameter.
So I have to design it such that my server code works fine in both cases.
Kindly let me know which design would be the best.
Will it be
#RequestMapping(value="test", method={RequestMethod.POST,RequestMethod.PUT})
#ResponseBody
public String testApp(#RequestParam String priceA, #RequestHeader("PRICE") String priceAFromAnother, #RequestBody String valueOfProduct) throws Exception {
}
But above logic wont work as #RequestParam wont be available all the time.
Have you tried searching by your own first?
Take a look that this answer.
If you know the header to search for, you could add a request filter (HttpServletRequestWrapper) that pre-process the request. Here is simple example Modify request parameter with servlet filter
NOTE: I would caution you that this methodology is not maintainable if your parameter set/api is going to grow over time.
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/
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.
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.