SpringBoot WebFlux series WebFlux Path parameter parsing and URL mapping

Asynchronous, reactive, functional programming, can be said to be the mainstream of late; Spring5 adds support for reactive programming through Reactor, and Spring WebFlux is different from previous Web frameworks. As a non-blocking asynchronous Web framework, Spring WebFlux can make full use of multi-core CPU hardware resources and provide stronger concurrency support. Spring’s official support for WebFlux is very friendly and basically easy to migrate to for Java developers who are used to Spring WEB

Next, we will enter the WebFlux series tutorial, trying to use the most concise language, to introduce the basic gameplay of WebFlux, so that you can smoothly switch and use WebFlux to experience the charm of reactive programming

This article mainly introduces url matching and path parameter resolution when WebFlux provides web interfaces

I. Project environment

This project is developed by SpringBoot 2.2.1.RELEASE + Maven 3.5.3 + IDEA

1. Rely on

With WebFlux, the main imported dependencies are as follows (omitted from SpringBoot dependencies). If you are not sure how to create a SpringBoot project, please refer to my previous post).

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
</dependencies>
Copy the code

II. Path matching and parameter parsing

All the following content based on the official documentation completed: docs. Spring. IO/spring/docs…

The following example is primarily annotation-based, and the basics are not that different from SpringWeb (functional usage will be covered later)

1. Obtain the basic path parameter

Path parameters, for example: http://127.0.0.1:8080/name/test in the name and the test even path parameters, we mainly use the @ PathVariable for

A concrete example

@RestController
@RequestMapping(path = "path")
public class PathAction {

    /** * The most basic way to obtain path **@param index
     * @return* /
    @GetMapping(path = "/basic/{index}")
    public Mono<String> basic(@PathVariable(name = "index") int index) {
        return Mono.just("path index: "+ index); }}Copy the code

For the above case, we simply designed three access cases, and the specific results are as follows

➜  ~ curl 'http://127.0.0.1:8080/path/basic/1'Path index: 1% ➜ ~ curl'http://127.0.0.1:8080/path/basic/1/2'
{"timestamp":"The 2020-08-26 T13: humiliated. 221 + 0000"."path":"/path/basic/1/2"."status": 404,"error":"Not Found"."message":null,"requestId":"8256bf73"➜} % ~ curl'http://127.0.0.1:8080/path/basic/'
{"timestamp":"The 2020-08-26 T13: has 196 + 0000"."path":"/path/basic/"."status": 404,"error":"Not Found"."message":null,"requestId":"eeda1111"} %Copy the code

Note that /basic/{index} can only match a single level of path parameters, and this level of path must exist

If you look at the PathVariable annotation, you can see that there is a required attribute in it. What happens if it is set to false

@GetMapping(path = "/basic2/{index}")
public Mono<String> basic2(@PathVariable(name = "index", required = false) Integer index) {
    return Mono.just("basic2 index: " + index);
}
Copy the code

The test case is as follows

➜  ~ curl 'http://127.0.0.1:8080/path/basic2/'
{"timestamp":"The 2020-08-26 T13:41:40. 100 + 0000"."path":"/path/basic2/"."status": 404,"error":"Not Found"."message":null,"requestId":"b2729e2c"➜} % ~ curl'http://127.0.0.1:8080/path/basic2/22'Basic2 index: 22% ➜ ~ curl'http://127.0.0.1:8080/path/basic2/22/3'
{"timestamp":"The 2020-08-26 T13:41:44. 400 + 0000"."path":"/path/basic2/22/3"."status": 404,"error":"Not Found"."message":null,"requestId":"0b3f173c"} %Copy the code

As can be seen from the above case, the level property is set to false, but the URL path still needs to match correctly, either one level more or one level less

2. Multiple path parameters

There is only one path parameter above, and it is easier to have multiple parameters

/** * Multiple parameter scenarios **@param index
 * @param order
 * @return* /
@GetMapping(path = "/mbasic/{index}/{order}")
public Mono<String> mbasic(@PathVariable(name = "index") int index, @PathVariable(name = "order") String order) {
    return Mono.just("mpath arguments: " + index + "|" + order);
}
Copy the code

The test case is as follows

➜  ~ curl 'http://127.0.0.1:8080/path/mbasic/1/asc'
mpath arguments: 1 | asc%
Copy the code

3. Some path parameters match

The above two cases are complete matching of a level 1 path. The following are partial matching cases

TXT -> name = test * - /part/a/test. TXT -> Do not match **@param name
 * @return* /
@GetMapping(path = "/part/{name}.txt")
public Mono<String> part(@PathVariable(name = "name") String name) {
    return Mono.just("part path argument: " + name);
}
Copy the code

Note that the path suffix is.txt, which corresponds to Hello in part/hello.txt in the following example

➜  ~ curl 'http://127.0.0.1:8080/path/part/hello.txt'Part path argument: hello% ➜ ~ curl'http://127.0.0.1:8080/path/part/hello.tx'
{"timestamp":"The 2020-08-26 T13:47:49. 121 + 0000"."path":"/path/part/hello.tx"."status": 404,"error":"Not Found"."message":null,"requestId":"1075d683"} %Copy the code

4. Regular matching

Next comes the higher-end path parameter matching, which supports some simple regex. For example, we want to parse the spring-web-3.0.5.jar path, and we want spring-web as name, 3.0.5 as version, and.jar as ext

So our REST interface could be written as follows

/ * * * * * regular matching/path/path/pattern/spring - web - 3.0.5. Jar - > name = spring - web, version = 3.0.5, ext. = the jar * *@return* /
@GetMapping(path = "/pattern/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public Mono<String> urlPattern(@PathVariable(name = "name") String name,
        @PathVariable(name = "version") String version, @PathVariable(name = "ext") String ext) {
    return Mono.just("pattern arguments name=" + name + " version=" + version + " ext=" + ext);
}
Copy the code

5. Multiple paths are matched

If I want my path parameter to match more than one level, what can I do if I want my path parameter to match more than one level

  • Such as/path/name/helloIn the request path, I want to be/name/helloAs a path parameter

For the above scenario, we mainly use {*name}. Note the * before the parameter name

/** * Match: * * - /path/pattern2 -> name == "" * - /path/pattern2/hello -> name == /hello * - /path/pattern2/test/hello -> name = /test/hello * *@param name
 * @return* /
@GetMapping(path = "/pattern2/{*name}")
public Mono<String> pattern2(@PathVariable(name = "name") String name) {
    return Mono.just("pattern2 argument: " + name);
}
Copy the code

The test case is as follows

➜  ~ curl 'http://127.0.0.1:8080/path/pattern2'Pattern2 argument: % ➜ ~ curl'http://127.0.0.1:8080/path/pattern2/hello'Pattern2 argument: /hello% ➜ ~ curl'http://127.0.0.1:8080/path/pattern2/hello/world'
pattern2 argument: /hello/world%
Copy the code

6. Paths match

Following the path parameter resolution, let’s briefly take a look at the three most common path matching methods

a. *

An asterisk (asterisk) indicates that 0 or1 single-level path paths are matched

/** * Single *, matches only level 1 directory, note the difference between this approach and pattern2 above ** matches: * * - /path/pattern3/hello * - /path/pattern3 * * Does not match * * - /path/pattern3/hello/1 * *@return* /
@GetMapping(path = "/pattern3/*")
public Mono<String> pattern3(a) {
    return Mono.just("pattern3 succeed!");
}
Copy the code

The measured case is as follows

# Note that there is no/ending here
➜  ~ curl 'http://127.0.0.1:8080/path/pattern3'
{"timestamp":"The 2020-08-27 T00:01:20. 703 + 0000"."path":"/path/pattern3"."status": 404,"error":"Not Found"."message":null,"requestId":"c88f5066"➜} % ~ curl'http://127.0.0.1:8080/path/pattern3/'pattern3 succeed! % ➜ ~ curl'http://127.0.0.1:8080/path/pattern3/a'pattern3 succeed! % ➜ ~ curl'http://127.0.0.1:8080/path/pattern3/a/b'
{"timestamp":"The 2020-08-27 T00:01:18. 144 + 0000"."path":"/path/pattern3/a/b"."status": 404,"error":"Not Found"."message":null,"requestId":"203dc7d4"} %Copy the code

Note that in the example above, /path/pattern3 visits 404, while /path/pattern3/ is ok, the only difference being that there is an extra suffix /

  • why?
  • Because path has an asterisk before the path/Cause?

Next, we will design a case that kills the/in front of * and test it again

@GetMapping(path = "/pattern33**")
public Mono<String> pattern33(a) {
    return Mono.just("pattern33 succeed!");
}
Copy the code

Again, the results are as follows

➜  ~ curl 'http://127.0.0.1:8080/path/pattern3311'pattern33 succeed! % ➜ ~ curl'http://127.0.0.1:8080/path/pattern33/11'
{"timestamp":"The 2020-08-27 T00:05:51. 236 + 0000"."path":"/path/pattern33/11"."status": 404,"error":"Not Found"."message":null,"requestId":"d8cbd546"➜} % ~ curl'http://127.0.0.1:8080/path/pattern33'pattern33 succeed! % ➜ ~ curl'http://127.0.0.1:8080/path/pattern331/'pattern33 succeed! %Copy the code

Based on the previous two cases, we can basically see the function of *

  • *The previous one is a perfect match
    • Such as/pattern3/*, then the path path prefix for access must be/pattern3/
  • *At most, it represents a single level path*Cannot appear in the position represented by/x
    • Such as/pattern33**, then/pattern331/It can match, but/pattern331/1Can’t

b. **

A single * matches a level 0-1 path, and two ** matches all the way to the last level

/** * matches ** for those beginning with Pattern4@return* /
@GetMapping(path = "/pattern4/**")
public Mono<String> pattern4(a) {
    return Mono.just("pattern4 succeed!");
}
Copy the code

The test case is as follows

➜  ~ curl 'http://127.0.0.1:8080/path/pattern4'pattern4 succeed! % ➜ ~ curl'http://127.0.0.1:8080/path/pattern4/12'pattern4 succeed! % ➜ ~ curl'http://127.0.0.1:8080/path/pattern4/12/3'pattern4 succeed! %Copy the code

Please note that

  • Direct access to the/pattern4It can be hit. There’s a difference between this and the top

c. ?

The wildcard of a single character is as follows

/** * Match pattern5/test pattern5/tast... * Does not match pattern5/ TST Pattern5 /tesst * *@return* /
@GetMapping(path = "/pattern5/t? st")
public Mono<String> pattern5(a) {
    return Mono.just("pattern5 succeed!");
}
Copy the code

Access to the case

➜  ~ curl 'http://127.0.0.1:8080/path/pattern5/test'pattern5 succeed! % ➜ ~ curl'http://127.0.0.1:8080/path/pattern5/t/st'
{"timestamp":"The 2020-08-27 T00:13:42. 557 + 0000"."path":"/path/pattern5/t/st"."status": 404,"error":"Not Found"."message":null,"requestId":"add34639"➜} % ~ curl'http://127.0.0.1:8080/path/pattern5/tst'
{"timestamp":"The 2020-08-27 T00:14:01. 078 + 0000"."path":"/path/pattern5/tst"."status": 404,"error":"Not Found"."message":null,"requestId":"b2691121"} %Copy the code

You can also see this from the test output above

  • ?The corresponding place cannot be/And other unsupported characters (e.g?.'.".%Etc.)
  • ?A corresponding place must exist

7. Summary

Although the topic of this article is path parameter resolution and URL mapping matching in WebFlux, it is amazing to find that these knowledge points seem to be no different from SpringMVC, and in fact they are; The usage scenarios for annotations, for the most part, are how can they be played before and how can they still be played now

Here is a table to summarize the above points

pattern describe For example,
? Match a character pages/t? st.htmlmatching/pages/test.html and /pages/t3st.html
* Matches 0 to more than one character in a single path "/resources/*.png" matches "/resources/file.png"

"/projects/*/versions" matches "/projects/spring/versions" but does not match "/projects/spring/boot/versions"
六四屠杀 Match 0- multiple paths "/resources/**" matches "/resources/file.png" and "/resources/images/file.png"

"/resources/**/file.png"This writing is illegal
{name} Matches single-level path path parameters "/projects/{project}/versions" matches "/projects/spring/versions" and captures project=spring
{name:[a-z]+} regular "/projects/{project:[a-z]+}/versions" matches "/projects/spring/versions" but not "/projects/spring1/versions"
{*path} Matches the 0- last level path parameter in the path "/resources/{*file}" matches "/resources/images/file.png" and captures file=images/file.png

II. The other

0. Project

  • Project: github.com/liuyueyi/sp…
  • Source: github.com/liuyueyi/sp…

1. An ashy Blog

As far as the letter is not as good, the above content is purely one’s opinion, due to the limited personal ability, it is inevitable that there are omissions and mistakes, if you find bugs or have better suggestions, welcome criticism and correction, don’t hesitate to appreciate

Below a gray personal blog, record all the study and work of the blog, welcome everyone to go to stroll

  • A grey Blog Personal Blog blog.hhui.top
  • A Grey Blog-Spring feature Blog Spring.hhui.top