Java Spring Controller handling a ridiculous url - java

Okay so I am using a payment service called Thrive cart, I am doing this for a membership website I'm creating. When the user has paid I want them to be redirected to a URL where I can use that data to update the current users information.
The data that get's sent in the params is insane:
http://localhost:5000/user/welcome?thrivecart%5Baccount_id%5D=3196&thrivecart%5Baccount_name%5D=testacount&thrivecart%5Bcustomer%5D%5Bemail%5D=testname8%40gmail.com&thrivecart%5Bcustomer%5D%5Baddress%5D%5Bcountry%5D=GB&thrivecart%5Bcustomer%5D%5Baddress%5D%5Bzip%5D=pe303wu&thrivecart%5Border%5D%5B0%5D%5Bt%5D=product&thrivecart%5Border%5D%5B0%5D%5Bid%5D=6&thrivecart%5Border%5D%5B0%5D%5Bn%5D=Monthly+membership&thrivecart%5Border%5D%5B0%5D%5Bp%5D=799&thrivecart%5Border%5D%5B0%5D%5Bq%5D=1&thrivecart%5Border%5D%5B0%5D%5Bpo%5D=60120&thrivecart%5Border%5D%5B1%5D%5Bt%5D=product&thrivecart%5Border%5D%5B1%5D%5Bid%5D=6&thrivecart%5Border%5D%5B1%5D%5Bn%5D=Monthly+membership&thrivecart%5Border%5D%5B1%5D%5Bp%5D=799&thrivecart%5Border%5D%5B1%5D%5Bq%5D=1&thrivecart%5Border%5D%5B1%5D%5Bpo%5D=60120&thrivecart%5Border_currency%5D=GBP&thrivecart%5Border_id%5D=1041278&thrivecart%5Border_tax%5D=0.2&thrivecart%5Border_tax_id%5D=gb&thrivecart%5Border_total%5D=799&thrivecart%5Bpayment_processor%5D=paypal&thrivecart%5Bproduct_id%5D=6&thrivecart%5Bpurchases%5D%5B0%5D=6&thrivecart%5Bpurchases%5D%5B1%5D=6&thrivecart_hash=a5b711d2288b4cb587511811bc0a3473
So far I've set up a simple controller which doesn't get hit:
#RestController
#RequestMapping("/user")
public class UserController {
#RequestMapping(value = "/welcome", method = RequestMethod.POST)
public void welcomeMember(#PathVariable String data) {
System.out.println(data);
}
}
How do I deal with crazy data like this? Do I have to specific each path param?

First of all, what you seem to get are not path elements but request parameters, so you will need #RequestParam annotations to get the values.
Since there are so many request parameters, I would also recommend to take just one parameter, a Map<String, String>. That Map will contain all the parameters as key/value pairs, for example:
key: "thrivecart[account_id]"
value: "3196"
If you're not sure whether you receive a POST or a GET request, you can also add a second parameter to receive the HttpMethod.
Change your RestController to:
#RestController
#RequestMapping("/user")
public class UserController {
#RequestMapping(value = "/welcome")
public void welcomeMember(#RequestParam Map<String, String> data, HttpMethod method) {
System.out.println(method);
System.out.println(data);
}
}

That looks like a problem with how the rest api is called from the service consumer side.
try sending the data in a request body rather then as a param. This way you can use a POJO to handle the data.

Question 1: So far I've set up a simple controller which doesn't get hit:
As per your URL http://localhost:5000/user/welcome "user" seems to be your projects context name. Try removing #RequestMapping("/user") from your class.
Also, instead of#PathVariable String data use #RequestParam Map<String,String> params. #PathVariable String data is used when data is part of url but in your case it's parameter. Final code should be like below.
#RestController
public class UserController {
#RequestMapping(value = "/welcome", method = RequestMethod.POST)
public void welcomeMember(#RequestParam Map<String,String> params ) {
for(Map.Entry<String, String> entry : params.entrySet()){
//This will print all paramters name and their value
System.out.println(entry.getKey() + "-" + entry.getValue());
}
}
}
Question 2: How do I deal with crazy data like this? Do I have to specific each path param?
I will suggest to follow standard practice. Send data in json format. There are different ways for this depend upon front end technology you are using. One way is Link

Related

Is there a way to obtain in Spring the list of path variables from the URI?

#RequestMapping(value = "/{ids}", method=RequestMethod.GET)
public String getMethod(#PathVariable List<String> ids){
}
I would like something similar, but I need the request to map to something like: localhost:8080/id1/id2/id3/.../idn
I don’t know the number of path variables (ids) and neither their names.
When you are using a path it essentially adds to the uri . The uri has length limitations.
Related post : Maximum length of HTTP GET request
So it is not advisable to add multiple number of parameters to the uri as path variable, when the number is not restricted.
You could use query params like :
#RequestMapping(value = "/{ids}", method=RequestMethod.GET)
public String getMethod(#RequestParam("myparam") List<String> ids)
{
}
Instead what you could have is convert it to a post request and have a request body with the list of data as an object.
#PostMapping("/")
public ResponseEntity postController(
#RequestBody CustomPojo data) {
exampleService.fakeAuthenticate(data);
return ResponseEntity.ok(HttpStatus.OK);
}
class CustomPoJo {
List<String> ids;
//getter setter etc
}
and the json could look like :
{"custompojo":["id1","id2"]}
It cannot be done since the request will look for endpoint having matching URL and matching method.

How to simplify REST controllers with same methods and different headers?

I have one Java REST API which is used by 2 different consumers. By default REST principles my API should define the names of request headers. But now I have not common situation. Consumers use different security layer which provides different headers which means same parameter in both ways.
Example method: (not real code)
For 1st consumer:
#PostMapping("/number")
Integer getNumber(#RequestHeader("no") String number, #RequestBody User user) {
/*...*/
}
For 2nd consumer:
#PostMapping("/number")
Integer getNumber(#RequestHeader("number") String number, #RequestBody User user) {
/*...*/
}
I have up to 10 methods in one controller and they should be with same name and logic, but different header. The request path prefix could be different.
Question:
How to simplify REST controller and don't create 2 different controllers with same methods and same logic?
What I tried:
I tried several examples to create one controller with 2 different interfaces with same methods, but different mapping.
Example:
Controller class
#RestController
#RequestMapping(path ="/application")
#Api(tags = {"application"})
public class ApplicationController implements AppMapping1, AppMapping2 {
#Override
public Integer getNumber(String number, User user) {
/*...*/
}
}
First interface
interface AppMapping1 {
#PostMapping("/num")
Integer getNumber(#RequestHeader("num") String number, #RequestBody User user);
}
Second interface
interface AppMapping2 {
#PostMapping("/number")
Integer getNumber(#RequestHeader("number") String number, #RequestBody User user);
}
Result:
Controller maps only with the first interface. So http://.../application/num works fine, but http://.../application/number - gets 404 error code. That means Java Spring-Boot doesn't have such functionality. Need some more ideas.
Project developed with Java 8; spring-boot:2.1.1.RELEASE; gradle
According to this , If we're not sure which headers will be present, or we need more of
them than we want in our method's signature, we can use the
#RequestHeader annotation without a specific name.
You have a few choices for variable type: a Map, a MultiValueMap or an HttpHeaders object.
Sample
#PostMapping("/number")
public Integer getNumber(#RequestHeader Map<String, String> headers) {
if (Optional.ofNullable(headers.get("no")).isPresent()){
//...
}
else if (Optional.ofNullable(headers.get("number")).isPresent())
{
//...
}
}
It is not maintenance friendly to repeat the same block of code twice or more times just to receive the same input with different names (number and no). Instead, it is advisable to read all the headers and traverse through it to fetch input using different names.
Sample Code
#PostMapping("/number")
public Integer getNumber(#RequestHeader Map<String, String> headers) {
String number = headers.containsKey("number") ? headers.get("number") : headers.get("no");
if(Objects.isNull(number)) {
throw new RuntimeException("Number input not received from header!");
}
// relevant processing
}
I found this answer on https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/spring-mvc-request-header.html
Avoid ambiguity by using #RequestMapping(headers = ....)
We can fix the ambiguity similar to #RequestParam where we used
'params' . In case of #RequestHeader we can define related headers in
#RequestMapping annotation.
#Controller
#RequestMapping("trades")
public class TradesController {
#RequestMapping(headers = "User-Agent")
public String handleAllTradesRequests (#RequestHeader("User-Agent") String userAgent,
Model model) {
model.addAttribute("msg", "all trades requests, User-Agent header : "
+ userAgent);
return "my-page";
}
#RequestMapping(headers = "From")
public String handleRequestByFromHeader (#RequestHeader("From") String from,
Model model) {
model.addAttribute("msg", "trade request by From header : " + from);
return "my-page";
}
You could remove the #RequestHeader annotation and consider doing the following:
#PostMapping("/number")
Integer getNumber(HttpServletRequest request, #RequestBody User user) {
String number = request.getHeader("num");
if(number == null){
number = request.getHeader("number");
}
/*...*/
}
If you want a cleaner approach, consider creating a util class that takes the HttpServletRequest object and returns the desired header value.
The best way is to add the HttpServletRequest as an argument of your single controller and do some logic with the header map provided by the HttpServletRequest object.
If you want to see a full example take a look here. I have implemented I single controller that wraps all my logic accordingly to headers/methods and so on. You can customize the logic as you want with the HttpServletRequest.

Can I create multiple HTTP POST methods with same request mapping with different requestbody

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) {}

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, deserialize single JSON?

How can I easily separate JSON values that are sent in the same request?
Given that I POST a JSON to my server:
{"first":"A","second":"B"}
If I implement the following method in the Controller:
#RequestMapping(value = "/path", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public void handleRequest(#RequestBody String input) {
// ...
}
then the input parameter will constitute a String with the entire JSON object, {"first":"A","second":"B"}. What I really want is two separate Strings (or a String and an int whichever is suitable for the particular request) with just the two values (other key / value pairs that the client may send should be ignored).
If the strings were sent as request parameters instead of JSON request body it would be simple:
#RequestMapping(value = "/path", method = RequestMethod.POST)
public void handleRequest(#RequestParam("first") String first,
#RequestParam("second") String second) {
// ...
}
I know that I can create a simple bean class that can be used in conjunction with the #RequestBody annotation that will contain both A and B when used, but it seems like a detour, since they will have different purposes inside the web app.
Dependencies:
org.springframework : spring-web : 3.1.0.RELEASE
org.codehaus.jackson : jackson-mapper-asl : 1.9.3
POJO
public class Input {
private String first;
private String second;
//getters/setters
}
...and then:
public void handleRequest(#RequestBody Input input)
In this case you need Jackson to be available on the CLASSPATH.
Map
public void handleRequest(#RequestBody Map<String, String> input)
I have written a custom WebArgumentResolver that does exactly this, combined with a custom annotation.
I don't have the source available to me now, but basically I annotated my method like this:
#RequestMapping(value = "/path", method = RequestMethod.POST)
public void handleRequest(#JsonField("first") String first, #JsonField("second") String second) {
// ...
}
Then my JsonFieldWebArgumentResolver checks if the method parameter is annotated with JsonField, and if it is it extracts the actual type from the parameter (not quite straight-forward it turns out if you want to handle generic parameters as well, such as List<String> or List<Pojo>), and invokes Jackson's JsonParser manually to create the correct type. It's a shame I can't show you any code, but that's the gist of it.
However, that solution is for Spring MVC 3.0, and if you are using 3.1 I think you will be better off using a custom HandlerMethodArgumentResolver instead. But the idea should be the same.

Categories

Resources