How to get request in Spring REST controller depending on JSON requirement? - java

public ResponseEntity<TastyDashResponse> order(#PathVariable("restaurantId") String restaurantId,
#RequestBody RestaurantOrderBook request,
#RequestBody ExpItems exp) {}
I have two Objects RestaurantOrderBook and ExpItems. Incoming request should have either of them and the other becomes optional.
How to achieve this within same method. When this code I get 404 bad request. please help with it.

You cannot use two #RequestBody as it can bind to a single object .
solution create one object that will capture all the relevent data like this this :
public class Data {
private String restaurantId;
private RestaurantOrderBook request;
private ExpItems exp;
getters/setters
}
public ResponseEntity<TastyDashResponse> order(#RequestBody Data data) {}

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

Spring RequestBody with either List<Object> or Object. Name for both are same "data"

I want to handle json request for List of Object and Object it self in the same Spring controller. Below is the exact example.
Json Request for Single Object:
{"data":{"prop":"123456","prop2":"123456"}}
Json Request for List of Objects:
{"data":[{"prop":"123456","prop2":"123456"},{"prop":"123456","prop2":"123456"}]}
My Controller is as follow.
#PostMapping(path="/path")
public #ResponseBody String getSomething(#RequestBody Input data){
return service.getSomething(data);
}
I want to handle both of this requests in a single spring controller.
Appreciate your help.
Thanks.
Its been some time.
However I was facing the same issue and was thinking whether any solution is there
The below worked for me
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<Data> data;
EDIT
You can create DTOs for your inputs but receive the #RequestBody as a JSON string and then parse it as a list or a single object request.
DTO for a request list:
public class InputDataList {
private List<Input> data;
// getters and setters
}
DTO for a single request object:
public class InputDataSingle {
private Input data;
// getters and setters
}
Then on your controller:
#PostMapping(path="/path")
public #ResponseBody String getSomething(#RequestBody String json){
// this is better done in a service but for simplicity, I write it in the controller
try {
InputDataSingle data = new ObjectMapper().readValue(json, InputDataSingle.class);
// If fails to parse as a single object, parse as a list
} catch (Exception) (
InputDataList data = new ObjectMapper().readValue(json, InputDataList.class);
}
// handle objects in the service layer
}
Sorry, Really not a good workaround (solution), because it is not kind of misleading and poor for maintainability, secondly not good at all to keep code in the catch block and this is violating the coding principles.
I would highly suggest splitting 2 endpoints one for single i.e (/something) and another for list (/something-batch)

Spring ResponseEntity best practice

I am new to RESTful web services in general, and am learning the Spring implementation of web services.
I am particularly interested in learning how to properly use ResponseEntity return types for most of my use cases.
I have one endpoint:
/myapp/user/{id}
This endpoint supports a GET request, and will return a JSON formatted string of the User object whose ID is {id}. I plan to annotate the controller method as producing JSON.
In the case that a user with ID {id} exists, I set a status of 200, and set the JSON string of the user in the body.
In the event that no user exists with that ID, I was going to return some flavor of a 400 error, but I am not sure what to set in the body of the ResponseEntity. Since I annotate the endpoint method as producing JSON, should I come up with a generic POJO that represents an error, and return that as JSON?
You donĀ“t need to use a generic Pojo, using RequestMapping you can create different responses for every Http code. In this example I show how to control errors and give a response accordingly.
This is the RestController with the service specification
#RestController
public class User {
#RequestMapping(value="/myapp/user/{id}", method = RequestMethod.GET)
public ResponseEntity<String> getId(#PathVariable int id){
if(id>10)
throw new UserNotFoundException("User not found");
return ResponseEntity.ok("" + id);
}
#ExceptionHandler({UserNotFoundException.class})
public ResponseEntity<ErrorResponse> notFound(UserNotFoundException ex){
return new ResponseEntity<ErrorResponse>(
new ErrorResponse(ex.getMessage(), 404, "The user was not found") , HttpStatus.NOT_FOUND);
}
}
Within the getId method there is a little logic, if the customerId < 10 It should response the Customer Id as part of the body message but an Exception should be thrown when the customer is bigger than 10 in this case the service should response with an ErrorResponse.
public class ErrorResponse {
private String message;
private int code;
private String moreInfo;
public ErrorResponse(String message, int code, String moreInfo) {
super();
this.message = message;
this.code = code;
this.moreInfo = moreInfo;
}
public String getMessage() {
return message;
}
public int getCode() {
return code;
}
public String getMoreInfo() {
return moreInfo;
}
}
And finally I'm using an specific Exception for a "Not Found" error
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
In the event that no user exists with that ID, I was going to return
some flavor of a 400 error, but I am not sure what to set in the body
of the ResponseEntity. Since I annotate the endpoint method as
producing JSON, should I come up with a generic POJO that represents
an error, and return that as JSON?
This is definitely a possible solution, if you want to add e.g. a more specific reason why the request failed or if you want to add a specific I18N message or just want to generify your API to provide some abstract structure.
I myself prefer the solution #Herr Derb suggested, if there is nothing to return, don't return anything. The returned data may be completely unnecessary/unused and the receiver may just discard it if the return code is anything else than 2XX.
This may be related:
http://www.bbenson.co/post/spring-validations-with-examples/
The author describes how to validate incoming models and builds a generic error response. Maybe this is something you want to do...

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 mapping request params to object

I am using Spring Boot and I have to write function, that takes a lot a parameters:
public ResponseEntity function(
#RequestParam("page") Integer page,
#RequestParam("size) Integer size,
#RequestParam("name") String name,
#RequestParam("groups") String name) {
....
}
I dont like this method signature (as there is a lot more parameters than 4) and I can't use #RequestBody.
I tried creating a special Object
public ParamHandler {
private int age;
private int size;
private String name;
private Group[] groups;
}
And then
public ResponseEntity(ParamHadler ph) {....}
However, as you see I need to cast String "groups" to array of object Group. Spring can't do this automatically. So I tried to write custom Jackson deserializer for ParamHandler. Obviously, it doesn't help as #RequestParam hardly connected wit JSON.
So I want to know how can override Spring's object mapper for request params? Any other techniques will also be appriciated.
Thanks in advance!

Categories

Resources