1 - 1.初体验

Spring5就引入了Webflux,基于响应式编程的web框架,号称相比较于传统的SpringMVC性能更加(当然我也没测过,官方以及很多用过的小伙伴都持有这个观点),近年来响应式编程越来越主流了,作为一个紧跟时代潮流的小伙,有必要深入学习一下了

本篇作为Webflux系列教程的开篇,一个hello world的体验版

I. 环境

选择SpringBoot 2.2.1.RELEASE来搭建项目环境

pom依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

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

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </pluginManagement>
</build>
<repositories>
    <repository>
        <id>spring-snapshots</id>
        <name>Spring Snapshots</name>
        <url>https://repo.spring.io/libs-snapshot-local</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/libs-milestone-local</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
    <repository>
        <id>spring-releases</id>
        <name>Spring Releases</name>
        <url>https://repo.spring.io/libs-release-local</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

webflux默认开启的端口号也是8080, 如果需要修改,和SpringMVC的配置一样,如修改配置文件application.yml

server:
  port: 8080

II. WebFlux体验

使用WebFlux来提供http服务,如果是熟悉SpringMVC这一套玩法的话,基本上只要一点点改动即可

1. SpringMVC写法

借助@Controller, @RequestMapping注解来实现rest接口

@RestController
@RequestMapping(path = "base")
public class BasicAction {

    @GetMapping(path = "hello")
    public Mono<String> sayHello(@RequestParam("name") String name) {
        return Mono.just("hello " + name);
    }


    @GetMapping(path = "loop")
    public Flux<ServerSentEvent<String>> everySayHello(@RequestParam("name") String name) {
        return Flux.interval(Duration.ofSeconds(1)).map(seed -> seed + seed)
                .map(s -> ServerSentEvent.<String>builder().event("rand").data(name + "|" + s).build());
    }

}

上面提供了两个接口,请注意返回值

  • Mono<String>: Mono 表示返回0到1个数据
  • Flux<ServerSentEvent<String>>: Flux 表示返回0到n个数据

其次对于第二个接口everySayHello,它实现了SSE的功能,每1s往客户端推送一个字符串

关于sse,推荐查看【WEB系列】异步请求知识点与使用姿势小结 了解基本知识点;查看 【WEB系列】SSE服务器发送事件详解 查看SpringMVC的用法,两相对比获取更好的体验

接下来演示一下测试结果

演示

2. 函数开发模式

除了上面的写法之外,Webflux还可以通过配置Router来指定url与function之间的匹配关系,在这种写法中,我们常用的Controller被称为Handler;使用RouterFunction配置路由

重写一下上面的两个功能

@Component
public class ShowAction {

    public Mono<ServerResponse> hello(ServerRequest serverRequest) {
        return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
                .body(Mono.just("hello " + serverRequest.queryParam("name").orElse("NoBody")), String.class);
    }
 
    /**
     * sse 服务端推送模式, 每隔一秒向客户端推送一次数据
     *
     * @param serverRequest
     * @return
     */
    public Mono<ServerResponse> sendTimePerSec(ServerRequest serverRequest) {
        return ok().contentType(MediaType.TEXT_EVENT_STREAM).body(Flux.interval(Duration.ofSeconds(1))
                .map(l -> new SimpleDateFormat("HH:mm:ss").format(new Date())), String.class);
    }
}

请注意,上面的写法并没有表明什么url匹配这个方法,所以我们需要额外的配置

@Configuration
public class RouterConfig {
    @Autowired
    private ShowAction showAction;

    @Bean
    public RouterFunction<ServerResponse> timerRouter() {
        return RouterFunctions
                .route(RequestPredicates.GET("/hello"), showAction::hello)
                .andRoute(RequestPredicates.GET("/times"), showAction::sendTimePerSec);
    }
}

再次测试

演示

3. 小结

本文主要属于webfluxhello world篇,主要目的就是先对这个有一点熟悉和了解,函数式编程模式和我们一把的开发方式还是有一些区别的,这些会在后续的系列教程中逐步展开

下一篇将主要介绍WebFlux中的两个重要的类Mono,Flux是什么,怎么用(恳请持续关注😊😊😊)

II. 其他

0. 项目

2 - 2.静态资源配置与访问

上一篇博文介绍SpringMVC的静态资源访问,那么在WebFlux中,静态资源的访问姿势是否一致呢

I. 默认配置

与SpringBoot的默认配置一样,WebFlux同样是classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/

即,将静态文件放在这四个目录下,可以直接访问

1. 项目演示

创建一个SpringBoot项目,添加依赖(本文使用的版本为: 2.2.1-RELEASE)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

在资源路径下添加目录 static,目录下添加两个html文件,如下图

实现启动类,不添加额外逻辑,既可以直接通过完整url方式访问静态资源

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

主要观察上面三个请求,放在index.html是无法直接访问到的,因为它所在的目录并不在默认的四个静态资源路径中

2. Url映射

上面是直接通过静态资源文件名的方式进行访问,那么WebFlux是否可以实现SpringMVC那种,根据视图名返回View的方式呢?

@Controller
public class ViewAction {
    @GetMapping(path = "a")
    public String a() {
        return "a.html";
    }
}

直接访问,结果发现500,找不到名为a.html的视图

这种方式不行的话,改用WebFlux的路由写法

@Bean
public RouterFunction<ServerResponse> indexRouter() {
    return RouterFunctions.route(RequestPredicates.GET("/b"),
                    request -> ServerResponse.ok().contentType(MediaType.TEXT_HTML).bodyValue("b.html");
}

II. 自定义配置路径

如果我们希望指定一个自定义的路径,是否可以如SpringMvc那样,修改配置or代码设置映射完成呢?

在资源目录下,新加两个文件夹,分别是 o1, o2

1. 配置修改

如SpringMVC,修改静态资源配置

spring:
  resources:
    static-locations: classpath:/o1/,classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/

然后访问 /o1.html,发现404,这种直接修改配置方式不行!!!

2. WebFluxConfigurer添加映射

参考自官方文档: web-reactive.html#webflux-config-static-resources

直接修改启动类,实现WebFluxConfigurer接口,手动添加资源映射

@SpringBootApplication
public class Application implements WebFluxConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/o2/");
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

接着访问 /o2.html

3. @Value方式

除了上述手动映射的方式之外,还有一种非主流的是方式,如

@Bean
public RouterFunction<ServerResponse> indexRouter(@Value("classpath:/index.html") final Resource indexHtml,
        @Value("classpath:/self/s.html") final Resource sHtml) {
    return RouterFunctions.route(RequestPredicates.GET("/index"),
            request -> ServerResponse.ok().contentType(MediaType.TEXT_HTML).bodyValue(indexHtml))
            .andRoute(RequestPredicates.GET("/s"),
                    request -> ServerResponse.ok().contentType(MediaType.TEXT_HTML).bodyValue(sHtml));
}

请注意上面的两个文件, s.html, index.html都不在默认的静态资源目录下

III. 小结

文中给出了WebFlux的静态资源访问姿势,与SpringMVC有一些区别

  • url映射时,直接返回视图名,会提示Could not resolve view with name xxx
  • 通过修改配置spring.resources.static-locations 指定新的静态资源目录无效

在WebFlux中,推荐使用实现WebFluxConfigure接口的方式,重写addResourceHandlers方法来自定义资源路径映射

也可以针对单独的静态资源,借助@Value来手动路由

II. 其他

0. 项目

3 - 3.Path参数解析与url映射

异步、反应式、函数式编程,近来可以说是逐渐主流了;Spring5通过Reactor增加了对反应式编程的支持,而Spring WebFlux不同于以往的web框架,作为一个非阻塞异步web框架,可以充分的利用多核CPU硬件资源,提供更强的并发支持;Spring官方对WebFlux的支持非常友好,基本上对于惯于Spring WEB的java开发者,可以很简单的迁移过来

接下来我们将进入WebFlux系列教程,努力使用最简明的语言,来介绍一下WebFlux的基本玩法,让各位小伙伴可以顺畅的切换和使用WebFlux来体验反应式编程的魅力

本文将主要介绍WebFlux提供web接口时的url匹配,以及对应的path参数解析

I. 项目环境

本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

1. 依赖

使用WebFlux,最主要的引入依赖如下(省略掉了SpringBoot的相关依赖,如对于如何创建SpringBoot项目不太清楚的小伙伴,可以关注一下我之前的博文)

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

II. Path匹配与参数解析

下面所有内容基于官方文档完成: https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-ann-requestmapping-uri-templates

下面的示例主要是基于注解的方式,基本知识点和SpringWeb没有太大的区别(至于函数式的用法,后面会专门介绍)

1. 基本path参数获取

path参数,举例如: http://127.0.0.1:8080/name/testnametest就算是path参数,我们主要是借助@PathVariable来获取

一个具体实例

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

    /**
     * 最基本的path获取方式
     *
     * @param index
     * @return
     */
    @GetMapping(path = "/basic/{index}")
    public Mono<String> basic(@PathVariable(name = "index") int index) {
        return Mono.just("path index: " + index);
    }
}

针对上面的case,我们简单的设计了三个访问case,具体结果如下

➜  ~ 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":"2020-08-26T13:35:26.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":"2020-08-26T13:35:32.196+0000","path":"/path/basic/","status":404,"error":"Not Found","message":null,"requestId":"eeda1111"}%

请注意上面的输出,/basic/{index} 只能匹配单级的path路径参数,而且上面的写法中,这级path路径必须存在

查看PathVariable注解可以看到里面有一个required属性,如果设置为false,会怎样呢

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

测试case如下

➜  ~ curl 'http://127.0.0.1:8080/path/basic2/'
{"timestamp":"2020-08-26T13: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":"2020-08-26T13:41:44.400+0000","path":"/path/basic2/22/3","status":404,"error":"Not Found","message":null,"requestId":"0b3f173c"}%

从上面的实际case,也可以看出来,级别这个属性设置为false,但是url路径依然需要正确匹配,多一级和少一级都不行

2. 多path参数

上面只有一个path参数,如果有多个参数,也比较简单

/**
 * 多个参数的场景
 *
 * @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);
}

测试case如下

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

3. 部分path参数匹配

上面的两个case,都是完整的匹配某一级路径,下面介绍部分匹配的case

/**
 * 路径中的部分内容匹配
 *
 * - /part/test.txt -> name = test
 * - /part/a/test.txt -> 不匹配
 *
 * @param name
 * @return
 */
@GetMapping(path = "/part/{name}.txt")
public Mono<String> part(@PathVariable(name = "name") String name) {
    return Mono.just("part path argument: " + name);
}

请注意上面的path路径,后缀是.txt,如下面的实例中part/hello.txt中那么对应的就是hello

➜  ~ 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":"2020-08-26T13:47:49.121+0000","path":"/path/part/hello.tx","status":404,"error":"Not Found","message":null,"requestId":"1075d683"}%

4. 正则匹配

接下来更高端的path参数匹配来了,支持一些简单的正则,如我们希望对spring-web-3.0.5.jar这段path路径进行解析,希望将spring-web作为name, 3.0.5作为version.jar作为ext

因此我们的rest接口写法可以如下

/**
 * 正则匹配
 *
 * /path/path/pattern/spring-web-3.0.5.jar  -> name = spring-web,  version=3.0.5,  ext=.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);
}

5. 多级path参数匹配

注意上面的所有写法,都有一个特点,那就是只能针对单级的path路径进行全/部分匹配(本文中将path路径中//之间作为一级),那么如果我希望我的path参数可以匹配多级,可以怎么办

  • /path/name/hello 请求路径中,我希望将 /name/hello 作为一个path参数

针对上面的场景,我们主要是借助{*name}方式来处理,注意这个参数名前面的*号

/**
 * 匹配:
 *
 * - /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);
}

测试case如下

➜  ~ 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%

6. 路径匹配

前面介绍的是path参数解析,接下来我们简单的看一下最常见的三种路径匹配方式

a. *

一个星号,表示匹配0个or1个单级path路径

/**
 * 单个*号,只能匹配一级目录,注意这种方式与上面的 pattern2 之间的区别
 *
 * 可以匹配:
 *
 * - /path/pattern3/hello
 * - /path/pattern3
 *
 * 不能匹配
 *
 * - /path/pattern3/hello/1
 *
 * @return
 */
@GetMapping(path = "/pattern3/*")
public Mono<String> pattern3() {
    return Mono.just("pattern3 succeed!");
}

实测case如下

# 请注意,这里是没有/结尾的
➜  ~ curl 'http://127.0.0.1:8080/path/pattern3'
{"timestamp":"2020-08-27T00: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":"2020-08-27T00:01:18.144+0000","path":"/path/pattern3/a/b","status":404,"error":"Not Found","message":null,"requestId":"203dc7d4"}%

请注意上面的实例,/path/pattern3 访问404, 而/path/pattern3/是可以的,唯一的区别就是多了一个后缀/

  • why?
  • 是因为path路径的星号前面有一个/导致的么?

接下来我们再设计一个case,将*前面的/干掉,再测试一下

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

再次测试,结果如下

➜  ~ curl 'http://127.0.0.1:8080/path/pattern3311'
pattern33 succeed!%

➜  ~ curl 'http://127.0.0.1:8080/path/pattern33/11'
{"timestamp":"2020-08-27T00: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!%

借助前面两个case,我们基本上可以看出*的作用

  • *前面的完全匹配
    • 比如/pattern3/*,那么访问的path路径前缀必须是/pattern3/
  • *最多表示单级路径,简单来讲就是*所代表的的位置中不能出现/x
    • 比如/pattern33**,那么/pattern331/可以匹配,但是/pattern331/1不能

b. **

有别与上面的单个*匹配0-1级path路径,两个**则表示可以一直匹配到最后一层

/**
 * 对于 pattern4开头的都可以匹配
 *
 * @return
 */
@GetMapping(path = "/pattern4/**")
public Mono<String> pattern4() {
    return Mono.just("pattern4 succeed!");
}

测试case如下

➜  ~ 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!%

请注意

  • 直接访问/pattern4也是可以命中的,这个和上面是有区别的

c. ?

单个字符的通配,比较简单如下

/**
 * 匹配  pattern5/test   pattern5/tast ...
 * 不匹配 pattern5/tst pattern5/tesst
 *
 * @return
 */
@GetMapping(path = "/pattern5/t?st")
public Mono<String> pattern5() {
    return Mono.just("pattern5 succeed!");
}

访问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":"2020-08-27T00: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":"2020-08-27T00:14:01.078+0000","path":"/path/pattern5/tst","status":404,"error":"Not Found","message":null,"requestId":"b2691121"}%

从上面的测试输出也可以看出

  • ? 对应的地方不能是/以及其他不被支持的字符(如?,',", %等)
  • ? 对应的地方必须存在

7. 小结

虽然本文的主题是webflux中path参数解析与url映射匹配,但是看下来我们会神奇的发现,这些知识点和SpringMVC中,貌似也没有什么区别,事实上也确实如此;对于注解的使用场景时,绝大多数,都是之前怎么玩,现在依然可以怎么玩

下面用一个表格针对上面的知识点进行汇总

pattern 描述 举例
? 匹配一个字符 pages/t?st.html 匹配 /pages/test.html and /pages/t3st.html
* 匹配单级path路径中0-多个字符 "/resources/*.png" matches "/resources/file.png"
"/projects/*/versions" matches "/projects/spring/versions" but does not match "/projects/spring/boot/versions"
** 匹配0-多个path路径 "/resources/**" matches "/resources/file.png" and "/resources/images/file.png"
"/resources/**/file.png" 这种写法是非法的
{name} 匹配单级path路径参数 "/projects/{project}/versions" matches "/projects/spring/versions" and captures project=spring
{name:[a-z]+} 正则 "/projects/{project:[a-z]+}/versions" matches "/projects/spring/versions" but not "/projects/spring1/versions"
{*path} 匹配path路径中,0-最后一级path路径参数 "/resources/{*file}" matches "/resources/images/file.png" and captures file=images/file.png

II. 其他

0. 项目

4 - 4.header参数解析

上一篇weblfux主要介绍了path参数的解析与映射关系,在我们进入url参数/post表单之前,先看一下另外的一种参数–请求头中的参数如何处理

I. 项目环境

本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

1. 依赖

使用WebFlux,最主要的引入依赖如下(省略掉了SpringBoot的相关依赖,如对于如何创建SpringBoot项目不太清楚的小伙伴,可以关注一下我之前的博文)

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

II. 请求头参数解析

在实际的业务开发中,有几个请求头出现的频率特别高,如常用于反爬的User-Agent,鉴定强求来源的referer,跨域相关的Access-Control-Allow-,cookie、session自定义的请求头等

1. 请求头限制

RequestMappingGetMapping中指定请求头参数时,表示只有请求中包含这个请求头才会匹配过去

/**
 * 只有请求头包含 myheader 且值为 myvalue的才可以访问到
 *
 * - 正常访问: curl 'http://127.0.0.1:8080/header/filter/yihhui' -H 'myheader: myvalue'
 * - 异常访问: curl 'http://127.0.0.1:8080/header/filter/yihhui' -H 'myheader: myvalue2'  因为请求头不匹配,404
 *
 * @param name
 * @return
 */
@GetMapping(path = "/filter/{name}", headers = "myheader=myvalue")
public Mono<String> headerFilter(@PathVariable(name = "name") String name) {
    return Mono.just("request filter: " + name);
}

实例如下:

➜  ~ curl 'http://127.0.0.1:8080/header/filter/yihhui' -H 'myheader: myvalue'
request filter: yihhui%

➜  ~ curl 'http://127.0.0.1:8080/header/filter/yihhui' -H 'myheader: myvalue2'
{"timestamp":"2020-09-07T00:40:34.493+0000","path":"/header/filter/yihhui","status":404,"error":"Not Found","message":null,"requestId":"aa47f5a5"}%   

2. 请求头参数解析

WebFlux依然是可以通过注解@RequestHeader来获取对应的请求头

从使用姿势上来看,webflux与webmvc并没有什么区别

/**
 * 获取请求头
 *
 * curl 'http://127.0.0.1:8080/header/get' -H 'myheader: myvalue' -H 'user-agent: xxxxxxx'
 *
 * @param header  注意,这个是自定义的请求头
 * @param userAgent
 * @return
 */
@GetMapping(path = "get")
public Mono<String> getHeader(@RequestHeader("myheader") String header,
        @RequestHeader("user-agent") String userAgent) {
    return Mono.just("request headers: myheader=" + header + " userAgent=" + userAgent);
}

测试case如下

➜  ~ curl 'http://127.0.0.1:8080/header/get' -H 'myheader: myvalue' -H 'user-agent: xxxxxxx'
request headers: myheader=myvalue userAgent=xxxxxxx%  

3. cookie获取

利用cookie来标识用户身份可以说是非常普遍的场景了,我们通过专用的CookieValue来获取指定的cookies值

/**
 * 获取cookie
 *
 * curl 'http://127.0.0.1:8080/header/cookie' --cookie 'tid=12343123;tt=abc123def'
 *
 * @param tid
 * @return
 */
@GetMapping(path = "cookie")
public Mono<String> getCookie(@CookieValue("tid") String tid) {
    return Mono.just("request cookies tid=" + tid);
}

上面的case中,标识只需要获取tid这个cookies值,其他的不care

➜  ~ curl 'http://127.0.0.1:8080/header/cookie' --cookie 'tid=12343123;tt=abc123def'
request cookies tid=12343123% 

II. 其他

0. 项目

系列博文