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();
}
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 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 will like to use the JSON response inside a controller. I am calling a method that returns the JSON. See my code below. Please how do I loop through the Json object returned inside my controller. I need to use the properties like sending mail to the email addresses from another method inside my controller .
My method that does that returns the JSON :
#ResponseBody
private ResponseEntity<?> queryIsw(String ref, String amt) throws Exception{
String pdtid = "62";
String salt = "D3D1D05AFE42AD50818167EAC73C109168A0F108";
RestTemplate restt = new RestTemplate();
String uri = "https://bestng.com/gettransaction.json";
MultiValueMap<String, String> params = new LinkedMultiValueMap<String, String>();
params.add("productid", pdtid);
params.add("transactionreference", ref);
params.add("amount", amt);
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(uri).queryParams(params).build();
URI oro = uriComponents.toUri();
HttpHeaders hea = new HttpHeaders();
String hs = pasher(pdtid, ref, salt);
hea.add("hash", hs);
HttpEntity<String> hent = new HttpEntity<String>(hea);
ResponseEntity<Object> resp = restt.exchange(oro, HttpMethod.GET, hent, Object.class);
return resp;
}
Below is my call to this method above from another method :
ResponseEntity<?> dres = queryIsw(dref,ama);
Kindly explain how I can use properties of 'dres' returned in my controller .
Thanks
Try taking a look at the Jackson JSON to Java mapping tools and specifically the ObjectMapper. It can convert a properly formatted JSON string into an object hierarchy from which you can pull out the data that you need. Jackson is a frequently used tool for this activity. Take a look at the tutorial for more details:
http://www.mkyong.com/java/how-to-convert-java-object-to-from-json-jackson/
If you need more help, do ask.
I am assuming that it will return within the body of the ResponseEntity.
Try:
String body = dres.getBody();
You can use something that can parse that string to a json object. Something like:
JSONObject jObject = new JSONObject(body);
See:
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html
Java String to JSON conversion
In My controller i have a method below which is working well
#RequestMapping(value="/searchresults",method = RequestMethod.GET)
public SearchResponse searchResults(
#PathVariable("domain") String domain,
#RequestParam(value="rowCount" , defaultValue="0", required=false) Integer rowCount,
HttpServletRequest req){}
but the same thing is not working when adding headers,
#RequestMapping(value="/searchresults", method = RequestMethod.GET,headers = "application/json;charset=UTF-8")
public SearchResponse searchResults(
#PathVariable("domain") String domain,
#RequestParam(value="rowCount" , defaultValue="0", required=false) Integer rowCount,
HttpServletRequest req){}
Exception :
Representation: null org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException: No matching handler method found for servle
t request: path '/search/searchresults.json', method 'GET',
I tried as follows,
#RequestMapping(value="/searchresults", method = RequestMethod.GET,headers = {"content-type=application/json,charset=UTF-8"})
but it throws,
java.lang.IllegalArgumentException: "charset=UTF-8" does not contain '/'
How to resolve it
You forgot to add the headers names :|
application/json is the Content-Type, while UTF-8 is the Charset.
Take a look at the complete list of HTTP headers.
The correct mapping will then be :
#RequestMapping(value="/searchresults", method = RequestMethod.GET,
headers = {"content-type=application/json,charset=UTF-8"})
That said, it's worth knowing that the ContentType should be specified only for POST and PUT requests.
You need to specify the header name also, which is content-type. Change this:
headers ="application/json;charset=UTF-8"
to
headers = {"content-type=application/json,charset=UTF-8"}
Change headers to produces
#RequestMapping(value="/searchresults", method = RequestMethod.GET,produces = "application/json;charset=UTF-8")
public SearchResponse searchResults(
#PathVariable("domain") String domain,
#RequestParam(value="rowCount" , defaultValue="0", required=false) Integer rowCount,
HttpServletRequest req){}
Ideally you should use #RestController if you are using Spring 4
Use ; instead of ,
#RequestMapping(value="/searchresults", method = RequestMethod.GET,headers = {"content-type=application/json;charset=UTF-8"})
There is a workaround for those who use WebLogic, while maybe other app servers an do the similar, here is what worked for me in weblogic.xml
<wls:charset-params>
<wls:input-charset>
<wls:resource-path>/restful</wls:resource-path>
<wls:java-charset-name>UTF-8</wls:java-charset-name>
</wls:input-charset>
</wls:charset-params>
My Request Mapping annotation looks like this:
#RequestMapping(method = RequestMethod.POST, value = "/echo", produces = "text/plain;charset=UTF-8", headers = "Accept=*/*")
adding
headers = {"content-type=application/json,charset=UTF-8"}
didn't help and I am puzzled why, but I got away somehow. HTH
I found this useful (being stuck with Spring 3.0.x):
#ResponseBody
public ResponseEntity<String> getResponse() {
String body = ...
MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>();
headers.set("Content-Type", "application/json;charset=UTF-8");
return new ResponseEntity<String>(body, headers, HttpStatus.OK);
}
I'm using Spring Restful web service & having request body with request header as shown below:
#RequestMapping(value = "/mykey", method = RequestMethod.POST, consumes="applicaton/json")
public ResponseEntity<String> getData(#RequestBody String body, #RequestHeader("Auth") String authorization) {
try {
....
} catch (Exception e) {
....
}
}
I want to pass one more optional request header called "X-MyHeader". How do I specify this optional request header in Spring rest service?
Also, how do I pass this same value in response header??
Thanks!
UPDATE: I just found that I can set required=false in request header, so one issue is resolved. Now, the only issue remaining is how do I set the header in the response??
Use required=false in your #RequestHeader:
#PostMapping("/mykey")
public ResponseEntity<String> getData(
#RequestBody String body,
#RequestHeader(value = "Auth", required = false) String authorization) {}
This question is answered here:
In Spring MVC, how can I set the mime type header when using #ResponseBody
Here is a code sample from: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-httpentity
#RequestMapping("/something")
public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader");
byte[] requestBody = requestEntity.getBody();
// do something with request header and body
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("MyResponseHeader", "MyValue");
return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
}