I can't figure out a way to bind several arguments and headers to one request parameter using annotations in Spring 3.
For example, let's say I'm getting this request:
Headers:
Content-type: text/plain;
POST Body:
Name: Max
Now I want it all to mysteriously bind to this object:
class NameInfo {
String name;
}
Using some code like this:
String getName() {
if ("text/plain".equals(headers.get("content-type"))) {
return body.get("name");
} else if ("xml".equals(headers.get("content-type")) {
return parseXml(body).get("name");
} else ...
}
So that in the end I would be able to use:
#RequestMapping(method = RequestMethod.POST)
void processName(#RequestAttribute NameInfo name) {
...
}
Is there a way to achieve something similar to what I need?
Thanks in advance.
#RequestBody is what you want, I think. See the Spring docs about it here.
The #RequestBody method parameter
annotation indicates that a method
parameter should be bound to the value
of the HTTP request body.
You convert the request body to the
method argument by using an
HttpMessageConverter.
HttpMessageConverter is responsible
for converting from the HTTP request
message to an object.
Related
Before everything I tried this two solution but didn't work for me
Equivalent of javax.ws.rs NameBinding in Micronaut?
https://blogs.ashrithgn.com/custom-annotation-to-handle-authorisation-in-micronaut-aop-tutorial/
In my application I have to get a string in the Authorization header and then decode it from base64 and the json transform it into a POJO. Certainly the string is a jwt and I need to decode the public part of the json to get a data from a field.
Technically speaking a client will forward the header to me to take it, decode it and extract the data. (It's very bad practice but that's what I have to do).
For this I am using micronaut 2.4.1 and this is my code:
Interceptor:
public class HeadInterceptor implements MethodInterceptor<Object, Object> {
#SneakyThrows
#Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
Request request = (Request) context.getParameterValueMap().get("request");
// Where do i get Authorization header?
// i.e String token = (String) context.getParameterValueMap().get("Authorization");
String token = "eyJhdWQiOiJ0ZXN0IiwiaXNzIjoidGVzdCIsInN1YiI6InRlc3QiLCJleHAiOjExMTExMTEsImlhdCI6MTExMTExMTEsImRhdGEiOiJ0ZXN0In0=";
ObjectMapper mapper = new ObjectMapper();
Info info = mapper.readValue(new String(Base64.getDecoder().decode(token)), Info.class);
request.setData(info.getSub().toUpperCase());
return context.proceed();
}
}
Controller:
#Controller("/main")
public class MainController {
#Post
#Head
public Single<Response> index(#Body #Valid Request request) {
return Single.just(
Response.builder()
.message(String.format("%s-%s", request.getData(), request.getInfo()))
.build()
);
}
}
Here's a sample app https://github.com/j1cs/micronaut-jacksonxml-error
(ignore the name is for other issue)
In your implementation, the header cannot be shown in the interceptor because your index method doesn't receive it as a parameter.
So, if you add it as a parameter as below:
...
#Post
#Head
public Single<Response> index(#Body #Valid Request request, #Header("Authorization") String authorizationHeader) {
return Single.just(
Response.builder()
.message(String.format("%s-%s", request.getData(), request.getInfo()))
.build()
);
}
...
Then, you can retrieve it in the intercept method via getParameterValues(). Basically, it will be the second argument.
...
#SneakyThrows
#Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
...
String token = (String) context.getParameterValues()[1];
...
}
...
Update
Since you want your Request to contain both body and header, I edited the solution a bit. Basically, the header is added as a member variable to Request as below:
public class Request {
#NotNull
#NotBlank
private String info;
private String data;
#Header("Authorization")
String authorizationHeader;
}
Then, use #RequestBean rather than a #Body annotation on your Request parameter:
...
#Post
#Head
public Single<Response> index(#RequestBean #Valid Request request) {
return Single.just(
Response.builder()
.message(String.format("%s-%s", request.getData(), request.getInfo()))
.build()
);
}
...
Finally, you can access the header easily in your intercept() method as follows:
#SneakyThrows
#Override
public Object intercept(MethodInvocationContext<Object, Object> context) {
...
Request request = (Request) context.getParameterValueMap().get("request");
String token = request.authorizationHeader;
...
}
I created a pull request for this change here, so you can check how it works.
In order to address the problem, you may first break the problem into parts.
Part 1: How to get arbitrary header (or list all headers)?
Try to use request.getHeaders() doc.
Part 2: How to get the header named Authorization ?
Use the way in part 1. In addition, be careful about the case. For example, is Authorization the same as authorization?
Method 2:
In controller (https://github.com/j1cs/micronaut-jacksonxml-error/blob/master/src/main/java/me/jics/MainController.java):
public Single<Response> index(#Body Request request, #Header('Authorization') String authorization) {
...
}
p.s. the "Header" annotation's doc is here: https://docs.micronaut.io/2.0.1/api/io/micronaut/http/annotation/Header.html
In interceptor:
...
String token = context.getParameterValueMap().get("authorization");
...
Why the code looks like this:
Firstly get the auth header you want using parameter injection.
Secondly, recall the fundamental concepts of AOP / AspectJ (which your interceptor class uses). Inside your interceptor, you intercept a method (in your case, the index method in controller. Thus, you can happily get the parameters of that method. In the code above, just the authorization parameter.
Please tell me if you are stuck on somewhere (and paste the code and the outputs).
I'm developing a REST API backend with Spring for a Slack App. I was able to receive messages from Slack (the slash commands) but I'm not able to properly receive component interactions (button clicks).
The official documentation says:
Your Action URL will receive a HTTP POST request, including a payload body parameter, itself containing an application/x-www-form-urlencoded JSON string.
therefore I have written the following #RestController:
#RequestMapping(method = RequestMethod.POST, value = "/actions", headers = {"content-type=application/x-www-form-urlencoded"})
public ResponseEntity action(#RequestParam("payload") ActionController.Action action) {
return ResponseEntity.status(HttpStatus.OK).build();
}
#JsonIgnoreProperties(ignoreUnknown = true)
class Action {
#JsonProperty("type")
private String type;
public Action() {}
public String getType() {
return type;
}
}
however I get the following error:
Failed to convert request element: org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'controllers.ActionController$Action'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'controllers.ActionController$Action': no matching editors or conversion strategy found
What does it mean, and how to resolve?
You receive a string that contains a JSON content. You don't receive a JSON input as application/x-www-form-urlencoded is used as content type and not application/json as stated :
Your Action URL will receive a HTTP POST request, including a payload
body parameter, itself containing an application/x-www-form-urlencoded
JSON string.
So change the parameter type to String and use Jackson or any JSON library to map the String to your Action class :
#RequestMapping(method = RequestMethod.POST, value = "/actions", headers = {"content-type=application/x-www-form-urlencoded"})
public ResponseEntity action(#RequestParam("payload") String actionJSON) {
Action action = objectMapper.readValue(actionJSON, Action.class);
return ResponseEntity.status(HttpStatus.OK).build();
}
As pvpkiran suggests, you could have replaced #RequestParam by #RequestBody if you could pass the JSON string directly in the body of the POST request, and not as a value of a parameter but it seems that is not the case there.
Indeed by using #RequestBody, the body of the request is passed through an HttpMessageConverter to resolve the method argument.
To answer to your comment, Spring MVC doesn't provide a very simple way to achieve your requirement : mapping the String JSON to your Action class.
But if you really need to automatize this conversion you have a lengthy alternative as stated in the Spring MVC documentation such as Formatters (emphasis is mine) :
Some annotated controller method arguments that represent String-based
request input — e.g. #RequestParam, #RequestHeader, #PathVariable,
#MatrixVariable, and #CookieValue, may require type conversion if the
argument is declared as something other than String.
For such cases type conversion is automatically applied based on the
configured converters. By default simple types such as int, long,
Date, etc. are supported. Type conversion can be customized through a
WebDataBinder, see DataBinder, or by registering Formatters with the
FormattingConversionService, see Spring Field Formatting.
By creating a formatter (FormatterRegistry subclass) for your Action class you could add that in the Spring web config as documented :
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
// ... add action formatter here
}
}
and use it in your parameter declaration :
public ResponseEntity action(#RequestParam("payload") #Action Action actionJ)
{...}
For simplicity, you could use the code block below. #Request body maps the the payload to the Action class. It also validates to make sure that the type is not blank. The #Valid and #NotBlank is from javax.validation package.
#PostMapping("actions")
public ResponseEntity<?> startApplication(#RequestBody #Valid Action payload) {
// use your payload here
return ResponseEntity.ok('done');
}
class Action {
#NotBlank
private String type;
public Action() {
}
public String getType() {
return type;
}
}
Can I create multiple HTTP POST methods with same request mapping with different requestbody
#RequestMapping("api/v1/")
#RequestMapping(value = "test" ,method = RequestMethod.POST)
public RObjet create(#RequestBody RBody rbody)
{
// do some process...
}
#RequestMapping("api/v1/")
#RequestMapping(value = "test" ,method = RequestMethod.POST)
public RObjet2 create2(#RequestBody RBody2 rbody)
{
// do something.
}
Is this possible.? How do we handle this in spring boot.
Yes, you can use POST Http Method for the same end point URI with different request body and also you could get different responses. One way to achieve this, is mapping requests using end point URI + Headers
e.g.
#RestController
#RequestMapping("/api/bills")
public class BillingController {
#RequestMapping(method = RequestMethod.POST, headers = "action=add-bill")
public BillId addBill(#Valid #RequestBody BillingData data) {
//Some code
}
#RequestMapping(method = RequestMethod.POST, headers = "action=delete-bill-by-id")
#ResponseStatus(code = HttpStatus.NO_CONTENT)
public void removeBill(#Valid #RequestBody BillId identifier) {
//Some code here to remove bill
}
}
In this case, both class methods in BillingController are mapped to the same HTTP Method (POST) and URI (/api/bills). The header action drives what class method in BillingController is going to be invoked once you point your post request to /api/bills
How to hit BillingController.addBill?
NOTE: I know that good REST API design dictates that if I want to delete records I should use DELETE method, however this sample was created only as reference to show how to use same URI/Method to handle 2 different end points.
You have to option for this.
it is possible with consumes field. You can use different consuming types.
You can user params field if you have in url.
#RequestMapping(value="/path", params="id")
public String test1(#RequestBody RBody body) {}
#RequestMapping(value="/path", params="name")
public String test2(#RequestBody RBody body) {}
I have a simple servlet as follows:
#RestController
public class TestServlet {
#RequestMapping(value = "/test1")
public String test1() {
return "test1";
}
#RequestMapping(value = "/test2")
public String test2(#RequestBody TestClass req) {
return "test2";
}
public static class TestClass {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}
But only the servlet not receiving parameters is working:
Works: http://localhost:8080/test1
Doesn't work: http://localhost:8080/test2?value=1234
org.springframework.http.converter.HttpMessageNotReadableException:
Required request body is missing: public java.lang.String
Why is the #RequestBody annotation not working? Am I missing an important piece?
One of the differences between #Controller and #RestController is that you don't have to write #RequestBody and #ResponseBody, that means that any parameter in your controller method which does not have an annotation (like #PathVariable, #ModelAttribute, ...) will implicitly have #RequestBody, and must therefore be POSTed as the HTTP entity body. So you need to send JSON/XML as part of a POST. What you have done is to send data on as part of the URL, which makes it a request parameter and not body-data, and you need #RequestParam to to extract data from the URL.
Also, I would recommend that you use the #GetMapping/#PostMapping or include the method parameter in the #RequestMapping annotation, it is highly unlikely that you want a service to be used for both POST and GET, so you should be as specific as possible in you controller method descriptions, to limit error scenarios.
The reason the second URL does not work is because when using #RequestBody the data you are sending to the endpoint needs to come through via the data attribute in the request header. When you append ?attr=value to your URL that is sending the attribute in the params header.
There are two ways to fix this:
Change your endpoint to read something like this:
public String test2(#RequestParam("value") TestClass req) {
//Endpoint code
}
Change your endpoint to read something like this:
#RequestMapping(value="test2",method=RequestMethod.POST)
public String test2(#RequestBody TestClass req){
//Endpoint code
}
and make your call similar to this (e.g. angularjs):
http.post({url:/*url*/,data:/*object to send*/});
The second option will most likely be what you want to go with because it looks like you are trying to send a json object to your endpoint and I believe you can only do that by making a POST request rather than a GET request
Just leave out the #RequestBody annotation, as this is only for POST requests.
public String test2(#Valid TestClass req) {
return "test2";
}
When you declare a controller method parameter as #RequestBody, you are wishing it to be recovered from the request body and not as a "regular" http parameter.
You could try using any kind of plugin for Firefox (RESTClient) or Chrome (PostMan) and try using one of them. You could do it using SoapUI as well.
The request should be a POST to the requested url this way:
POST http://localhost:8080/test2
You must provide http headers provinding expected Content-Type and Accept. In case of using Json, set them like this:
Content-Type: application/json
Accept: text/html (As your method returns only a String)
And then write the param to the request body. If in Json, like this:
{
"value":"the provided value"
}
I have a spring web service method where i want to get a string as a parameter. The string is sent in body of the request. My web service class is:
#Controller
#RequestMapping(value = "/users/{uid}/openchart")
public class OpenChartWebService {
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public String saveABC(#PathVariable("uid") Long uid,
#RequestBody String myString) {
System.out.println("sent string is: "+myString);
return "something";
}
}
My request in body is :
{
"name":"Ramesh"
}
But this is not working. This shows "Bad Request" HTTP error(400). How to send a string in a body and how to get a string sent in a body inside webservice method?
As #Leon suggests, you should add the media type to your request mapping, but also make sure you have Jackson on your classpath. You'll also want to change your #RequestBody argument type to something that has a "name" property, rather than just a String so that you don't have to convert it after.
public class Person {
private name;
public getName() {
return name;
}
}
If your data object looked like the above, then you could set your #RequestBody argument to Person type.
If all you want is a String, then perhaps just pass the value of "name" in your request body rather than an object with a name property.