当前位置: 首页 > 知识库问答 >
问题:

Spring WebFlux Web客户端-迭代分页REST API

邵俊才
2023-03-14

我需要从可分页REST API的所有页面获取项目。我还需要开始处理项目,只要它们可用,不需要等待所有页面加载。为了做到这一点,我使用了Spring WebFlux及其WebClient,并希望返回Flux

  • 当前窗口的大小
  • 当前窗口中的剩余时间
  • 在窗口中请求配额
  • 请求留在当前窗口中

对单个页面请求的响应如下所示:

{
    "data": [],
    "meta": {
      "pagination": {
        "total": 10,
        "current": 1
      }
    }
}

数据数组包含实际项目,而元对象包含分页信息。

我当前的解决方案首先执行一个“虚拟”请求,只是为了获取总页数和速率限制。

Mono<T> paginated = client.get()
    .uri(uri)
    .exchange()
    .flatMap(response -> {                  
        HttpHeaders headers = response.headers().asHttpHeaders();

        Limits limits = new Limits();
        limits.setWindowSize(headers.getFirst("X-Window-Size"));
        limits.setWindowRemaining(headers.getFirst("X-Window-Remaining"));
        limits.setRequestsQuota(headers.getFirst("X-Requests-Quota");
        limits.setRequestsLeft(headers.getFirst("X-Requests-Remaining");

        return response.bodyToMono(Paginated.class)
                .map(paginated -> { 
                    paginated.setLimits(limits);
                    return paginated;
                });
    });

之后,我发出一个包含页码的流量,对于每个页面,我执行一个REST API请求,每个请求都被延迟了足够长的时间,因此不会超过限制,并返回一个提取项目的流量:

return paginated.flatMapMany(paginated -> {
    return Flux.range(1, paginated.getMeta().getPagination().getTotal())
            .delayElements(Duration.ofMillis(paginated.getLimits().getWindowRemaining() / paginated.getLimits().getRequestsQuota()))
            .flatMap(page -> {
                return client.get()
                        .uri(pageUri)
                        .retrieve()
                        .bodyToMono(Item.class)
                        .flatMapMany(p -> Flux.fromIterable(p.getData()));
            });
});

这确实有效,但我对此不满意,因为:

  • 它执行初始“虚拟”请求以获取页数,然后重复相同的请求以获取实际数据
  • 它仅在初始请求时获取速率限制,并假设限制不会更改(例如,它是唯一使用API的限制)-这可能不是真的,在这种情况下,它将得到一个超出限制的错误

因此,我的问题是如何重构它,使其不需要初始请求(而是从第一个请求中获取限制、页码和数据,并继续遍历所有页面,同时更新(并尊重)限制)。

共有1个答案

微生毅然
2023-03-14

我认为这段代码可以满足您的要求。其想法是创建一个流量来调用资源服务器,但在处理响应的过程中,在该流量上添加一个新事件,以便能够调用下一页。

代码由以下部分组成:

一个简单的包装器,包含要调用的下一页和在执行调用之前要等待的延迟

private class WaitAndNext{
    private String next;
    private long delay;
}

一个将进行HTTP调用并处理响应的FLuxProcess:

FluxProcessor<WaitAndNext, WaitAndNext> processor= DirectProcessor.<WaitAndNext>create();
FluxSink<WaitAndNext> sink=processor.sink();

processor
    .flatMap(x-> Mono.just(x).delayElement(Duration.ofMillis(x.delay)))
    .map(x-> WebClient.builder()
    .baseUrl(x.next)
    .defaultHeader("Accept","application/json")
    .build())
    .flatMap(x->x.get()        
                 .exchange()
                 .flatMapMany(z->manageResponse(sink, z))
            )
    .subscribe(........);

我用一个只管理响应的方法拆分代码:它只是打开您的数据并向接收器添加一个新事件(在给定延迟后调用下一页的事件)

private Flux<Data> manageResponse(FluxSink<WaitAndNext> sink, ClientResponse resp) {

    if (resp.statusCode()!= HttpStatus.OK){
        sink.error(new IllegalStateException("Status code invalid"));
    }

    WaitAndNext wn=new WaitAndNext();
    HttpHeaders headers=resp.headers().asHttpHeaders();
    wn.delay= Integer.parseInt(headers.getFirst("X-Window-Remaining"))/ Integer.parseInt(headers.getFirst("X-Requests-Quota"));

    return resp.bodyToMono(Item.class)
        .flatMapMany(p -> {
            if (p.paginated.current==p.paginated.total){
                sink.complete();
            }else{
                wn.next="https://....?page="+(p.paginated.current+1);
                sink.next(wn);
            }
            return Flux.fromIterable(p.getData());
        });
}

现在我们只需要通过无延迟地调用第一页的检索来初始化系统:

WaitAndNext wn=new WaitAndNext();
wn.next="https://....?page=1";
wn.delay=0;
sink.next(wn);
 类似资料:
  • 问题内容: 我正在使用php,mysql进行搜索,过滤操作。 我的分页课程是 我已经使用以下复选框执行了过滤器: 过滤器的Javascript / ajax代码 process.php文件 我返回分页的结果,但是当我单击页码时,将我带到process.php,因为分页类使用 $ _SERVER [PHP_SELF] 如何在不更改页面url的情况下对结果进行分页,即使用ajax实施。我不能做太多更改

  • 问题内容: 我将GWT用于UI,将Hibernate / Spring用于业务层。以下GWT小部件用于显示记录。(http://collectionofdemos.appspot.com/demo/com.google.gwt.gen2.demo.scrolltable.PagingScrollTableDemo /PagingScrollTableDemo.html )。我认为排序是在客户端完成

  • 客户端分享 对接前提 1.需要APP本身有客户端分享功能和对应的分享接口 2.在管理后台配置好并开启分享功能 分享定制 如果开发者已有自己的一套H5分享接口,可以联系兑吧技术支持,由技术支持根据分享接口要求做定制。请联系兑吧技术支持协助配置。 分享案例 1.客户端分享兑吧活动 2.分享结果 3.点击分享跳转兑吧活动页 4.点击参与跳转下载页

  • 问题内容: 我想基于其api实现Google地图。我想添加一个基于坐标的路径。因此,我从模型中获取了坐标,并希望在对象上进行迭代以用此点填充地图。在我的Jade模板中,我包含api js代码,如下所示: 问题是:玉呈现此片段 就像在jade模板源中一样…-如果不被解析!有任何想法吗? 谢谢! 问题答案: 整个脚本标签(在其下缩进的所有内容)将通过原始文件传递,而无需进一步解析。Jade不会HTML

  • Java import java.io.IOException; import java.net.URLEncoder; import java.security.MessageDigest; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import jav

  • 我想在一些计算机之间建立点对点连接,这样用户就可以在没有外部服务器的情况下聊天和交换文件。我最初的想法如下: 我在服务器上制作了一个中央服务器插座,所有应用程序都可以连接到该插座。此ServerSocket跟踪已连接的套接字(客户端),并将新连接的客户端的IP和端口提供给所有其他客户端。每个客户端都会创建一个新的ServerSocket,所有客户端都可以连接到它。 换句话说:每个客户端都有一个Se