说明
在 Spring boot web
中我们可以通过 RequestContextHolder
很方便的获取 request
。
- ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
-
- // 获取 request
- HttpServletRequest request = requestAttributes.getRequest();
不再需要通过参数传递 request
。在 Spring webflux 中并没提供该功能,使得我们在 Aop 或者一些其他的场景中获取 request
变成了一个奢望???
寻求解决方案
首先我想到的是看看 spring-security
中是否有对于的解决方案,因为在 spring-security
中我们也是可以通过 SecurityContextHolder
很方便快捷的获取当前登录的用户信息。
找到了 ReactorContextWebFilter,我们来看看 security 中他是怎么实现的。 https://github.com/spring-projects/spring-security/blob/master/web/src/main/java/org/springframework/security/web/server/context/ReactorContextWebFilter.java#L43
- public class ReactorContextWebFilter implements WebFilter {
- private final ServerSecurityContextRepository repository;
-
- public ReactorContextWebFilter(ServerSecurityContextRepository repository) {
- Assert.notNull(repository, "repository cannot be null");
- this.repository = repository;
- }
-
- @Override
- public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
- return chain.filter(exchange)
- .subscriberContext(c -> c.hasKey(SecurityContext.class) ? c :
- withSecurityContext(c, exchange)
- );
- }
-
- private Context withSecurityContext(Context mainContext, ServerWebExchange exchange) {
- return mainContext.putAll(this.repository.load(exchange)
- .as(ReactiveSecurityContextHolder::withSecurityContext));
- }
- }
源码里面我们可以看到 他利用一个 Filter,chain.filter(exchange)
的返回值 Mono 调用了 subscriberContext 方法。 那么我们就去了解一下这个 reactor.util.context.Context
。找到 reactor 官方文档中的 context 章节:https://projectreactor.io/docs/core/release/reference/#context
大意是:从 Reactor 3.1.0 开始提供了一个高级功能,可以与 ThreadLocal 媲美,应用于 Flux 和 Mono 的上下文工具 Context。更多请大家查阅官方文档,对英文比较抵触的朋友可以使用 google 翻译。
mica 中的实现
mica 中的实现比较简单,首先是我们的 ReactiveRequestContextFilter
:
- /**
- * ReactiveRequestContextFilter
- *
- * @author L.cm
- */
- @Configuration
- @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
- public class ReactiveRequestContextFilter implements WebFilter {
-
- @Override
- public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
- ServerHttpRequest request = exchange.getRequest();
- return chain.filter(exchange)
- .subscriberContext(ctx -> ctx.put(ReactiveRequestContextHolder.CONTEXT_KEY, request));
- }
- }
在 Filter
中直接将 request
存储到 Context
上下文中。
ReactiveRequestContextHolder 工具:
- /**
- * ReactiveRequestContextHolder
- *
- * @author L.cm
- */
- public class ReactiveRequestContextHolder {
- static final Class<ServerHttpRequest> CONTEXT_KEY = ServerHttpRequest.class;
-
- /**
- * Gets the {@code Mono<ServerHttpRequest>} from Reactor {@link Context}
- * @return the {@code Mono<ServerHttpRequest>}
- */
- public static Mono<ServerHttpRequest> getRequest() {
- return Mono.subscriberContext()
- .map(ctx -> ctx.get(CONTEXT_KEY));
- }
-
- }
怎么使用呢?
mica 中对未知异常处理,从 request
中获取请求的相关信息
- @ExceptionHandler(Throwable.class)
- @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
- public Mono<?> handleError(Throwable e) {
- log.error("未知异常", e);
- // 发送:未知异常异常事件
- return ReactiveRequestContextHolder.getRequest()
- .doOnSuccess(r -> publishEvent(r, e))
- .flatMap(r -> Mono.just(R.fail(SystemCode.FAILURE)));
- }
-
- private void publishEvent(ServerHttpRequest request, Throwable error) {
- // 具体业务逻辑
- }
WebClient 透传 request
中的 header
此示例来源于开源中国问答中笔者的回复: 《如何在gateway 中获取 webflux的 RequestContextHolder》
- @GetMapping("/test")
- @ResponseBody
- public Mono<String> test() {
- WebClient webClient = testClient();
- return webClient.get().uri("").retrieve().bodyToMono(String.class);
- }
-
- @Bean
- public WebClient testClient() {
- return WebClient.builder()
- .filter(testFilterFunction())
- .baseUrl("https://www.baidu.com")
- .build();
- }
-
- private ExchangeFilterFunction testFilterFunction() {
- return (request, next) -> ReactiveRequestContextHolder.getRequest()
- .flatMap(r -> {
- ClientRequest clientRequest = ClientRequest.from(request)
- .headers(headers -> headers.set(HttpHeaders.USER_AGENT, r.getHeaders().getFirst(HttpHeaders.USER_AGENT)))
- .build();
- return next.exchange(clientRequest);
- });
- }
上段代码是透传 web 中的 request
中的 user_agent
请求头到 WebClient
中。
开源推荐
mica
Spring boot 微服务核心组件集:https://gitee.com/596392912/micaAvue
一款基于vue可配置化的神奇框架:https://gitee.com/smallweigit/avuepig
宇宙最强微服务(架构师必备):https://gitee.com/log4j/pigSpringBlade
完整的线上解决方案(企业开发必备):https://gitee.com/smallc/SpringBladeIJPay
支付SDK让支付触手可及:https://gitee.com/javen205/IJPay
关注我们
扫描上面二维码,更多精彩内容每天推荐!
转载声明
如梦技术对此篇文章有最终所有权,转载请注明出处,参考也请注明,谢谢!