41

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_@./#&+-]*$/
Jack
  • 6,430
  • 27
  • 80
  • 151
  • What is your spring version ? [Jira SPR-11101](https://jira.spring.io/browse/SPR-11101) is about a problem affecting URLs containing forward slashes fixed in 3.2.8 and 4.0.2 - unsure if related ... – Serge Ballesta Jul 22 '15 at 06:24

7 Answers7

31

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);
SteveFerg
  • 3,466
  • 7
  • 19
  • 31
22

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";
    }
}
Community
  • 1
  • 1
Arpit Aggarwal
  • 27,626
  • 16
  • 90
  • 108
  • how to catch the value of mystring then? – Jack Jul 16 '15 at 03:02
  • It does not work. It seems like because you have /{string:.+} at the beginning of the requestMapping the request will be caught by that therefore won't reach the second request handler. – Jack Jul 16 '15 at 22:38
  • 1
    this one www.example.com/show/abcd/efg?name=alex&family=moore your solution discards abcd/ part – Jack Jul 17 '15 at 08:34
  • 1
    weird, yes I did, did you notice /abcd/efg?name=alex&family=moore is right after show/ ? I mean it should put /abcd/efg in one variable and catch values of name and family. – Jack Jul 17 '15 at 13:22
  • @Jack This is the best solution for your problem i guess. If you want the values to be stored under a single variable name maybe you can concatenate them inside your controller. I'm afraid that is all that can be done. – Anand Jul 21 '15 at 14:27
  • 1
    @Anand The issue is it is discarding the passed value because it is taking "/" as delimiter not part of the provided value. For example if you pass "abcd/efg" it is not taking it as a single value; therefore it would be difficult to know the value that is in the second variable is part of the value of first part or is name of an action. For example: /show/abcd/efg and /show/jack/edit first request should send user to the page that is related to "abcd/efg" (because thats a name) and the second one should redirect user to edit page of "jack" (because edit is name of an action) – Jack Jul 22 '15 at 05:39
  • @Jack, `if you pass "abcd/efg" it is not taking it as a single value;`, you can't take `"abcd/efg"` as a single parameter because you can't have optional path variables. In your scenario you should go with `@RequestParam("action") String action)` changing the url to `/show/jack?action=edit` and acting accordingly :) – Arpit Aggarwal Jul 22 '15 at 06:00
  • @Arpit whats your idea about Daniel's answer? – Jack Jul 22 '15 at 06:15
  • @Jack, I am not having any idea bout this, try it if it works for you, then its good :) – Arpit Aggarwal Jul 22 '15 at 06:37
  • @Jack Well if it involves some set of predefined values, like edit in the above example, to be used at the end you can always go for something like this '@RequestMapping(value = /show/{value}/predefinedSuffix)' to map those url values. This with the above combinations suggested by Arpit, would solve your problem i hope. – Anand Jul 23 '15 at 06:22
  • I have the same problem (after URL encoding as well). I think I will replace HTTP GET/DELETE with HTTP POST and put the value with '/' in the entity body – toongeorges May 24 '17 at 22:32
  • Why would you, in the second controller method, have two path mappings? Shouldn't the first method handle those without additional slash and the second those with additional slash? For me, it only worked when I removed the first path from the second method like so: @RequestMapping(value = "/{string:.+}/{mystring:.+}") – marc82ch Oct 24 '18 at 09:33
  • So is this https://stackoverflow.com/questions/54260546/requestmapping-issue-with-multiple-angular-clients-in-spring-boot-application even possible then? – Stefan Falk Jan 19 '19 at 13:18
  • What if document id has three slashes or 4 or 10 or 12? abc/def/hij/klm As / is a reserved character, then clearly if you are going to use path variable, where the variable can contain / (or any other reserved character) then you need to escape the variable value (/ -> %2F as per Nikitas answer) – dan carter Sep 04 '19 at 02:00
4

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.

Ankur Jain
  • 1,386
  • 3
  • 17
  • 27
  • 1
    @Jack yes, it can handle.. But you better run as I never tried the same with **@PathVariable**, I have hardcoded URL's instead of this. – Ankur Jain Jul 15 '15 at 09:30
  • 1
    It does not work. I passed abcs-fdfrfr-(dfdf)-dfgdf/f-sdff223 bu when I used system.out to print the mystring value its printed f-sdff223 I think it has been caught by first option (/{mystring:.*} – Jack Jul 16 '15 at 03:00
4

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

}
Nikita Kuznetsov
  • 2,527
  • 1
  • 11
  • 12
3

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>
kryger
  • 12,906
  • 8
  • 44
  • 65
Daniel Newtown
  • 2,873
  • 8
  • 30
  • 64
3

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

Master Slave
  • 27,771
  • 4
  • 57
  • 55
  • Can you elaborate on "write a custom path mapper, that would change this logic for the particular handler method". 1) What interface do I need to implement for my custom path mapper? And 2) how do I annotate the method to tell it to use my custom path mapper? – Kevin Pauli Feb 15 '17 at 00:36
2

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

John Coleman
  • 51,337
  • 7
  • 54
  • 119
SKalariya
  • 48
  • 5
  • This really has to be the simplest method, and then un-regex on the server. The above answers really don't cope if you've got an UNKNOWN amount of slashes. As you cannot specify in the rest specification an unknown amount, this really is the only way forward. – PeterS Sep 06 '17 at 09:30