我正在运行一个 Spring Boot 应用程序,它使用 WebClient 来处理非阻塞和阻塞 HTTP 请求。应用程序运行一段时间后,所有传出的 HTTP 请求似乎都会被卡住。
WebClient 用于向多个主机发送请求,但作为示例,以下是它的初始化和用于向 Telegram 发送请求的方式:
网络客户端配置:
@Bean
public ReactorClientHttpConnector httpClient() {
HttpClient.create(ConnectionProvider.builder("connectionProvider").build())
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout)
.responseTimeout(Duration.ofMillis(responseTimeout));
return new ReactorClientHttpConnector(httpClient);
}
Run Code Online (Sandbox Code Playgroud)
所有 WebClient 使用相同的 ReactorClientHttpConnector。
电报客户端:
@Autowired
ReactorClientHttpConnector httpClient;
WebClient webClient;
RateLimiter rateLimiter;
@PostConstruct
public void init() {
webClient = WebClient.builder()
.clientConnector(httpClient)
.baseUrl(telegramUrl)
.build();
rateLimiter = RateLimiter.of("telegram-rate-limiter",
RateLimiterConfig.custom()
.limitRefreshPeriod(Duration.ofMinutes(1))
.limitForPeriod(20)
.build());
}
public void sendMessage(@PathVariable("token") String token, @RequestParam("chat_id") long chatId, @RequestParam("text") String message) {
webClient.post().uri(String.format("/bot%s/sendMessage", token))
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromFormData("chat_id", String.valueOf(chatId))
.with("text", message))
.retrieve()
.bodyToMono(Void.class) …Run Code Online (Sandbox Code Playgroud) 我需要发出同步的阻塞请求,并且我正在使用 SpringWebClient而不是 Spring RestTemplate,因为后者已被弃用。在这种情况下,我不需要反应式功能,我只想以简单的方式使用 REST API,而不包含额外的依赖项。
我有以下代码,其按预期工作:
\nMyObject object = webClient.get()\n .uri( myUri )\n .retrieve()\n .bodyToMono( MyObject.class )\n .block()\nRun Code Online (Sandbox Code Playgroud)\n但是,我需要处理无法连接到 API 或连接但收到 4xx/5xx 代码时的情况。
\n因此,最简单的方法是将调用放入 try/catch 中,并捕获 Spring 的,如果它获得 4xx/5xx 代码,WebClientResponseException则会抛出该错误:.bodyToMono
import org.springframework.web.reactive.function.client.WebClientResponseException;\n\ntry {\n\n MyObject object = webClient.get()\n .uri( myUri )\n .retrieve()\n .bodyToMono( MyObject.class )\n .block()\n\n}\n\ncatch ( WebClientResponseException e ) {\n\n // Logic to handle the exception.\n\n}\nRun Code Online (Sandbox Code Playgroud)\n这工作正常,但如果连接被拒绝(例如,如果 URL 错误或服务关闭),则不起作用。在这种情况下,我在控制台中看到以下内容:
\n\nreactor.core.Exceptions$ErrorCallbackNotImplemented:\nio.netty.channel.AbstractChannel$AnnotatedConnectException:\nfinishConnect(..) 失败: 连接被拒绝: /127.0.0.1:8090 …
基本上,我想在一个包含 Spring 的主体/标头的日志中记录请求/响应信息WebClient。
有了 Spring,RestTemplate我们就可以用ClientHttpRequestInterceptor. 我找到了ExchangeFilterFunctionSpring 的相关内容WebClient,但还没有设法以干净的方式做类似的事情。我们可以使用此过滤器并记录请求,然后记录响应,但我需要在同一日志跟踪上两者。
此外,我还没有设法用ExchangeFilterFunction.ofResponseProcessor方法获取响应正文。
我期望这样的日志(当前的实现与 ClientHttpRequestInterceptor 一起使用)包含我需要的所有信息:
{
"@timestamp": "2019-05-14T07:11:29.089+00:00",
"@version": "1",
"message": "GET https://awebservice.com/api",
"logger_name": "com.sample.config.resttemplate.LoggingRequestInterceptor",
"thread_name": "http-nio-8080-exec-5",
"level": "TRACE",
"level_value": 5000,
"traceId": "e65634ee6a7c92a7",
"spanId": "7a4d2282dbaf7cd5",
"spanExportable": "false",
"X-Span-Export": "false",
"X-B3-SpanId": "7a4d2282dbaf7cd5",
"X-B3-ParentSpanId": "e65634ee6a7c92a7",
"X-B3-TraceId": "e65634ee6a7c92a7",
"parentId": "e65634ee6a7c92a7",
"method": "GET",
"uri": "https://awebservice.com/api",
"body": "[Empty]",
"elapsed_time": 959,
"status_code": 200,
"status_text": "OK",
"content_type": "text/html",
"response_body": "{"message": "Hello World!"}"
}
Run Code Online (Sandbox Code Playgroud)
有人设法用 Spring WebClient 做这样的事情吗?或者如何继续使用 Spring WebClient …
logging spring-boot spring-web spring-webflux spring-webclient
我有一个 REST 服务,它接受一个 id 和两个字符串作为 json,并返回 id 和连接为 json 的两个字符串。如果出现错误,它可以返回状态代码 400、404 和 500,并在正文中包含 json 错误响应。
swagger.json 文件如下所示:
{
"swagger": "2.0",
"info": {
"description": "---",
"version": "---",
"title": "---"
},
"basePath": "/rest",
"tags": [
{
"name": "Concatenation resource"
}
],
"schemes": [
"http",
"https"
],
"paths": {
"/concatenation/{id}/concat": {
"post": {
"tags": [
"Concatenation resource"
],
"summary": "Concatenates two strings",
"description": "",
"operationId": "concatenate",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true, …Run Code Online (Sandbox Code Playgroud) java openapi spring-webflux openapi-generator spring-webclient
我在使用 WebFlux 的 Spring Boot 应用程序中收到以下错误
org.springframework.core.io.buffer.DataBufferLimitException:超出了缓冲区最大字节数限制:262144
application.yml我尝试设置内部限制
spring:
codec:
max-in-memory-size: 10MB
Run Code Online (Sandbox Code Playgroud)
或者通过将以下内容添加到 WebClient 配置类,但它们会被忽略。
.exchangeStrategies(ExchangeStrategies.builder()
.codecs(clientCodecConfigurer -> clientCodecConfigurer
.defaultCodecs()
.maxInMemorySize(10 * 1024 * 1024)
)
.build()
)
Run Code Online (Sandbox Code Playgroud)
我正在使用 Spring Boot 2.3.3.RELEASE。
您知道问题可能是什么吗?
看起来Spring RestTemplate不能将响应直接流式传输到文件而不将其全部缓存在内存中。使用较新的Spring 5实现此目标的合适方法是WebClient什么?
WebClient client = WebClient.create("https://example.com");
client.get().uri(".../{name}", name).accept(MediaType.APPLICATION_OCTET_STREAM)
....?
Run Code Online (Sandbox Code Playgroud)
我看到人们已经找到了解决此问题的一些变通方法/技巧RestTemplate,但是我对使用正确的方法更感兴趣WebClient。
有许多RestTemplate用于下载二进制数据的示例,但是几乎所有示例都将其加载byte[]到内存中。
我的以下 WebClient 可以正常使用 Internet 连接,但不能通过我们的代理连接。
WebClient webClient = WebClient.builder()
.baseUrl("https://targetsite.com")
.build();
webClient.post()
.uri("/service/serviceName")
.body(BodyInserters.fromObject(reqData))
.retrieve()
.bodyToMono(WebServiceResponse.class)
Run Code Online (Sandbox Code Playgroud)
事件虽然,同一个客户端通过代理工作,如果我如下设置它,
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(tcpClient -> tcpClient
.proxy(proxy -> proxy
.type(ProxyProvider.Proxy.HTTP)
.host("ourproxy.com")
.port(8080)));
ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
WebClient webClient = WebClient.builder()
.clientConnector(connector)
.baseUrl("https://targetsite.com")
.build();
webClient.post()
.uri("/service/serviceName")
.body(BodyInserters.fromObject(reqData))
.retrieve()
.bodyToMono(WebServiceResponse.class)
Run Code Online (Sandbox Code Playgroud)
但是,如果我使用System.setProperty("http.proxyHost","ourproxy.com");
System.setProperty("http.proxyPort","8080");
或 JVM 运行时参数设置相同的代理详细信息-Dhttp.proxyHost=ourproxy.com -Dhttp.proxyPort=8080
WebClient webClient = WebClient.builder()
.baseUrl("https://targetsite.com")
.build();
System.setProperty("http.proxyHost", "ourproxy.com");
System.setProperty("http.proxyPort", "8080");
webClient.post()
.uri("/service/serviceName")
.body(BodyInserters.fromObject(reqData))
.retrieve()
.bodyToMono(WebServiceResponse.class)
Run Code Online (Sandbox Code Playgroud)
调用因 UnknownHostException 而失败,例如,
[04/11/2019 12:32:43.031 IST] DEBUG [reactor-http-epoll-3] [PooledConnectionProvider:254] - …Run Code Online (Sandbox Code Playgroud) 我试图为我的休息客户端设置标题,但每次我必须写
webclient.get().uri("blah-blah")
.header("key1", "value1")
.header("key2", "value2")...
Run Code Online (Sandbox Code Playgroud)
如何使用 headers() 方法同时设置所有标题?
rest-client resttemplate java-8 spring-boot spring-webclient
我使用Web客户端发布第三方API,第三方API返回响应如下:
\n{\n "RetailTransactionGenericResponse": {\n "authID": 1146185,\n "product": {\n "amount": 20,\n "balance": 0,\n "status": "D",\n "metafields": {\n "metafield": [\n {\n "name": "serialNum",\n "value": 8095843490\n }\n ]\n }\n },\n "responseCode": 0,\n "responseText": "Success",\n "transactionID": "b0924cca-f2a9-477f-915b-9153e74ebce0"\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n所以在我的payload包中,它有RetailTransactionGenericResponseWrapper类,其中包含RetailTransactionGenericResponse类。
RetailTransactionGenericResponse这是类的代码
import com.fasterxml.jackson.annotation.JsonCreator;\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.ToString;\n\n@Data\n@ToString\n@Builder\n@AllArgsConstructor(onConstructor_ = @JsonCreator)\n@JsonInclude(JsonInclude.Include.NON_NULL)\npublic class RetailTransactionGenericResponse {\n\n @JsonProperty\n private String authID;\n\n @JsonProperty\n private Product product;\n\n @JsonProperty\n private String responseCode;\n\n @JsonProperty\n private String responseText;\n\n @JsonProperty\n private String transactionID;\n}\n …Run Code Online (Sandbox Code Playgroud) 长期以来,Spring 一直推荐使用RestTemplate来同步 http 请求。然而,现在的文档说:
注意:从 5.0 开始,此类处于维护模式,仅接受较小的更改请求和错误。请考虑使用 org.springframework.web.reactive.client.WebClient ,它具有更现代的 API 并支持同步、异步和流场景。
但我还没有看到如何建议使用WebClient进行同步场景。文档中有这样的内容:
WebClient 可以通过在结果结束时阻塞来以同步方式使用
我已经看到一些代码库到处都使用 .block() 。然而,我的问题是,凭借一些反应式框架的经验,我逐渐明白阻止反应式调用是一种代码味道,实际上应该只在测试中使用。例如这个页面说
有时,您只能将部分代码迁移为响应式,并且需要在更多命令式代码中重用响应式序列。
因此,如果您需要阻塞直到 Mono 的值可用,请使用 Mono#block() 方法。如果 onError 事件被触发,它将抛出异常。
请注意,您应该通过尽可能支持端到端的反应式代码来避免这种情况。您必须在其他反应式代码中间不惜一切代价避免这种情况,因为这有可能锁定整个反应式管道。
那么我是否错过了一些可以避免 block() 但允许您进行同步调用的东西,或者是否在任何地方都使用 block() 真的是这样?
或者 WebClient API 的意图是暗示人们不应该再在代码库中的任何地方进行阻塞?由于 WebClient 似乎是 Spring 提供的未来 http 调用的唯一替代方案,因此未来在整个代码库中使用非阻塞调用并更改代码库的其余部分以适应这一点是唯一可行的选择吗?
这里有一个相关的问题,但它只关注发生的异常,而我有兴趣听到一般的方法应该是什么。
spring-webclient ×10
java ×7
spring-boot ×7
spring ×3
java-8 ×1
logging ×1
openapi ×1
reactive ×1
rest ×1
rest-client ×1
resttemplate ×1
spring-web ×1
synchronous ×1