SpringBoot と SpringCloud は、webFlux の WebClient を使用してファイルをアップロードおよびダウンロードし、ダウンロード中にバッファーする最大バイト数の制限を超えましたというエラーの解決策を示します。

プロジェクトのシナリオ:

最近行っているJavaバックエンドプロジェクトでは、まず他のサーバーから添付ファイルをダウンロードし、それをこのプロジェクトのMinIOサーバーにアップロードし、取得した添付ファイル情報を保存してからWebClientで実装する必要があります。

1. 基本的な紹介

1. Webクライアントとは

Spring 5 から、リアクティブ応答プログラミングが Spring に完全に導入されました。WebClient は、Spring WebFlux モジュールによって提供されるリアクティブ プログラミングに基づく、HTTP リクエスト用のノンブロッキング クライアント ツールです。
WebClient のリクエスト モードは非同期でノンブロッキングであるため、少数の固定スレッドで高度な同時 HTTP リクエストを処理できます。したがって、Spring 5 からは、HTTP サービス間の通信に以前の RestTemplate を WebClient で置き換えることを検討できます。

2. WebClient の利点
(1) WebClient は RestTemplate と比較して以下の利点があります。

ノンブロッキング、リアクティブで、より高い同時実行性とより少ないハードウェア リソースをサポートします。
Java 8 ラムダを利用する機能的な API を提供します。
同期シナリオと非同期シナリオの両方がサポートされています。
サーバーからの上りまたは下りのストリーミングをサポートします。
(2) RestTemplate はノンブロッキング アプリケーションでの使用には適していないため、Spring WebFlux アプリケーションでは常に WebClient を使用する必要があります。WebClient は、ほとんどの同時実行性の高いシナリオや、相互依存する一連のリモート呼び出しを構成する場合にも Spring MVC の最初の選択肢となるはずです。

WebClient を使用できるように、pom.xml ファイルを編集して Spring WebFlux 依存関係を追加します。

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>io.projectreactor.netty</groupId>
            <artifactId>reactor-netty-http</artifactId>
        </dependency>

 

2. WebClient インスタンスを作成する

Builder を使用して作成する (推奨)

次に、 builder() を使用して WebClient.Builder を返し、 build を呼び出して WebClient オブジェクトを返します。そして、オブジェクトを使用してネットワーク インターフェイスを要求し、最後に結果を文字列の形式で出力します。

注: 返される型は WebClient ではなく WebClient.Builder であるため、返された WebClient.Builder を通じていくつかの構成パラメータ (baseUrl、ヘッダー、Cookie など) を設定し、build を呼び出して WebClient オブジェクトを返すことができます。

WebClient webClient = WebClient.builder()
        .baseUrl("http://better.blog.csdn.net")
        .defaultHeader(HttpHeaders.USER_AGENT,"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)")
        .defaultCookie("ACCESS_TOKEN", "test_token")
        .build();
 
Mono<String> mono = webClient
        .get() // GET 请求
        .uri("/posts/1")  // 请求路径
        .retrieve() // 获取响应体
        .bodyToMono(String.class); //响应数据类型转换
         
System.out.println(mono.block());

 ファイルのダウンロードとファイルのアップロードの詳細な例

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.FileUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;

import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;

/**
 * 文件 工具类
 * 工具类编写规范
 * 工具类定义为public final class 工具类名称;
 * 工具类名称的命名应为Utils结尾,例如 DateUtils;
 * 工具类的构造方法最好为private,及这个类无法实例化,无法new出一个工具类;
 * 工具类的方法应为static修饰的,表示静态方法,可直接通过类名.方法名的格式去访问;
 * 工具类应使用单例模式,不要使用new,重复创建对象,浪费系统资源
 *
 * @author QC 班长
 * @version V1.0
 * @since 2023/7/5 14:37
 */
public final class FileUtil {
    private static FileUtil fileUtil = null;

    static {
        // 类加载创建时只会创建一个对象
        if (fileUtil == null) {
            fileUtil = new FileUtil();
        }
    }

    private FileUtil() {
    }

    private static final int TEN = 10;
    private static final int HUNDRED = 100;
    private static final int ONE_ZERO_TWO_FOUR = 1024;


    /**
     * 调用接口的-认证秘钥
     */
    private static final String AUTHORIZATION_VALUE = "xxx";

    /**
     * 下载文件
     * @param dowUrl 完整下载地址
     * @return 本地文件路径
     * @throws IOException 异常
     */
    public static String downloadFile(String dowUrl) throws IOException {
        // 记录下开始下载时的时间
        Instant now = Instant.now();
        // 创建 WebClient 对象
        WebClient webClient = WebClient.builder()
                .codecs(obj -> obj.defaultCodecs().maxInMemorySize(HUNDRED * ONE_ZERO_TWO_FOUR * ONE_ZERO_TWO_FOUR))
                .build();
        //发送请求
        ClientResponse response = webClient
                .get() // GET 请求
                .uri(dowUrl)  // 请求路径
                .accept(MediaType.APPLICATION_OCTET_STREAM)
                .exchange()
                .block(); // 获取响应体
        String fileName = "";

        // 从header中获取原始文件名
        if (response != null) {
            HttpHeaders httpHeaders = response.headers().asHttpHeaders();
            String content = httpHeaders.getFirst(HttpHeaders.CONTENT_DISPOSITION);
            if (content != null) {
                fileName = content.substring(content.lastIndexOf("filename=") + TEN, content.length() - 1);
            }
            //解码已编码的url字符串,将十六进制转换成中文。
            fileName = System.getProperty("user.dir") + File.separator + URLDecoder.decode(fileName, "UTF-8");
            File out = new File(fileName);
            // 将文件保存(文件名不变)
            Resource resource = response.bodyToMono(Resource.class).block();
            if (resource != null) {
                FileUtils.copyInputStreamToFile(resource.getInputStream(), out);
            }
        }
        System.out.println("文件下载完成,耗时:" + ChronoUnit.SECONDS.between(now, Instant.now()) + " 秒");
        return fileName;
    }

    /**
     * 上传文件
     * @param filePath 本地文件路径
     * @return 文件信息
     * @throws IOException 异常
     */
    public static String uploadFile(String filePath) throws IOException {
        // 记录下开始下载时的时间
        Instant now = Instant.now();
        FileSystemResource upload = new FileSystemResource(new File(filePath));
        // 封装请求参数
        MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
        param.add("file", upload);
        param.add("formCode", "form_5964");
        param.add("bucketName", "education");
        WebClient webClient = WebClient.builder().build();
        String response = webClient
                .post() // POST 请求
                .uri("http://10.1.1.204/gw/form-api/file/upload")  // 请求路径
                .header(HttpHeaders.AUTHORIZATION, AUTHORIZATION_VALUE)
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(BodyInserters.fromMultipartData(param))
                .retrieve() // 获取响应体
                .bodyToMono(String.class).block(); //响应数据类型转换
        System.out.println("文件下载完成,耗时:" + ChronoUnit.SECONDS.between(now, Instant.now()) + " 秒");
        // 将 JSON 响应转换为 Map
        Map result = JSON.parseObject(response, Map.class);
        if (result != null) {
            //获取文件信息并返回
            JSONObject jsonObject = (JSONObject) result.get("data");
            return jsonObject.getString("uid");
        } else {
            return "";
        }

    }

    public static void main(String[] args) throws IOException {
        String fileName = downloadFile("http://ibeastidea.com/file/downFile/a86727dfe5f449f29ae262d50aad98");
        System.out.println(fileName);
        String uid = uploadFile(fileName);
    }
}

問題の説明

ダウンロード中に報告されるエラーは次のとおりです

org.springframework.core.io.buffer.DataBufferLimitException: 
Exceeded limit on max bytes to buffer : 262144
    at org.springframework.core.io.buffer.LimitedDataBufferList.raiseLimitException(LimitedDataBufferList.java:98)
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 


 

原因分析:

DataBufferLimitException: バッファーへの最大バイト数の制限を超えました:262144。アップロードできるのは 256KB 未満のファイルのみであることがわかりましたが、現在ファイルは 10M 以上です。応答データを受信するためのバッファーが十分に大きくないため、制限は 256k です。(AbstractDataBufferDecoder クラスにはハードコーディングされた制限があります)

アップロードできるのは 256KB 未満のファイルだけですが、今では気軽に 10M 以上のファイルもアップロードできるようになりました。


解決:

codecsメソッドを使用して、 WebClient.build 中にmaxInMemorySize 値を変更できます

 

    private static final int HUNDRED = 100;
    private static final int ONE_ZERO_TWO_FOUR = 1024;

    // 创建 WebClient 对象
    WebClient webClient = WebClient.builder()
    //修改maxInMemorySize的缓存值,默认是256k。修改为100MB
                .codecs(obj -> obj.defaultCodecs().maxInMemorySize(HUNDRED * ONE_ZERO_TWO_FOUR * ONE_ZERO_TWO_FOUR))
                .build();

 


参考文献

 0. SpringBoot - ネットワーク リクエスト クライアント WebClient の使用方法の詳細_springboot webclient_weixin_35688430 のブログ-CSDN ブログ

1. SpringBoot - ネットワークリクエストクライアントWebClient使い方 詳細解説5(ファイルアップロード)

2. spring-boot – Spring BootがWebClient経由であるAPIから別のAPIにファイルを渡したりアップロードしたりする - コードログ 

3.春 - レスポンシブ Web クライアントについて集中的に語る その 4 - ファイルのアップロードとダウンロード - 個人的な記事 - SegmentFault 思否 

4. 【WEB連載】WebClientファイルアップロード | 灰色のブログ 

5. SpringBoot - ネットワーク リクエスト クライアント WebClient (ファイル処理)_webclient が zip_weixin_35688430 のブログを受信 - CSDN ブログ 

6. WebFlux_webfux_Cheat Coke ブログ - CSDN ブログ 

7. WebFlux フレームワーク webClient リクエスト データ エラー: バッファーの最大バイト数の制限を超えました: 262144_Reality、あまりにも残酷なブログ-CSDN ブログ 

8. DataBufferLimitException: バッファーへの最大バイト数の制限を超えたエラーがスプリング ブートで表示される: 262144_spring_私は本当にスパイシーなストリップを食べません - HUAWEI CLOUD Developer Alliance 

おすすめ

転載: blog.csdn.net/qq_35624642/article/details/131551364