Spring application/x-www-form-urlencoded prevent encoding of string - java

We have a #PostMapping which is a webhook provided to a third party, which is called whenever an event occurs on the third party.
#PostMapping(path = "/some-api", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public void proessWebhook(#RequestBody String request) {
The request strings keys are encoded in the String request. For our internal requirements, we need the key to not be pre-encoded when spring sets it to the String request variable. We tried URL decoding the variable, however, the problem is that there are values that we don't want to decode, so decoding the entire string decodes the key and the values.
An example if in the CURL request the input is some key=some%20value, the request variable would be set to some%20key=some%20value. Whereas we only need the key decoded.
On top of this, the order of the parameters also change from the input, it is important for the order to be the same.
How do we go about doing this?

if your method parameter is of a type MultiValueMap, you can use either the #RequestParam or #RequestBody annotation to bind it appropriately with the body of the HTTP request.
#PostMapping(
path = "/some-api",
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public ResponseEntity<String> funName(
#RequestParam MultiValueMap<String,String> paramMap) throws Exception {
}

Related

Http Put request with content type application/x-www-form-urlencoded not working in Spring

I have a spring-boot application that the only thing it does is receive http requests.
This is my spring cotroller:
#RestController
public class WebController {
#Autowired
private CallRecording callRecording;
#PutMapping(path = "/cdrpostbox/callrecording/{hostedAccountId}/{mp3FileName}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseEntity<Object> callRecording(#PathVariable("hostedAccountId") String hostedAccountId, #PathVariable("mp3FileName") String mp3FileName, MultipartFile file) {
return ResponseEntity.status(callRecording.service(hostedAccountId, mp3FileName, file)).body(null);
}
}
I'm using Postman to send the request. The request that my spring application receives can't be changed, because the code is not maintained by my team.
request headers
request body
I found quite a few questions in here about the similar problems, but not quite the same problem that I have to solve. I tried adding and removing #RequestBody , replacing #RequestBody with #RequestParam, I tried the MultiValueMap, but it keeps returning the same error.
java.lang.IllegalArgumentException: URLDecoder: Illegal hex characters in escape (%) pattern - For input string: "x"
I can't even debug the code because it fails before it reaches the controller.
What am I missing?
The content-type in the header is urlencoded, but you send binary data.
--- edit
If you want to get the file as a MultipartFile, then send the file as a form-data from postman, set the name of the data to file and add #RequestParam to the file argument in your method.
After a bit more investigation, I found the solution.
#RestController
#EnableWebMvc
public class WebController {
#Autowired
private CallRecording callRecording;
#PutMapping(path = "/cdrpostbox/callrecording/{hostedAccountId}/{mp3FileName:.+}")
public ResponseEntity<Object> callRecording(#PathVariable("hostedAccountId") String hostedAccountId, #PathVariable("mp3FileName") String mp3FileName, #RequestBody byte[] requestBody) {
return ResponseEntity.status(callRecording.service(hostedAccountId, mp3FileName, requestBody)).body(null);
}
It was missing the #EnableWebMvc. I also changed the mapping {mp3FileName:.+} because it was truncating the end of the request, you can check the SO question here .
Thanks #Selindek for the help

GET call with request body - request body not accessible at controller

I am trying to do a get call with request body(JSON) as the request parameter list exceeds the limit. I am able to send the request via postman/insomnia and request is reaching till controller without any error. But the "requstBody" is empty at controller. What i am missing here?
#GET
#Path("\path")
#Consumes(APPLICATION_JSON)
#Produces(APPLICATION_JSON)
public Response getResponse(String requestBody) throws IOException { }
When I replaced #GET with #POST, requestBody has value. For GET call do we need to add anything more?
I am trying to do a get call with request body(JSON) as the request parameter list exceeds the limit. I am able to send the request via postman/insomnia and request is reaching till controller without any error. But the "requstBody" is empty at controller. What i am missing here?
One thing you are missing is the fact that the semantics of a request body with GET are not well defined.
RFC 7231, Section 4.3.1:
A payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request.
There are two ways for sending parameters in an Http Get method. PathVariable and RequestParam. In this way, sent parameters are visible in the request URL. for example:
www.sampleAddress.com/countries/{parameter1}/get-time?city=someValues
In the above request, parameter1 is a path variable and parameter2 is a request parameter. So an example of a valid URL would be:
www.sampleAddress.com/countries/Germany/get-time?city=berlin
To access these parameters in a java controller, you need to define a specific name for the parameters. For example the following controller will receive this type of requests:
#GetMapping(value = "/countries/{parameter1}/get-time", produces = "application/json; charset=utf-8")
public String getTimeOfCities(
#PathVariable(value = "parameter1") String country,
#RequestParam(value = "city") String city
){
return "the method is not implemented yet";
}
You are able to send RequestBody through a Get request but it is not recommended according to this link.
yes, you can send a body with GET, and no, it is never useful
to do so.
This elaboration in elasticsearch website is nice too:
The HTTP libraries of certain languages (notably JavaScript) don’t allow GET requests to have a request body. In fact, some users are suprised that GET requests are ever allowed to have a body.
The truth is that RFC 7231—the RFC that deals with HTTP semantics and
content—does not define what should happen to a GET request with a
body! As a result, some HTTP servers allow it, and some—especially
caching proxies—don’t.
If you want to use Post method, you are able to have RequestBody too. In the case you want to send data by a post request, an appropriate controller would be like this:
#PostMapping(value = "/countries/{parameter1}/get-time", produces = "application/json; charset=utf-8")
public String getTimeOfCitiesByPost(
#PathVariable(value = "parameter1") String country,
#RequestParam(value = "city") String city,
#RequestBody Object myCustomObject
){
return "the method is not implemented yet";
}
myCustomObject could have any type of data you defined in your code. Note that in this way, you should send request body as a Json string.
put #RequestBody on String requestBody parameter
#RequestMapping("/path/{requestBody}")
public Response getResponse(#PathVariable String requestBody) throws IOException { }

web request with request params and content body

I've used Matlab's webwrite() to make calls to a REST API, providing the requset params. However, I need to now make a call where the Request Body must be specified. Is there a way to do this?
The REST API is defined by a Java Spring controller, e.g.:
#PostMapping(value = "post")
public ResponseEntity<?> setMySTuff(
#RequestParam(name = "myId") int myId,
#RequestBody Collection<MyCustomObject> myObjList) {
THe data paramemter for webwrite seems intended for being a set of key/value request param pairs, and not a means of setting the request body.
If I remember correctly, #RequestParam is used for mapping values as query parameters, while #RequestBody defines the content of the response. If my assumptions are valid, the Matlab equivalent should be:
url = ['http://mywebsite.net/service/?myId=' num2str(5778)];
body = struct('Item1','Hello','Item2','World');
opts = weboptions('MediaType','application/json','RequestMethod','post');
response = webwrite(url,body,opts);

How to return proper error response for content-type text/csv?

I have a servlet where in general I return text/csv as response. So a plain comma separated string.
BUT: in case of exceptions, I'd want to show just some error text as response. But instead Spring generates a custom ResponseEntity object, then tries to convert this response to csv which obviously fails.
Is it possible to replace the requested format to format=json, and then just return the default Spring error response?
#RestController
public class CsvServlet {
#RequestMapping(value = "/test", produces = "text/csv")
#ResponseBody
public String errorCsv(HttpServletRequest request) {
return "some, plain, text";
}
}
Usage: localhost:8080/test?format=csv
When having spring.security.enabled=true, this will first validate the basic auth credentials. If they fail, spring will automatically redirect to /error servlet.
Thereby BasicErrorController.error() method comes in, catching the error and generating a ResponseEntity with error attributes like timestamp, exception, path, etc.
Problem: now an Object of type ResponseEntity is returned. This is fine as long as the format parameter is either ?format=json/xml. As the response can then be properly converted.
But in my special case, where I request ?format=csv, the conversation will fail, leading to:
Response could not be created:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not
find acceptable representation
This is partially true, moreover misleading because the user should directly see that the authentication credentials have been invalid. Because in general I'm accepting csv, but Spring and the mappers don't know how to convert a ResponseEntity to a plain format like csv.
Question: how can I preserve the original exception? So that I could just return a plain text error message. It would also be fine if I could return a application/json response in this case!
Workaround as follows for the moment: override the /error servlet handler, and if the ?format parameter is not either json/xml, just return the original http status error without a body.
Thus spring cannot fail on converting the body into plain format and return the response correctly (but without exception details in the body).
#Controller
public class WorkaroundBasicErrorController extends BasicErrorController {
//default constructor from ErrorMvcAutoConfiguration
public DefaultBasicErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties,
ObjectProvider<List<ErrorViewResolver>> errorViewResolversProvider) {
super(errorAttributes, serverProperties.getError(), errorViewResolversProvider.getIfAvailable());
}
#RequestMapping
#ResponseBody
#Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
String format = request.getParameter("format");
return (StringUtils.containsAny(format, "json", "xml"))
? super.error(request)
: new ResponseEntity<>(getStatus(request)); //neglect body for plain formats
}
}
Try to use a void #ExceptionHandler, and write error message directly in HttpResponse

Spring MVC using same path on endpoints to return different content?

I'm going to use a very basic hello world endpoint as an example
#RequestMapping("/hello")
public String hello(#RequestParam(value="name", required=false, defaultValue="World") String name, Model model) {
model.addAttribute("name", name);
return "helloworld";
}
If I have this endpoint and I want to be able to go to /hello and retrieve the helloworld view.
Is it possible for me to use the SAME /hello path to retrieve model as json if I pass in a specific request param like content-type?
You could try passing in a parameter using the RequestMapping params option. This does require modifying the URL, but the mapping is still the same and a mapped method without a params tag could be added as a default.
#RequestMapping(value="/hello" params= param1)
public returnType method(#RequestParam("param1") p) { ... }
#RequestMapping(value="/hello" params= param2)
public differentreturnType method2(#RequestParam("param2") p) { ... }
So to handle the first, request URL : http://etc.com/hello?param1=x and the second http://etc.com/hello?param2=y.
Params section of #RequestMapping docs: http://docs.spring.io/spring/docs/4.0.5.RELEASE/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html#params--
I'm not sure I understand what you mean.
If you mean that you want to be able to send a request to /hello and get two different responses, with different content types, yes, you can do that.
#RequestMapping identifies a method as being a request handler, but it also provides options for restricting when the handler should be used.
In this case, you should use the Accept header in your HTTP request and set it to application/json for a response containing JSON and text/html for a response containing HTML.
You can then have two #RequestMapping methods like
#RequestMapping(value = "/hello", produces = "application/json")
public SomeType handleJson() {...}
#RequestMapping(value = "/hello", produces = "text/html")
public String handleHtml() {...}
Spring will determine which method to use based on the request's Accept header and the method's produces value.

Categories

Resources