springmvc异步流
这是SpringWebMVC应用程序的一部分,它使用WebClient进行OAuth2客户端集成,其目的是使用适当的授权头代理一些请求到资源服务器和从资源服务器发出的请求,这些授权头被拼凑在一起,以将数据从异步springmvc异步流,spring,spring-mvc,spring-webflux,spring-oauth2,spring-webclient,Spring,Spring Mvc,Spring Webflux,Spring Oauth2,Spring Webclient,这是SpringWebMVC应用程序的一部分,它使用WebClient进行OAuth2客户端集成,其目的是使用适当的授权头代理一些请求到资源服务器和从资源服务器发出的请求,这些授权头被拼凑在一起,以将数据从异步流量传输到同步输出流。我这样做是因为我找不到一种方法让Spring神奇地处理Mono作为返回类型。我的意思是它做了一些事情,但不是我想从中得到的,那就是流式传输原始二进制数据 @RestController public class ResourcePassthroughControlle
流量
传输到同步输出流
。我这样做是因为我找不到一种方法让Spring神奇地处理Mono
作为返回类型。我的意思是它做了一些事情,但不是我想从中得到的,那就是流式传输原始二进制数据
@RestController
public class ResourcePassthroughController {
private final WebClient webClient;
private final AntPathMatcher pathMatcher = new AntPathMatcher();
private final String resourceServerUri;
private final DataBufferFactory bufferFactory = new DefaultDataBufferFactory();
public ResourcePassthroughController(
WebClient webClient,
@Value("${app.urls.my-client-resource-server}") String uri) {
this.webClient = webClient;
this.resourceServerUri = uri;
}
@RequestMapping(value = "/resource/**")
// public Mono<ResponseEntity<Flux<DataBuffer>>> getResource(
public ResponseEntity<StreamingResponseBody> getResource(
@RegisteredOAuth2AuthorizedClient("my-client") OAuth2AuthorizedClient authorizedClient,
HttpServletRequest req) throws IOException {
String resourcePath = "/" + pathMatcher.extractPathWithinPattern("/resource/**", req.getServletPath()) // req.getPathInfo() // ?
+ (req.getPathInfo() != null ? req.getPathInfo() : "")
+ (req.getQueryString() != null ? "?" + req.getQueryString() : "");
// Mono<ResponseEntity<Flux<DataBuffer>>> mono = this
// .webClient
// .method(HttpMethod.valueOf(req.getMethod()))
// .uri(this.resourceServerUri + resourcePath)
// .attributes(oauth2AuthorizedClient(authorizedClient))
// // .accept(MediaType.APPLICATION_JSON)
// .retrieve()
// .toEntityFlux(DataBuffer.class);
// return mono;
ResponseEntity<Flux<DataBuffer>> responseEntity = this
.webClient
.method(HttpMethod.valueOf(req.getMethod()))
.uri(this.resourceServerUri + resourcePath)
.body(BodyInserters.fromDataBuffers(DataBufferUtils.read(new InputStreamResource(req.getInputStream()), bufferFactory, 4096)))
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.toEntityFlux(DataBuffer.class)
.block()
;
responseEntity
.getBody()
// .timeout(Duration.ofSeconds(90), Flux.empty());
.timeout(Duration.ofSeconds(90)) // stop reading after 90 seconds and proapagate TimeoutException ?
;
HttpHeaders headers = responseEntity.getHeaders();
StreamingResponseBody streamer = (outputStream) -> {
Flux<DataBuffer> flux = DataBufferUtils
.write(responseEntity.getBody(), outputStream)
.publish()
.autoConnect(2); // expect two subscribers (.subscribe and .blockLast)
flux.subscribe(DataBufferUtils.releaseConsumer()); // resolve MEMORY LEAK
// flux.timeout(Duration.ofSeconds(120), Flux.error(() -> new IOException("Flux proxy write timeout"))); // stop writing after 120 seconds and propagate exception
flux.timeout(Duration.ofSeconds(120)); // stop writing after 120 seconds and propagate TimeoutException
try {
flux.blockLast(); // once this call returns, the streamer function will follow, so Spring can then close the outputStream it has given us
} catch (RuntimeException ex) {
Throwable cause = ex.getCause();
if (cause instanceof TimeoutException) {
throw new IOException("Flux proxy timeout",ex.getCause());
}
throw ex;
}
};
return ResponseEntity.ok().headers(headers)
// .contentLength(headers.getContentLength())
// .contentType(headers.getContentType())
.body(streamer);
}
}
@RestController
公共类ResourcePassthroughController{
私人最终网络客户网络客户;
私有最终AntPathMatcher pathMatcher=新AntPathMatcher();
私有最终字符串resourceServerUri;
private final DataBufferFactory bufferFactory=新的DefaultDataBufferFactory();
公共资源直通控制器(
网络客户端网络客户端,
@值(“${app.url.my client resource server}”)字符串uri){
this.webClient=webClient;
this.resourceServerUri=uri;
}
@请求映射(value=“/resource/**”)
//公共资源(
公共反应资源(
@RegisteredAuth2AuthorizedClient(“我的客户”)OAuth2AuthorizedClient授权客户,
HttpServletRequest(请求请求)引发IOException{
字符串resourcePath=“/”+路径匹配器.extractPathWithinPattern(“/resource/**”,req.getServletPath())//req.getPathInfo()/?
+(req.getPathInfo()!=null?req.getPathInfo():“”)
+(req.getQueryString()!=null?“+req.getQueryString():”);
//Mono=这个
//.网络客户
//.method(HttpMethod.valueOf(req.getMethod()))
//.uri(this.resourceServerUri+resourcePath)
//.attributes(oauth2AuthorizedClient(authorizedClient))
////.accept(MediaType.APPLICATION_JSON)
//.retrieve()
//.toEntityFlux(数据缓冲类);
//返回单声道;
ResponseEntity ResponseEntity=这个
.网络客户
.method(HttpMethod.valueOf(req.getMethod()))
.uri(this.resourceServerUri+resourcePath)
.body(BodyInserters.fromDataBuffers(DataBufferUtils.read(新的InputStreamResource(req.getInputStream()),bufferFactory,4096)))
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.toEntityFlux(数据缓冲类)
.block()
;
反应性
.getBody()
//.timeout(持续时间为秒(90),Flux.empty());
.timeout(持续时间.ofSeconds(90))//在90秒后停止读取并引发TimeoutException?
;
HttpHeaders=responseEntity.getHeaders();
StreamIngressponseBody拖缆=(outputStream)->{
通量=数据缓冲器
.write(responseEntity.getBody(),outputStream)
.publish()
.autoConnect(2);//预期有两个订户(.subscribe和.blockLast)
flux.subscribe(DataBufferUtils.releaseConsumer());//解决内存泄漏
//flux.timeout(持续时间为120秒),flux.error(()->new IOException(“flux proxy write timeout”);//120秒后停止写入并传播异常
flux.timeout(持续时间.of秒(120));//在120秒后停止写入并传播TimeoutException
试一试{
flux.blockLast();//一旦此调用返回,streamer函数将随之出现,因此Spring可以关闭它提供给我们的outputStream
}捕获(RuntimeException ex){
可丢弃原因=例如getCause();
if(导致TimeoutException的实例){
抛出新的IOException(“通量代理超时”,例如getCause());
}
掷骰子;
}
};
返回ResponseEntity.ok().headers(headers)
//.contentLength(headers.getContentLength())
//.contentType(headers.getContentType())
.主体(拖缆);
}
}
然而,当我切换到WebFlux时,Netty恰好在流媒体方面做得很好:
@RestController
public class ResourcePassthroughController {
private final WebClient webClient;
@Value("${app.main-oauth2-client-registration}")
String oauth2ClientRegistration;
public ResourcePassthroughController(WebClient webClient) {
this.webClient = webClient;
}
@RequestMapping(value = "/resource/{*path}")
public Mono<ResponseEntity<Flux<DataBuffer>>> getResource( // this return type does NOT work with servlet, may be a missing decoder
// @RegisteredOAuth2AuthorizedClient("${app.main-oauth2-client-id}") OAuth2AuthorizedClient authorizedClient,
ServerHttpRequest request,
@PathVariable("path") String resourcePath,
@RequestParam MultiValueMap<String,String> queryMap) {
return this.webClient
.method(request.getMethod())
.uri(builder -> builder
.path(resourcePath) // append to path defined in WebClientConfig
.queryParams(queryMap)
.build()
)
.body(BodyInserters.fromDataBuffers(request.getBody()))
.attributes(clientRegistrationId(oauth2ClientRegistration)) // OAuth2AuthorizedClient extracted from current logged-in user
.retrieve()
.toEntityFlux(DataBuffer.class)
.timeout(Duration.ofSeconds(90))
;
}
}
@RestController
公共类ResourcePassthroughController{
私人最终网络客户网络客户;
@值(${app.main-oauth2-client-registration}”)
字符串OAuth2Client注册;
公共资源直通控制器(网络客户端网络客户端){
this.webClient=webClient;
}
@请求映射(value=“/resource/{*path}”)
public Mono getResource(//此返回类型不适用于servlet,可能缺少解码器
//@RegisteredOAuth2AuthorizedClient(${app.main-oauth2-client-id}”)OAuth2AuthorizedClient授权客户端,
ServerHttpRequest请求,
@PathVariable(“path”)字符串resourcePath,
@RequestParam多值映射查询映射){
返回此Web客户端
.method(request.getMethod())
.uri(生成器->生成器
.path(resourcePath)//附加到WebClient配置中定义的路径
.queryParams(queryMap)
.build()
)
.body(BodyInserters.fromDataBuffers(request.getBody()))
.attributes(clientRegistrationId(OAuth2Client Registration))//OAuth2AuthorizedClient从当前登录用户中提取
.retrieve()
.toEntityFlux(数据缓冲类)
.超时(持续时间秒(90))
;
}
}
但是现在我真的很想继续使用servlet,并在我的
DataBuffer
s(也叫ByteBuffer
s)周围适当地Flux
使它的异步部分。有没有一个很好的方法来做到这一点呢?您必须知道,在servlet环境中使用反应性位是可行的,但不是(完全的)被动响应,因为它只利用Servlet API的异步部分来实现mimi