Spring boot webflux 中实现 RequestContextHolder

Stella981
• 阅读 1229

说明

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 中。

开源推荐

关注我们

Spring boot webflux 中实现 RequestContextHolder

扫描上面二维码,更多精彩内容每天推荐!

转载声明

如梦技术对此篇文章有最终所有权,转载请注明出处,参考也请注明,谢谢!

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Souleigh ✨ Souleigh ✨
3年前
前端性能优化 - 雅虎军规
无论是在工作中,还是在面试中,web前端性能的优化都是很重要的,那么我们进行优化需要从哪些方面入手呢?可以遵循雅虎的前端优化35条军规,这样对于优化有一个比较清晰的方向.35条军规1.尽量减少HTTP请求个数——须权衡2.使用CDN(内容分发网络)3.为文件头指定Expires或CacheControl,使内容具有缓存性。4.避免空的
虾米大王 虾米大王
2年前
java代码012
code012.jspInserttitlehere<%JSP内置对象1.request用于处理HTTP请求中的各项参数。如,删除可以通过request对象的getParameter()方法获取如,request.getParameter("id")在请求转发时,需要把一些数据传递到转发后的页面处理。就需要用到request的se
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Easter79 Easter79
3年前
SpringBoot中的响应式web应用
简介在Spring5中,SpringMVC引入了webFlux的概念,webFlux的底层是基于reactornetty来的,而reactornetty又使用了Reactor库。本文将会介绍在SpringBoot中reactive在WebFlux中的使用。ReactiveinSpring
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这