Spring boot webflux 中實現 RequestContextHolder

如夢技術發表於2019-04-04

說明

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 中他是怎麼實現的。 github.com/spring-proj…

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 章節:projectreactor.io/docs/core/r…

大意是:從 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 中。

開源推薦

關注我們

如夢技術-公眾號.jpg

掃描上面二維碼,更多精彩內容每天推薦!

轉載宣告

如夢技術對此篇文章有最終所有權,轉載請註明出處,參考也請註明,謝謝!

相關文章