Spring boot Rest controller - convert form-encoded body to POJO - java

I would like to process an HTTP POST request hitting my rest endpoint and convert its body to my defined POJO. I was successful in the past with mapping between JSON and POJOs but I am struggling with this form encoded content.
I have a controller as follows:
#Slf4j
#RestController("/example")
public class GatewayController {
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public void describeInstances(#RequestBody MyPojo body) {
log.debug("Value1: " + body.getValue1());
}
}
And a POJO:
#Data
public class MyPojo {
private String value1;
private String value2;
...
}
I am hitting the controller with a POST request which looks as follows:
Content-Type: application/x-www-form-urlencoded
Body: value1=abc&value2=efg...
But all I am getting is 415 Unsupported Media Type. Any help is appreciated.
EDIT: when I change the POJO into String and just print the body, it works (without receving the unsupported media type exception) so it is definitely in the POJO declaration.
Follow up:
Is it possible to annotate the POJO attributes to allow different names of the fields (to avoid using undorscope inside variable names, for example)?
Something like:
#Attribute("value_name")
private String valName;

#ModelAttribute is the recommended way to get form data in your controller. e.g.
public void describeInstances(#ModelAttribute("mypojo") MyPojo body)
and
#Data
#ModelAttribute("mypojo")
public class MyPojo {
Read the spring docs for more info here

Related

How to get Authorization header from an MethodInterpcetor on micronaut?

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).

Parsing Base64 json directly into a POJO in a spring MVC controller

code for the controller
#RequestMapping(value = "/second", method = RequestMethod.POST,
consumes = "application/json")
#ResponseBody
public void test(#RequestBody RegistrationRequest
registrationRequest)
{
System.out.println(registrationRequest.toString());
}
POJO
#Data
#JsonDeserialize(using = Base64Deserializer.class)
public class RegistrationRequest {
#JsonProperty("payment-method-details")
public PaymentMethodDetails paymentMethodDetails;
#JsonProperty("customer-details")
public CustomerDetails customerDetails;
}
CustomerDetails, PaymentMethodDetails are POJO of their own.
I am very new to the Spring MVC concepts, and this is all I have figured out till now but while making the post request from the Postman, it isn't working. What is wrong that I am doing?
I can not maneuver anything how the request is going to come.
Using Base64 deserialiser
Decode base64 encoded JSON to POJO with jackson and spring-boot
It is not clear from the question, about the desired format of the request.
Above code should work fine with a request like this :
curl -X POST "http://myhost:port/rootPath/second/" -d '{"payment-method-details":"eyJwYXltZW50TWV0aG9kRGV0YWlsc1ZhcjEiOiJzb21lIHRleHQiLCJwYXltZW50TWV0aG9kRGV0YWlsc1ZhcjIiOiJzb21lIHRleHQyIn0=", "customer-details":"eyJjdXN0b21lckRldGFpbHNWYXIxIjoic29tZSB0ZXh0IiwiY3VzdG9tZXJEZXRhaWxzVmFyMiI6InNvbWUgdGV4dDIifQ=="}'
This will get the above code working and prints the output.
Note, here eyJjdXN0b21lckRldGFpbHNWYXIxIjoic29tZSB0ZXh0IiwiY3VzdG9tZXJEZXRhaWxzVmFyMiI6InNvbWUgdGV4dDIifQ== decodes to {"customerDetailsVar1":"some text","customerDetailsVar2":"some text2"} in base64 format, similar is the case for other variable.

How to call a #RestController with #RequestBody?

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"
}

Spring MVC - parameter binding

How come this code just works? I didn't specify any custom converter or annotation (like #RequestBody or #ModelAttribute) before argument ? Request is filled correctly from this GET call:
http://localhost:8080/WS/foo?token=C124EBD7-D9A5-4E21-9C0F-3402A1EE5E9B&lastSync=2001-01-01T00:00:00&pageNo=1
Code:
#RestController
#RequestMapping(value = "/foo")
public class FooController {
#RequestMapping(method = RequestMethod.GET)
public Result<Foo> excursions(Request request) {
// ...
}
}
Request is just POJO with getters and setters. I use it to shorten argument code because plenty methods uses those same arguments ...
public class Request {
private String token;
#DateTimeFormat(pattern = IsoDateTime.DATETIME)
private Date lastSync;
private Integer pageNo;
// getters and setters
}
This was my original method before introducing Request.
#RestController
#RequestMapping(value = "/foo")
public class FooController {
#RequestMapping(method = RequestMethod.GET)
public Result<Foo> excursions(#RequestParam String token, #RequestParam #DateTimeFormat(pattern = IsoDateTime.DATETIME) Date lastSync, #RequestParam Integer pageNo) {
// ...
}
}
Request parameters will be mapped to POJOs, as it is happening in your case, by default. Additionally, if you use #ModelAttribute, an attribute in the Model will be created. That attribute can be then used in views, e.g. JSPs, to access the object.
#RequestBody annotation tells that the body of the request is NOT a set of form parameters like
token=C124EBD7-D9A5-4E21-9C0F-3402A1EE5E9B&lastSync=2001-01-01T00:00:00&pageNo=1
but is in some other format, such as JSON.
This is a feature provided by Spring MVC:
Customizable binding and validation. Type mismatches as application-level validation errors that keep the offending value, localized date and number binding, and so on instead of String-only form objects with manual parsing and conversion to business objects.
You can see it in the doc: http://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/htmlsingle/

Mapping POJOs to application/x-www-form-urlencoded with custom form keys

I'm using RoboSpice with Spring Android to perform REST api calls.
Data need to be sent with the Content-Type set to application/x-www-form-urlencoded.
Let's say I have a Pojo class like this:
public class Pojo {
private String pojoAttribute;
// getter + setter
}
Here is my request class:
public class PojoRequest extends AbstractApiRequest<PojoResult> {
private Pojo pojo;
public PojoRequest(Pojo pojo) {
super(PojoResult.class);
this.pojo = pojo;
}
#Override
public PojoResult loadDataFromNetwork() throws Exception {
HttpEntity<Pojo> requestEntity = new HttpEntity<Pojo>(pojo, getDefaultHeaders());
String url = buildUrlForPath("mypath");
RestTemplate restTemplate = getRestTemplate();
restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
return restTemplate.postForObject(url, requestEntity, PojoResult.class);
}
}
So, suppose I need to send this body:
pojo_attribute=foo
Now my code does not work because FormHttpMessageConverter does not handle POJOs.
What I would like to be able to do is something like this:
public class Pojo {
#FormProperty("pojo_attribute")
private String pojoAttribute;
// getter + setter
}
With a magic HttpMessageConverter that converts my POJO into key=value format taking care of converting the pojoAttribute to pojo_attribute by inspecting the #FormProperty (does not exist) annotation the same way Jackson does with #JsonProperty.
My questions are:
- is it possible to do this with existing classes/annotations?
- is there another way to do something similar?
- would it be overkill if I create my own HttpMessageConverter and set of annotations to do just that?

Categories

Resources