简体   繁体   English

使用 Reactor WebClient 发出非阻塞 HTTP 请求并反序列化对 object 的响应的最佳方法是什么?

[英]What is the best way to make a non-blocking HTTP request using Reactor WebClient and deserialize the response to an object?

I have experience with asynchronous libraries like Vert.x but new to Reactor/WebFlux specifically.我有使用 Vert.x 之类的异步库的经验,但对 Reactor/WebFlux 来说特别陌生。 I want to expose a single endpoint on a web application that when hit, turns around and calls another web service, parses the response into a Java object, then accesses fields within the object and does something with them. I want to expose a single endpoint on a web application that when hit, turns around and calls another web service, parses the response into a Java object, then accesses fields within the object and does something with them. I am using WebClient to make the HTTP call and Jackson ObjectMapper to deserialize it.我正在使用 WebClient 进行 HTTP 调用和 Jackson ObjectMapper 对其进行反序列化。 My code looks roughly like this (note: RequestUtil.safeDeserialize just uses Jackson to parse the string body into an object and returns Optional<Object> which is why I have an additional map step afterwards):我的代码看起来大致是这样的(注意: RequestUtil.safeDeserialize只使用 Jackson 将字符串主体解析为 object 并返回Optional<Object>这就是为什么我有一个额外的map步骤之后):

    public Mono<String> function(final String param) {
        final String encodedRequestBody = RequestUtil.encodeRequestBody(param);
        final Mono<Response> responseMono = webClient.post()
                .uri("/endpoint")
                .header("Content-Type", "application/x-www-form-urlencoded")
                .header("Authorization", "Basic " + basicAuthHeader)
                .accept(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromPublisher(Mono.just(encodedRequestBody), String.class))
                .exchange()
                .flatMap(clientResponseMono -> clientResponseMono.bodyToMono(String.class))
                .map(RequestUtil::safeDeserialize)
                .map(resp -> resp.orElseThrow(() -> new RuntimeException("Failed to deserialize Oscar response!")));

        responseMono.subscribe(response -> {
            // Pull out some of the fields from the `Response` type object and do something with them
        });

        return responseMono.map(Response::aStringField);
    }

After performance testing this code against an identical application that follows the exact same logic, but makes the HTTP call via the blocking Java11 HttpClient class, I see almost no difference between the two -- in fact, the WebClient implementation is slightly less performant than the blocking implementation.在针对遵循完全相同逻辑但通过阻塞 Java11 HttpClient class 进行 HTTP 调用的相同应用程序对该代码进行性能测试后,我发现两者之间几乎没有区别——事实上, WebClient实现的性能略低于阻止实施。

Clearly I made a mistake somewhere either with the code or my mental model of what's going on here, so any help/advice is very appreciated.显然,我在代码或我的心理 model 的某处犯了错误,因此非常感谢任何帮助/建议。 Thanks!谢谢!

Edit: Based on the advice in @Toerktumlare's response, I have updated the function to the following:编辑:根据@Toerktumlare 回复中的建议,我已将 function 更新为以下内容:

    public Mono<String> function(final String param) {
        final Mono<String> encodedRequestBody = RequestUtil.encodeRequestBodyToMono(param);
        final Mono<Response> responseMono = webClient.post()
                .uri("/endpoint")
                .header("Content-Type", "application/x-www-form-urlencoded")
                .header("Authorization", "Basic " + basicAuthHeader)
                .accept(MediaType.APPLICATION_JSON)
                .body(encodedRequestBody, String.class)
                .retrieve()
                .bodyToMono(Response.class);

        return responseMono.flatMap(response -> {
            final String field = response.field();
            // Use `field` to do something that would produce a log message
            logger.debug("Field is: {}", field);
            return Mono.just(field);
        });
}

When running this code, I don't see any logging.运行此代码时,我看不到任何日志记录。 This makes me think that the HTTP call isn't actually happening (or completing in time?) because when I use subscribe with the same WebClient code, I can successfully print out fields from the response.这让我认为 HTTP 调用实际上并没有发生(或及时完成?),因为当我使用相同的WebClient代码subscribe时,我可以成功地从响应中打印出字段。 What am I missing?我错过了什么?

Edit2: This function is being used to serve responses to an endpoint (a few lines of code are omitted for conciseness): Edit2:此 function 用于为端点提供响应(为简洁起见,省略了几行代码):

    @Bean
    public RouterFunction<ServerResponse> routerFunction(ResponseHandler handler) {
        return RouterFunctions.route(RequestPredicates.GET("/my/endpoint")
                .and(RequestPredicates.accept(MediaType.ALL)), handler::endpoint);
    }
 

    public Mono<ServerResponse> endpoint(ServerRequest request) {
        // Pull out a comma-separated list from the request
        final List<String> params = Arrays.asList(fieldFromRequest.split(","));

        // For each param, call function(param), roll into list
        List<Mono<String>> results = params.stream()
                .map(nonBlockingClient::function)
                .collect(Collectors.toList());

        // Check to see if any of the requests failed
        if (results.size() != params.size()) {
            return ServerResponse.status(500).build();
        }

        logger.debug("Success");
        return ServerResponse.ok().build();
    }

Most likely the your problem is with the usage of subscribe .您的问题很可能与subscribe的使用有关。

A consumer will subscribe to a producer . consumersubscribe producer Your backend application is a producer which makes the calling client the consumer .您的后端应用程序是一个producer ,它使调用客户端成为consumer Which means, its usually the calling client that should be subscribing not you.这意味着,它通常应该subscribing而不是您的调用客户端。

What you are doing now is basically consuming your own production .您现在所做的基本上是consuming您自己的production Which is in a way blocking.这在某种程度上是阻塞的。

In general you should never subscribe in a webflux application, unless your application for example calls an api and then consumes the response (for instance saves it in a database etc).通常,您永远不应该订阅 webflux 应用程序,除非您的应用程序例如调用 api 然后使用响应(例如将其保存在数据库中等)。 The one the initiates the call, is in general the subscriber .发起呼叫的通常是subscriber

I would rewrite the last part and drop the subscribe :我会重写最后一部分并放弃subscribe

return responseMono.flatMap(response -> {
        final string = doSomething(response);
        return OscarGuacResponse.aStringField(string);
    });

Also i see that in RequestUtil::safeDeserializez you return an Optional<T> i would instead look into returning either a Mono#empty , or Mono#error as the return type to be able to use the many error operators that are available in webflux for instance switchIfEmpty , onErrorContinue , defaultIfEmpty etc etc. There is an entire chapter on error handling in the reactor documentation.我还看到在RequestUtil::safeDeserializez你返回Optional<T>我会考虑返回Mono#emptyMono#error作为返回类型,以便能够使用 webflux 中可用的许多错误运算符例如switchIfEmptyonErrorContinuedefaultIfEmpty等。reactor 文档中有一整章是关于错误处理的。

Also maybe look into using flatMap instead of map in many places.也可以考虑在许多地方使用flatMap而不是map To understand the differences between these two you can look at this answer .要了解这两者之间的区别,您可以查看此答案

Also, when looking at performance later on, you should understand that when you measure webflux performance, you need to look at such things as memory footprint and number of threads, compared to non-blocking applications.此外,在稍后查看性能时,您应该了解,在测量 webflux 性能时,与非阻塞应用程序相比,您需要查看 memory 占用空间和线程数等内容。 You might not see any performance gain when it comes to speed, but instead see that the application uses a lot less threads which in turn means a smaller memory footprint which is a gain itself.在速度方面您可能看不到任何性能提升,而是看到应用程序使用更少的线程,这反过来意味着更小的 memory 占用空间,这本身就是一种增益。

Update:更新:

You are trying to code regular java when doing reactive programming which will not work.在进行反应式编程时,您正在尝试编写常规 java 代码,但这是行不通的。 Why your code is not working is because you are "breaking the chain" .为什么您的代码不起作用是因为您“打破了链条”

I wrote this without an IDE, so it might not compile, but you should get the understanding of it.我在没有 IDE 的情况下写了这个,所以它可能无法编译,但你应该了解它。 You always need to chain on the previous, and things like java streams etc are usually not needed in reactive programming.您总是需要链接上一个,并且在反应式编程中通常不需要像 java 流等。

public Mono<ServerResponse> endpoint(ServerRequest request) {
    final List<String> params = Arrays.asList(fieldFromRequest.split(","));
    return Flux.fromIterable(params)
               .flatMap(param -> nonBlockingClient.function(param))
               .collectList()
               .flatMap(list -> {
                   if (list.size() != params.size()) {
                       return ServerResponse.status(500).build();
                   }
                   return ServerResponse.ok().build();
               })
}

This is basic reactive programming and i HIGHLY suggest you go through the "getting started section" of the reactor documentation so you understand the basics, because if you are going to code regular java in a reactive application, you are going to have a bad time.这是基本的反应式编程,我强烈建议您通过反应器文档的“入门部分”使用 go,以便您了解基础知识,因为如果您要在反应式应用程序中编写常规 java,那么您将度过一段糟糕的时光.

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM