I have this simple controller that receives a map as input:
#RequestMapping(value = "/providers", method = RequestMethod.PATCH, headers = "Accept=application/json")
#ResponseBody
#Transactional
public Map<String, Object> updateProvider(#RequestBody Map<String, Object> updates,
UriComponentsBuilder uriComponentsBuilder, final HttpServletRequest request) {
return updates;
}
and I have this property configured in Spring Boot in the application.properties file:
spring.jackson.default-property-inclusion=non_empty
Then, if I make a PATCH request with following JSON object.
{
"name":"frndo",
"lastname":""
}
The content of result in the RESPONSE is:
{
"name":"frndo"
}
But the content of input in the REQUEST is:
{
"name":"frndo",
"lastname":""
}
My question is, Why the content in the REQUEST is different in the RESPONSE if to serialize the Map object you have a global configuration like:
spring.jackson.default-property-inclusion=non_empty
Precisely why when #RequestBody Map<String, Object> updates arrives have the name and lastname fields if lastname is empty?.
In the RESPONSE you can see the effect of the configuration but in the REQUEST is not seen. What is the explanation of this, if the mapper had to convert the JSON to a java object, and in that process the global configuration had to be applied?
I expected to have the same content in the REQUEST and the RESPONSE
Many thanks!
Related
This is related to an existing spring boot question raised by me(Request Body is not properly encoded and hidden when using spring form encoder in Feign Client).
According to this question, we can add either content type in headers or add during request mapping itself as consumes.
So what I did was added content type in headers in the client configuration class
public class EmailClientConfiguration {
#Bean
public RequestInterceptor requestInterceptor(Account<Account> account) {
return template -> {
template.header("Content-Type", "application/x-www-form-urlencoded");
};
}
#Bean
public OkHttpClient client() {
return new OkHttpClient();
}
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
#Bean
public Decoder feignDecoder() {
return new JacksonDecoder();
}
#Bean
public Encoder feignFormEncoder () {
return new SpringFormEncoder(new JacksonEncoder());
}
}
and I see in the headers the content type is correctly set as application/x-www-form-urlencoded when the request is sent. But the request body is still sent in json format and also not hidden.
Request Body:
Map<String, String> requestBody = new HashMap<>();
requestBody.put("username", "xyz");
requestBody.put("email", "xyz#gmail.com");
requestBody.put("key", "xxx");
Request Body received in server end:
{"{\n \"key\" : \"xxx\",\n \"email\" : \"xyz#gmail.com\",\n \"username\" : \"xyz\"\n}"
When I add consumes in my request mapping as application/x-www-form-urlencoded
#FeignClient(name = "email", url = "localhost:3000",
configuration = EmailClientConfiguration.class)
public interface EmailClient {
#PostMapping(value = "/email/send", consumes = "application/x-www-form-urlencoded")
ResponseDto sendEmail(#RequestBody Map<String, String> requestBody);
}
it works fine(request body is hidden in server end and also properly encoded). And when I removed the header in the configuration class and adding only consumes works fine without no issues but the vice versa has this problem.
I searched in internet for this and couldn't find any answer.
Feign encodes the request body and parameters before passing the request to any RequestInterceptor (and rightly so). If you do not declare consumes = "application/x-www-form-urlencoded", SprinFormEncoder doesn't know that you're trying to send form data, so it delegates serialization to the inner JacksonEncoder which only does JSON (see for yourself by printing template.body() before setting the header).
Handling such a well-supported header in the interceptor doesn't seem like a good idea, when you already have consumes. If you insist on doing so, you have to provide your own encoder which doesn't rely on the header value and always outputs form-urlencoded data.
I am new to web programming in general, especially in Java, so I just learned what a header and body is.
I'm writing RESTful services using Spring MVC. I am able to create simple services with the #RequestMapping in my controllers. I need help understanding how to get HTTP header information from a request that comes to my method in my REST service controller. I would like to parse out the header and get some attributes from it.
Could you explain how I go about getting that information?
When you annotate a parameter with #RequestHeader, the parameter retrieves the header information. So you can just do something like this:
#RequestHeader("Accept")
to get the Accept header.
So from the documentation:
#RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(#RequestHeader("Accept-Encoding") String encoding,
#RequestHeader("Keep-Alive") long keepAlive) {
}
The Accept-Encoding and Keep-Alive header values are provided in the encoding and keepAlive parameters respectively.
And no worries. We are all noobs with something.
You can use the #RequestHeader annotation with HttpHeaders method parameter to gain access to all request headers:
#RequestMapping(value = "/restURL")
public String serveRest(#RequestBody String body, #RequestHeader HttpHeaders headers) {
// Use headers to get the information about all the request headers
long contentLength = headers.getContentLength();
// ...
StreamSource source = new StreamSource(new StringReader(body));
YourObject obj = (YourObject) jaxb2Mashaller.unmarshal(source);
// ...
}
My solution in Header parameters with example is user="test" is:
#RequestMapping(value = "/restURL")
public String serveRest(#RequestBody String body, #RequestHeader HttpHeaders headers){
System.out.println(headers.get("user"));
}
You can use HttpEntity to read both Body and Headers.
#RequestMapping(value = "/restURL")
public String serveRest(HttpEntity<String> httpEntity){
MultiValueMap<String, String> headers =
httpEntity.getHeaders();
Iterator<Map.Entry<String, List<String>>> s =
headers.entrySet().iterator();
while(s.hasNext()) {
Map.Entry<String, List<String>> obj = s.next();
String key = obj.getKey();
List<String> value = obj.getValue();
}
String body = httpEntity.getBody();
}
I have a POJO of the form:
#Data
public class BaseRequest {
private String type;
private Map<String, Object> details;
private Map<String, Object> signature;
}
I have a service running which only accepts Content Type: "application/x-www-form-urlencoded".
I have written a client in Java which uses Spring's RestTemplate to make calls.
public String getInvoice(BaseRequest req, String url) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<BaseRequest> httpEntity = new HttpEntity<BaseRequest>(req, headers);
String response = this.restTemplate.postForObject(url, httpEntity, String.class);
return response;
}
However, it throws an error:
org.springframework.web.client.RestClientException: Could not write request: no suitable HttpMessageConverter found for request type [com.x.y.z.BaseRequest] and content type [application/x-www-form-urlencoded]
It works if I set the content type as JSON:
headers.setContentType(MediaType.APPLICATION_JSON);
I know it works for JSON because I have configured my RestTemplate Bean with JacksonHTTPMessageConverter. So I can easily convert POJOs to application/json. However, I am not able to figure out how to do that with application/x-www-form-urlencoded.
I've been searching this for awhile now, and the only solution which I've found is to write my own converter to convert my BaseRequest class to Spring's MultiValueMap, and then Spring's FormHttpMessageConverter will automatically handle it. But I want to avoid doing that. Is there any other way around this?
Any leads would be appreciated. Thanks!
EDIT:
My question is different from #JsonProperty not working for Content-Type : application/x-www-form-urlencoded. The conversion happening there is about accepting data in application/x-www-form-urlencoded and converting it to a POJO. My question is about converting a POJO to application/x-www-form-urlencoded while using Spring's resttemplate to make calls. And like I mentioned, I know I can achieve this by writing my own converter to convert my POJO to Spring's MultiValueMap. However, I want to know if I can avoid doing this.
EDIT:
Dump of $_POST on the API when I send my data as MultiValueMap<String, Object>:
"array(0) {
}"
Dump of $_POST on the API when I send my data through Postman in the correct format:
"array(2) {
["type"]=>
string(16) "abcd"
["details"]=>
array(1) {
["template_file"]=>
string(16) "x.html"
}
}"
Try to convert your nested object in request payload to the org.springframework.util.MultiValueMap. Add and implement converter method in your POJO
public class BaseRequest {
// ...
public MultiValueMap<String, Object> toMap() {
MultiValueMap<String, Object> result = new LinkedMultiValueMap<>();
result.add("type", type);
result.put("details", details);
result.put("signature", signature);
return result;
}
}
Now use it during request creation
HttpEntity<BaseRequest> httpEntity = new HttpEntity<BaseRequest>(req.toMap(), headers);
That is caused because inside FormHttpMessageConverter which performs actual conversion method canRead(Class<?>, MediaType) checks if MultiValueMap.class.isAssignableFrom(clazz) where clazz is your payload object. In your case it failed, so FormHttpMessageConverter skipped.
Hope it helps!
I have a request handler for which I would like to skip json processing and retrieve the request body as a string. Eg -
#RequestMapping(value = "/webhook", method = RequestMethod.POST)
public void webHook(#RequestBody String body) {
}
However, the above method definition doesnt work as Spring forcibly tries to parse the posted string as json and thus throws an exception.
How do i tell spring to skip json processing for this request?
use like this it'll work.
#RequestMapping(value = "/webhook", method = RequestMethod.POST)
public void webHook(HttpServletRequest request) {
String body = IOUtils.toString( request.getInputStream());
// do stuff
}
Not using #RequestBody is key here. When spring sees #RequestBody it tries to map the entire body as object.
I have a SpringMVC web service for uploading files which looks like this:
#RequestMapping(value="/upload.json", method = RequestMethod.POST)
public #ResponseBody Map<String, Object> upload(MultipartHttpServletRequest request) {
// upload the file
}
and everything is dandy. But if one of the consumers posts a non-multipart form, then i get this exception
java.lang.IllegalStateException: Current request is not of type [org.springframework.web.multipart.MultipartHttpServletRequest]
Which makes sense.. however I dont want my end users to see 500 servlet exceptions. I want a friendly error message.
I just tried this (to be like a catchall for other POSTs):
#RequestMapping(value="/upload.json", method = RequestMethod.POST)
public #ResponseBody Map<String, Object> upload2(){
// return friendly msg
}
but I get this error:
java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path '/upload.json'
Is there any way to safely handle both multipart and non-multipart POST requests? in one method, or 2 different methods i dont care.
Check if the request is a multipart yourself:
#RequestMapping(value="/upload.json", method = RequestMethod.POST)
public #ResponseBody Map<String, Object> upload(HttpServletRequest request) {
if (request instanceof MultipartHttpServletRequest) {
// process the uploaded file
}
else {
// other logic
}
}