How to handle requests that includes forward slashes (/)? - java

I need to handle requests as following:
www.example.com/show/abcd/efg?name=alex&family=moore (does not work)
www.example.com/show/abcdefg?name=alex&family=moore (works)
www.example.com/show/abcd-efg?name=alex&family=moore (works)
It should accept any sort of character from the value that is located between www.example.com/show/ and ?. Please note the value that would be located there would be a single value not name of an action.
For example: /show/abcd/efg and /show/lkikf?name=Jack in which the first request should redirect user to the page abcd/efg (because thats a name) and the second one should redirect user to the page lkikf along with value of parameter name.
I have following controller to handle it but the issue is when I have / in the address the controller is unable to handle it.
#RequestMapping(value = "/{mystring:.*}", method = RequestMethod.GET)
public String handleReqShow(
#PathVariable String mystring,
#RequestParam(required = false) String name,
#RequestParam(required = false) String family, Model model) {
I used following regex which did not work.
/^[ A-Za-z0-9_#./#&+-]*$/

Another way I do is:
#RequestMapping(value = "test_handler/**", method = RequestMethod.GET)
...and your test handler can be "/test_hanlder/a/b/c" and you will get the whole value using following mechanism.
requestedUri = (String)
request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);

You have to create two methods then one having the #RequestMapping(value = { "/{string:.+}" }) annotation and the other having #RequestMapping(value = { "/{string:.+}", "/{string:.+}/{mystring:.+}" }) and then act accordingly in each, because you can't have optional path variables.
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
#Controller
#RequestMapping("/show")
public class HelloController {
#RequestMapping(value = { "/{string:.+}" })
public String handleReqShow(#PathVariable String string,
#RequestParam(required = false) String name,
#RequestParam(required = false) String family, Model model) {
System.out.println(string);
model.addAttribute("message", "I am called!");
return "hello";
}
#RequestMapping(value = { "/{string:.+}", "/{string:.+}/{mystring:.+}" })
public String whatever(#PathVariable String string,
#PathVariable String mystring,
#RequestParam(required = false) String name,
#RequestParam(required = false) String family, Model model) {
System.out.println(string);
System.out.println(mystring);
model.addAttribute("message", "I am called!");
return "hello";
}
}

The first one is not working because you are trying to handle an entirely new URL which is not actually mapped your controller.
www.example.com/show/abcd/efg?name=alex&family=moore (does not work)
The correct mapping for the above URL could be like the below code.
#RequestMapping(value = {"/{mystring:.*}" , "/{mystring:.*}/{mystring2:.*}"}, method = RequestMethod.GET)
public String handleReqShow(
#PathVariable String mystring,
#PathVariable String mystring2,
#RequestParam(required = false) String name,
#RequestParam(required = false) String family, Model model) {
I have tried the similar concept when my one controller is used to handle multiple types of request.

You could encode slashes on UI with %2f: http://www.example.com/show/abcd%2fefg?name=alex&family=moore.
Now you should configure Spring to handle slashes. Simple config example:
#RestController
public class TestController {
#GetMapping("{testId:.+}")
public String test(#PathVariable String testId) {
return testId;
}
#GetMapping("{testId:.+}/test/{messageId}")
public String test2(#PathVariable String testId, #PathVariable String messageId) {
return testId + " " + messageId;
}
//Only if using Spring Security
#Configuration
public static class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
DefaultHttpFirewall firewall = new DefaultHttpFirewall();
firewall.setAllowUrlEncodedSlash(true);
return firewall;
}
#Override
public void configure(WebSecurity web) throws Exception {
web.httpFirewall(allowUrlEncodedSlashHttpFirewall());
}
}
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE)
public static class SpringMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setUrlDecode(false);
configurer.setUrlPathHelper(urlPathHelper);
}
}
}

The default Spring MVC path mapper uses the / as a delimiter for path variables, no matter what.
The proper way to handle this request would be to write a custom path mapper, that would change this logic for the particular handler method and delegate to default for other handler methods.
However, if you know the max possible count of slashes in your value, you can in fact write a handler that accepts optional path variables, and than in the method itself, assemble the value from path variable parts, here is an example that would work for max one slash, you can easily extend it to three or four
#RequestMapping(value = {"/{part1}", "/{part1}/{part2}"}, method = RequestMethod.GET)
public String handleReqShow(
#PathVariable Map<String, String> pathVariables,
#RequestParam(required = false) String name,
#RequestParam(required = false) String family, Model model) {
String yourValue = "";
if (pathVariables.containsKey("part1")) {
String part = pathVariables.get("part1");
yourValue += " " + part;
}
if (pathVariables.containsKey("part2")) {
String part = pathVariables.get("part2");
yourValue += " /" + part;
}
// do your stuff
}
You can catch all the path variables inside the map, the map #PathVariable Map<String, String> pathVariables, but the downside is that the static part of the mapping has to enumarate all the possible variations

You can define rules to avoid that
<filter>
<filter-name>UrlRewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>UrlRewriteFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
rules.xml add this to your WEB-INF
<urlrewrite>
<rule>
<from>^/(10\..*)$</from> <!-- tweak this rule to meet your needs -->
<to>/Show?temp=$1</to>
</rule>
</urlrewrite>

Try escaping forward slash.
Regex: /^[ A-Za-z0-9_#.\/#&+-]*$/

Related

validation of the entered field

in my application, I enter the values of the three parameters, fromCurrency, toCurrency, and amount into the address bar
and in the controller. I want to check the correctness of the entered data. But I have generated an exception during the test and nothing goes further
Those. I need a code that in the controller will check the correctness of the entered data and, depending on the field in which the error was made, will produce a 400th error with the name of the incorrectly filled field
I'm tried this validation, with
if(!Currency.getAvailableCurrencies().contains(Currency.getInstance(fromCurrency)))
but it's generate exception if Currency doesn't contain fromCurrency
#RestController
class ExchangeController {
private static final Logger logger = Logger.getLogger(ExchangeController.class.getName());
#SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
#Autowired
#Qualifier("dataService")
private CurrencyExchangeService currencyExchangeService;
#SuppressWarnings("SameReturnValue")
#RequestMapping(value = "/", method = RequestMethod.GET, produces = "application/json")
public String start() {
return "input parameters";
}
#RequestMapping(value = "/convert", method = RequestMethod.GET, produces = "application/json")
public ExchangeRateDTO converting(#RequestParam("fromCurrency") String fromCurrency,
#RequestParam("toCurrency") String toCurrency,
#RequestParam("amount") String amount) throws IOException {
if (!Currency.getAvailableCurrencies().contains(Currency.getInstance(fromCurrency))) {
}
BigDecimal convertedAmount = currencyExchangeService.convert(fromCurrency, toCurrency, new BigDecimal(amount));
return new ExchangeRateDTO(fromCurrency, toCurrency, new BigDecimal(amount), convertedAmount);
}
}
You can use Hibernate Validator to validate the #RequestParam of your controller.
Add this dependency to your pom.xml
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.10.Final</version>
</dependency>
Then you have to enable validation for both request parameters and path variables in your controllers by adding the #Validated annotation like this
#RestController
#RequestMapping("/")
#Validated
public class Controller {
// ...
}
Then you can add Annotations like #NotNull #Min #Max to your RequestParam Like
#RequestMapping(value = "/convert", method = RequestMethod.GET, produces = "application/json")
public ExchangeRateDTO converting(#RequestParam("fromCurrency") #NotNull #NotBlank #Size(max = 10) String fromCurrency,
#RequestParam("toCurrency") String toCurrency,
#RequestParam("amount") String amount) throws IOException {
if (!Currency.getAvailableCurrencies().contains(Currency.getInstance(fromCurrency))) {
}
BigDecimal convertedAmount = currencyExchangeService.convert(fromCurrency, toCurrency, new BigDecimal(amount));
You can also define custom annotations for your needs.
There is more detailed and nice article here

How to convert one request param into several request params in Spring before calling a controller?

The client sends some data in one request param like:
example.com/test?myparam=some123data
I would like to convert myparam into several other params and call a necessary controller with such parameters. Like this one:
#RequestMapping(value = "/test")
public #ResponseBody MyObject test(
#RequestParam(value = "prefix") String prefix, // some
#RequestParam(value = "number") int number, // 123
#RequestParam(value = "suffix") String suffix) //data
{ ... }
It is possible to put some custom converter for such situation?
I'm not really sure if it can be made wit request params. Instead, you could use path variables with regular expressions in the following way:
#RequestMapping(value = "/test/{prefix:[a-z]+}{number:[0-9]+}{suffix:[a-z]+}")
public #ResponseBody MyObject test(
#PathVariable(value = "prefix") String prefix, // some
#PathVariable(value = "number") int number, // 123
#PathVariable(value = "suffix") String suffix) //data
{ ... }
In this case your request URL will look like this:
example.com/test/some123data
You can try to implement your own argument resolver, here an example

How to find the id of the source link in a Spring controller [duplicate]

What is the difference between #RequestParam and #PathVariable while handling special characters?
+ was accepted by #RequestParam as space.
In the case of #PathVariable, + was accepted as +.
#PathVariable is to obtain some placeholder from the URI (Spring call it an URI Template)
— see Spring Reference Chapter 16.3.2.2 URI Template Patterns
#RequestParam is to obtain a parameter from the URI as well — see Spring Reference Chapter 16.3.3.3 Binding request parameters to method parameters with #RequestParam
If the URL http://localhost:8080/MyApp/user/1234/invoices?date=12-05-2013 gets the invoices for user 1234 on December 5th, 2013, the controller method would look like:
#RequestMapping(value="/user/{userId}/invoices", method = RequestMethod.GET)
public List<Invoice> listUsersInvoices(
#PathVariable("userId") int user,
#RequestParam(value = "date", required = false) Date dateOrNull) {
...
}
Also, request parameters can be optional, and as of Spring 4.3.3 path variables can be optional as well. Beware though, this might change the URL path hierarchy and introduce request mapping conflicts. For example, would /user/invoices provide the invoices for user null or details about a user with ID "invoices"?
#RequestParam annotation used for accessing the query parameter values from the request. Look at the following request URL:
http://localhost:8080/springmvc/hello/101?param1=10&param2=20
In the above URL request, the values for param1 and param2 can be accessed as below:
public String getDetails(
#RequestParam(value="param1", required=true) String param1,
#RequestParam(value="param2", required=false) String param2){
...
}
The following are the list of parameters supported by the #RequestParam annotation:
defaultValue – This is the default value as a fallback mechanism if request is not having the value or it is empty.
name – Name of the parameter to bind
required – Whether the parameter is mandatory or not. If it is true, failing to send that parameter will fail.
value – This is an alias for the name attribute
#PathVariable
#PathVariable identifies the pattern that is used in the URI for the incoming request. Let’s look at the below request URL:
http://localhost:8080/springmvc/hello/101?param1=10&param2=20
The above URL request can be written in your Spring MVC as below:
#RequestMapping("/hello/{id}") public String getDetails(#PathVariable(value="id") String id,
#RequestParam(value="param1", required=true) String param1,
#RequestParam(value="param2", required=false) String param2){
.......
}
The #PathVariable annotation has only one attribute value for binding the request URI template. It is allowed to use the multiple #PathVariable annotation in the single method. But, ensure that no more than one method has the same pattern.
Also there is one more interesting annotation:
#MatrixVariable
http://localhost:8080/spring_3_2/matrixvars/stocks;BT.A=276.70,+10.40,+3.91;AZN=236.00,+103.00,+3.29;SBRY=375.50,+7.60,+2.07
And the Controller method for it
#RequestMapping(value = "/{stocks}", method = RequestMethod.GET)
public String showPortfolioValues(#MatrixVariable Map<String, List<String>> matrixVars, Model model) {
logger.info("Storing {} Values which are: {}", new Object[] { matrixVars.size(), matrixVars });
List<List<String>> outlist = map2List(matrixVars);
model.addAttribute("stocks", outlist);
return "stocks";
}
But you must enable:
<mvc:annotation-driven enableMatrixVariables="true" >
#RequestParam is use for query parameter(static values) like: http://localhost:8080/calculation/pow?base=2&ext=4
#PathVariable is use for dynamic values like : http://localhost:8080/calculation/sqrt/8
#RequestMapping(value="/pow", method=RequestMethod.GET)
public int pow(#RequestParam(value="base") int base1, #RequestParam(value="ext") int ext1){
int pow = (int) Math.pow(base1, ext1);
return pow;
}
#RequestMapping("/sqrt/{num}")
public double sqrt(#PathVariable(value="num") int num1){
double sqrtnum=Math.sqrt(num1);
return sqrtnum;
}
1) #RequestParam is used to extract query parameters
http://localhost:3000/api/group/test?id=4
#GetMapping("/group/test")
public ResponseEntity<?> test(#RequestParam Long id) {
System.out.println("This is test");
return ResponseEntity.ok().body(id);
}
while #PathVariable is used to extract data right from the URI:
http://localhost:3000/api/group/test/4
#GetMapping("/group/test/{id}")
public ResponseEntity<?> test(#PathVariable Long id) {
System.out.println("This is test");
return ResponseEntity.ok().body(id);
}
2) #RequestParam is more useful on a traditional web application where data is mostly passed in the query parameters while #PathVariable is more suitable for RESTful web services where URL contains values.
3) #RequestParam annotation can specify default values if a query parameter is not present or empty by using a defaultValue attribute, provided the required attribute is false:
#RestController
#RequestMapping("/home")
public class IndexController {
#RequestMapping(value = "/name")
String getName(#RequestParam(value = "person", defaultValue = "John") String personName) {
return "Required element of request param";
}
}
it may be that the application/x-www-form-urlencoded midia type convert space to +, and the reciever will decode the data by converting the + to space.check the url for more info.http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1
#PathVariable - must be placed in the endpoint uri and access the query parameter value from the request
#RequestParam - must be passed as method parameter (optional based on the required property)
http://localhost:8080/employee/call/7865467
#RequestMapping(value=“/call/{callId}", method = RequestMethod.GET)
public List<Calls> getAgentCallById(
#PathVariable(“callId") int callId,
#RequestParam(value = “status", required = false) String callStatus) {
}
http://localhost:8080/app/call/7865467?status=Cancelled
#RequestMapping(value=“/call/{callId}", method = RequestMethod.GET)
public List<Calls> getAgentCallById(
#PathVariable(“callId") int callId,
#RequestParam(value = “status", required = true) String callStatus) {
}
Both the annotations behave exactly in same manner.
Only 2 special characters '!' and '#' are accepted by the annotations #PathVariable and #RequestParam.
To check and confirm the behavior I have created a spring boot application that contains only 1 controller.
#RestController
public class Controller
{
#GetMapping("/pvar/{pdata}")
public #ResponseBody String testPathVariable(#PathVariable(name="pdata") String pathdata)
{
return pathdata;
}
#GetMapping("/rpvar")
public #ResponseBody String testRequestParam(#RequestParam("param") String paramdata)
{
return paramdata;
}
}
Hitting following Requests I got the same response:
localhost:7000/pvar/!##$%^&*()_+-=[]{}|;':",./<>?
localhost:7000/rpvar?param=!##$%^&*()_+-=[]{}|;':",./<>?
!# was received as response in both the requests
#RequestParam:We can say it is query param like a key value pair
#PathVariable:-It is came from URI

Spring Rest Controller, Path Variables on an overriden method's arguement

I have a controller annotated with #RestController and it implements an interface:
public interface ContratEndpoint {
String ROOT = "/api/contrats";
String GET_CONTRAT = "";
String GET_CONTRAT_PER_PK = "/{idContrat}";
#RequestMapping(value = GET_CONTRAT)
Contrat getContrat(#RequestParam(value = "contratId")Long contratId);
#RequestMapping(value = GET_CONTRAT_PER_ID)
ExtContrat getContratById(#PathVariable("idContrat") Long idContrat);
}
The controller:
#RestController
#RequestMapping(value = ContratEndpoint.ROOT)
public class ContratController implements ContratEndpoint {
//Injecting Services....
#Resource
private Mapper mapper;
#Override
public Contrat getContrat(Long contratId) {
return mapper.map(contratService.get(contratId),Contrat.class);
}
#Override
public ExtContrat getContratById(#PathVariable("idContrat") Long idContrat){
Preconditions.checkArgument(idContrat !=null);
return mapper.map(contratService.get(idContrat),ExtContrat.class);
}
.The above Code works just fine.
. But For the first inherited method , I didn't have to annotate arguments with #RequestParam and it worked just fine.
As for the second method I tried at first :
#Override
public ExtContrat getContratById(Long idContrat){
Preconditions.checkArgument(idContrat !=null);
return mapper.map(contratService.get(idContrat),ExtContrat.class);
}
. I expected the same behaviour Like the first Method, But i was wrong and the code ended up firing an IllegalArgumentException because of the check in ligne Preconditions.checkArgument(idContrat!=null).
My question is what is so specific about #PathVariable that i've missed ?
Or is it just something is wrong with my approach?
Thanks.
There is difference between Request param and path variable,seee below post that you can confirm with your uri the cause for the exception :
#PathVariable is to obtain some placeholder from the uri (Spring call it an URI Template) — see Spring Reference Chapter 16.3.2.2 URI Template Patterns
#RequestParam is to obtain an parameter — see Spring Reference Chapter 16.3.3.3 Binding request parameters to method parameters with #RequestParam
Assume this Url http://localhost:8080/SomeApp/user/1234/invoices?date=12-05-2013 (to get the invoices for user 1234 for today)
#RequestMapping(value="/user/{userId}/invoices", method = RequestMethod.GET)
public List<Invoice> listUsersInvoices(
#PathVariable("userId") int user,
#RequestParam(value = "date", required = false) Date dateOrNull) {
...
}

#RequestParam in Spring MVC handling optional parameters

Is it possible for a Spring controller to handle both kind of requests?
1) http://localhost:8080/submit/id/ID123432?logout=true
2) http://localhost:8080/submit/id/ID123432?name=sam&password=543432
If I define a single controller of the kind:
#RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,
produces="text/xml")
public String showLoginWindow(#PathVariable("id") String id,
#RequestParam(value = "logout", required = false) String logout,
#RequestParam("name") String username,
#RequestParam("password") String password,
#ModelAttribute("submitModel") SubmitModel model,
BindingResult errors) throws LoginException {...}
the HTTP request with "logout" is not accepted.
If I define two controllers to handle each request separately, Spring complains with the exception "There is already 'Controller' bean method ... mapped".
Before Java 8 and Spring 5 (but works with Java 8+ and Spring 5+ too)
You need to give required = false for name and password request parameters as well. That's because, when you provide just the logout parameter, it actually expects for name and password because they are still "implicitly" mandatory.
It worked when you just gave name and password because logout wasn't a mandatory parameter thanks to required = false already given for logout.
Update for Java 8 and Spring 5 (and above)
You can now use the Optional class from Java 8 onwards to make the parameters optional.
#RequestMapping (value = "/path", method = RequestMethod.GET)
public String handleRequest(#RequestParam("paramName") Optional<String> variableName) {
String paramValue = variableName.orElse("");
// use the paramValue
}
As part of Spring 4.1.1 onwards you now have full support of Java 8 Optional (original ticket) therefore in your example both requests will go via your single mapping endpoint as long as you replace required=false with Optional for your 3 params logout, name, password:
#RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,
produces="text/xml")
public String showLoginWindow(#PathVariable("id") String id,
#RequestParam(value = "logout") Optional<String> logout,
#RequestParam("name") Optional<String> username,
#RequestParam("password") Optional<String> password,
#ModelAttribute("submitModel") SubmitModel model,
BindingResult errors) throws LoginException {...}
Create 2 methods which handle the cases. You can instruct the #RequestMapping annotation to take into account certain parameters whilst mapping the request. That way you can nicely split this into 2 methods.
#RequestMapping (value="/submit/id/{id}", method=RequestMethod.GET,
produces="text/xml", params={"logout"})
public String handleLogout(#PathVariable("id") String id,
#RequestParam("logout") String logout) { ... }
#RequestMapping (value="/submit/id/{id}", method=RequestMethod.GET,
produces="text/xml", params={"name", "password"})
public String handleLogin(#PathVariable("id") String id, #RequestParam("name")
String username, #RequestParam("password") String password,
#ModelAttribute("submitModel") SubmitModel model, BindingResult errors)
throws LoginException {...}
In case someone is looking for mapping Optional parameters with Pojo, same can be done like below.
#RequestMapping (value = "/submit/id/{id}", method = RequestMethod.GET,
produces="text/xml")
public String showLoginWindow(#PathVariable("id") String id,
LoginRequest loginRequest,
#ModelAttribute("submitModel") SubmitModel model,
BindingResult errors) throws LoginException {...}
#Data
#NoArgsConstructor
//#AllArgsConstructor - Don't use this
public class LoginRequest {
private Optional<String> logout = Optional.empty();
private Optional<String> username = Optional.empty();
private Optional<String> password = Optional.empty();
}
Note: Do not use #AllArgsConstructor on POJO else it will initialize the fields as null.

Categories

Resources