package luck.spring.boot.webflux;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.freemarker.FreeMarkerViewResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebExceptionHandler;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RequestPredicates.queryParam;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;
import static org.springframework.web.reactive.function.server.ServerResponse.status;
@Slf4j
@Configuration
public class RouterFunctionConfig {
// 定义复杂条件的路由,包含前置过滤器,后置过滤器,环绕过滤器
@Bean
public RouterFunction<ServerResponse> nestRouterFunction(RouterHandler handler) {
// 定义路由
RouterFunctions.Builder globalRouterBuilder = route();
return globalRouterBuilder
.GET("/luck", handler::simpleTestApi)
// 以下路由的基础路径
.path("/base/url",
// 定义嵌套路由,该嵌套路由的所有子路由的客户端都需要json类型的数据
baseUrlBuilder -> baseUrlBuilder.GET("/test", accept(APPLICATION_JSON), handler::simpleTestApi)
.nest(accept(APPLICATION_JSON),
// 定义一个子路由,url为"/router/aaa",访问路由为/base/url/router/aaa
// 并且/router/aaa这个路由必须有name=luck这个参数对,否则无法访问
// 匹配之后的处理函数为HandlerFunction,是一个函数式编程
nestBuilder -> nestBuilder.GET("/router/aaa", queryParam("name", "luck"), handler::simpleTestApi)
.GET("/router/bbb", handler::simpleTestApi)
.GET("/router/ccc", handler::simpleTestApi)
// 在同层次的builder(nestBuilder)构建的所有路由的前置拦截器,只会拦截请求,无法处理响应
.before(req -> {
MultiValueMap<String, HttpCookie> cookies = req.exchange().getRequest().getCookies();
if (!cookies.containsKey("cookie_lang")) {
log.error("不包含该cookie,不放行");
return req;
}
log.error("before:{}", req.exchange().getRequest().getURI().getPath());
return req;
}))
// 该过滤器是针对于变量baseUrlBuilder这个builder构建的路由以及子路由进行过滤的一个过滤器
// 注意构建的层级,同一个Builder构建的过滤器,作用于同级别的所有路由以及子路由
// 由此可总结: baseUrlBuilder变量builder构建的路由有/base/url/test,还有嵌套路由nestBuilder变量builder构建的
// 子路由/base/url/router/[aaa,bb,ccc],这些路由都会经过这个过滤器
.filter((request, next) -> {
boolean containsKey = request.queryParams().containsKey("bbb");
if (!containsKey) {
return status(HttpStatus.UNAUTHORIZED).build();
}
// 传递过下一个过滤器执行
return next.handle(request);
})
)
// 在同层次的builder(globalRouterBuilder)构建的所有路由的后置拦截器,执行完目标方法之后的拦截器
.after((req, resp) -> {
log.error("after过滤器,所有路由都会执行,包括嵌套路由");
req.exchange().getResponse().getHeaders().add("x-request-id", "response-request-id");
return resp;
})
// 在同层次的builder(globalRouterBuilder)构建的所有路由的过滤器
.filter((request, next) -> {
log.error("全局过滤器:{}", request.uri().getPath());
// List<String> header = request.headers().header("Authorization");
// if (CollUtil.isEmpty(header)) {
// return status(HttpStatus.UNAUTHORIZED).build();
// }
// 传递过下一个过滤器执行
return next.handle(request);
})
.build();
}
@Bean
public RouterFunction<ServerResponse> routerFunction(RouterHandler routerHandler) {
// 动态定义路由(接口)信息
// RouterFunctions.route()静态方法
return route()
// 定义一个接口,请求方式为GET,URL为"/router/a"
// 并且设置规则,客户端需要的响应类型为JSON,并且参数必须含有name=luck才能访问到,否则404
// 请求的处理函数为一个HandlerFunction,是一个函数式接口,通过res->resp
.GET("/router/a", accept(APPLICATION_JSON).and(queryParam("name", "luck")), routerHandler::simpleBody)
.POST("/router/b", accept(APPLICATION_JSON), routerHandler::requestBody)
.GET("/router/c", accept(APPLICATION_JSON), routerHandler::fromDataBody)
.build();
}
/**
* <pre>
* HttpHandler是一个约定,专门用来处理请求和响应的处理器,它主要并且是唯一的目的就是对不同的Http服务器进行最小的抽象
* 说白了就是对不同Http服务器,HttpHandler接口就是Http服务器处理请求响应的处理类
*
* // Netty服务器
* HttpHandler handler = null;
* ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
* HttpServer.create().host(host).port(port).handle(adapter).bind().block();
*
* // Undertow服务器
* HttpHandler handler = null;
* UndertowHttpHandlerAdapter adapter = new UndertowHttpHandlerAdapter(handler);
* Undertow server = Undertow.builder().addHttpListener(port, host).setHandler(adapter).build();
* server.start();
*
* // Tomcat服务器
* Servlet servlet = new TomcatHttpHandlerAdapter(handler);
* Tomcat server = new Tomcat();
* File base = new File(System.getProperty("java.io.tmpdir"));
* Context rootContext = server.addContext("", base.getAbsolutePath());
* Tomcat.addServlet(rootContext, "main", servlet);
* rootContext.addServletMappingDecoded("/", "main");
* server.setHost(host);
* server.setPort(port);
* server.start();
* </pre>
*
* <pre>
* 在HttpHandler约定之上,Spring还提供了一个通用的WebAPI,通过多个WebExceptionHandler
* 多个WebFilter和一个WebHandler组成一条链来处理请求,这条链可以通过WebHttpHandlerBuilder来构建
* 而WebHttpHandlerBuilder会包含Spring上下文,从容器中扫描WebExceptionHandler,WebFilter和一个WebHandler的Bean
* 最终构建成HttpHandler来适配不同的Http服务器
* public HttpHandler build() {
* // 创建过滤器的WebHandler
* WebHandler decorated = new FilteringWebHandler(this.webHandler, this.filters);
* // 使用装饰模式,包装成一个可以处理异常的WebHandler
* decorated = new ExceptionHandlingWebHandler(decorated, this.exceptionHandlers);
* // 创建HttpWebHandlerAdapter的适配器,它也是HttpHandler类型,就是将WebHandler对WebHandler进行适配
* HttpWebHandlerAdapter adapted = new HttpWebHandlerAdapter(decorated);
* // 在HttpHandler的基础上,赋予它管理session,编码解码等等的功能
* if (this.sessionManager != null) {
* adapted.setSessionManager(this.sessionManager);
* }
* if (this.codecConfigurer != null) {
* adapted.setCodecConfigurer(this.codecConfigurer);
* }
* if (this.applicationContext != null) {
* adapted.setApplicationContext(this.applicationContext);
* }
* adapted.afterPropertiesSet();
* return (this.httpHandlerDecorator != null ? this.httpHandlerDecorator.apply(adapted) : adapted);
* }
* </pre>
*/
// 如果存在自定义HttpHandler接口Bean,那么所有的RouterFunction就会失效
// 因为HttpHandler是Web服务器处理所有请求的处理类,存在这个类的时候,就不会在处理RouterFunction的bean
// 如果不存在HttpHandler,最终也会将所有的RouterFunction组装,封装成HttpHandler
// @Bean
public HttpHandler httpHandler(RouterHandler routerHandler) {
// 定义路由,也就是API接口,最终使用HttpHandler进行注册
RouterFunction<ServerResponse> function = route()
.POST("/router/aa", accept(APPLICATION_JSON), routerHandler::fileUpload)
.POST("/router/bb", accept(APPLICATION_JSON), routerHandler::fileDownload)
.GET("/router/cc", accept(APPLICATION_JSON), routerHandler::simpleTestApi)
.build();
HandlerStrategies strategies = HandlerStrategies.builder()
// 添加编码解码器
.codecs(configurer -> {
})
// 添加异常处理器
.exceptionHandler(webExceptionHandler())
// 添加过滤器
// .webFilter(WebFilter())
// 添加视图解析器
.viewResolver(new FreeMarkerViewResolver())
.build();
// 将RouterFunction转换为HttpHandler类型
return toHttpHandler(function, strategies);
}
@Bean
public WebExceptionHandler webExceptionHandler() {
return new WebExceptionHandler() {
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
log.error("出现异常了:{}", ex.getMessage());
return exchange.getResponse().writeWith(
Mono.just(exchange.getResponse().bufferFactory().wrap(("Hello" + ex.getMessage()).getBytes(StandardCharsets.UTF_8))
));
}
};
}
// 这是WebFlux的过滤器
// @Bean
public WebFilter WebFilter() {
return new WebFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
if (exchange.getRequest().getQueryParams().containsKey("luck")) {
return chain.filter(exchange);
}
log.error("没有luck请求参数,拦截请求");
return Mono.empty();
}
};
}
}
package luck.spring.boot.webflux;
import cn.hutool.core.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.json.Jackson2CodecSupport;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.FormFieldPart;
import org.springframework.http.codec.multipart.Part;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.io.File;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
@Slf4j
@Component
public class RouterHandler {
public static final String fileDir = "spring-boot-source-webflux-study\\src\\main\\resources";
public Mono<ServerResponse> simpleBody(ServerRequest request) {
// get请求没有body
// Mono<String> body = request.bodyToMono(String.class);
// 直接返回结果,将Mono中的数据响应映射为Mono<ServerResponse>类型
return ok().contentType(APPLICATION_JSON).body(Mono.just("Luck"), String.class);
}
public Mono<ServerResponse> requestBody(ServerRequest request) {
// 处理POST请求,获取Body中的数据,将Body中的数据转换为Mono类型
// 一下两种方式都可以获取Body数据
// Mono<Entity> body = request.bodyToMono(Entity.class);
return request.body(BodyExtractors.toMono(Entity.class))
// 得到Body中的数据,进行逻辑操作
// 操作完成最终将结果进行映射为最终的响应类型Mono<ServerResponse>
.flatMap(entity -> ok()
.contentType(APPLICATION_JSON)
.bodyValue(entity.setName(entity.getName() + RandomUtil.randomString(3))));
}
public Mono<ServerResponse> fromDataBody(ServerRequest request) {
// 处理GET请求,获取表单类型的数据
// 不是multipart/form-data,这个是处理文件请求
// 而是x-www-from-urlencoded
return request.formData()
// 将表单数据进行逻辑处理,得到最终结果,最终映射为Mono<ServerResponse>类型
.flatMap(map -> ok()
.contentType(APPLICATION_JSON)
.bodyValue(map));
}
public Mono<ServerResponse> fileUpload(ServerRequest request) {
// 和BodyExtractors.toMultipartData()效果一样
Mono<MultiValueMap<String, Part>> mono = request.multipartData();
return mono.flatMap(map -> {
map.toSingleValueMap().values().forEach(part -> {
if (part instanceof FormFieldPart fieldPart) {
log.error("field value is {}", fieldPart.value());
}
if (part instanceof FilePart filePart) {
log.error("filename is {}", filePart.filename());
// 所有操作都是异步的,必须要订阅才会触发保存操作
filePart.transferTo(new File(fileDir, filePart.filename())).subscribe();
}
});
return ok().contentType(APPLICATION_JSON).bodyValue("上传成功");
});
}
public Mono<ServerResponse> fileDownload(ServerRequest request) {
File file = new File(fileDir, "test.png");
return ok()
// 设置响应类型
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(file.length())
// 设置文件头
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getName())
// 设置响应体
.body(BodyInserters.fromResource(new FileSystemResource(file)));
}
public Mono<ServerResponse> simpleTestApi(ServerRequest request) {
// 设置提示信息,和Hibernate类型,给编码解码器的提供商设置额外的信息
// 例如,为json编解码器设置对应的响应分组
log.error("hello simple test api");
return ok()
.contentType(APPLICATION_JSON)
// 一种是map的形式,一种是单个设置的形式,key相同会被覆盖
.hints(map -> {
map.put(Jackson2CodecSupport.JSON_VIEW_HINT, Entity.UpdateJsonView.class);
})
.hint(Jackson2CodecSupport.JSON_VIEW_HINT, Entity.AddJsonView.class)
.bodyValue(new Entity("Luck", 100));
}
}
10-18
1634
![](https://csdnimg.cn/release/blogv2/dist/pc/img/readCountWhite.png)
11-15
3982
![](https://csdnimg.cn/release/blogv2/dist/pc/img/readCountWhite.png)
“相关推荐”对你有帮助么?
-
非常没帮助
-
没帮助
-
一般
-
有帮助
-
非常有帮助
提交