3

この質問を CXF リストに投稿しましたが、運がありませんでした。それでは、行きましょう。大きなファイルをリモート サーバー (仮想マシン ディスクと考えてください) にアップロードしようとしています。そのため、アップロード要求を受け入れる安らかなサービスがあります。アップロードのハンドラーは次のようになります。

@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Path("/doupload")
public Response receiveStream(MultipartBody multipart) {
    List<Attachment> allAttachments = body.getAllAttachments();
    Attachment att = null;
    for (Attachment b : allAttachments) {
        if (UPLOAD_FILE_DESCRIPTOR.equals(b.getContentId())) {
            att = b;
        }
    }
    Assert.notNull(att);
    DataHandler dh = att.getDataHandler();
    if (dh == null) {
        throw new WebApplicationException(HTTP_BAD_REQUEST);
    }
    try {
        InputStream is = dh.getInputStream();
        byte[] buf = new byte[65536];
        int n;
        OutputStream os = getOutputStream();
        while ((n = is.read(buf)) > 0) {
            os.write(buf, 0, n);
        }
        ResponseBuilder rb = Response.status(HTTP_CREATED);
        return rb.build();
    } catch (IOException e) {
        log.error("Got exception=", e);
        throw new WebApplicationException(HTTP_INTERNAL_ERROR);
    } catch (NoSuchAlgorithmException e) {
        log.error("Got exception=", e);
        throw new WebApplicationException(HTTP_INTERNAL_ERROR);
    } finally {}

}

このコードのクライアントはかなり単純です。

public void sendLargeFile(String filename) {
    WebClient wc = WebClient.create(targetUrl);
    InputStream is = new FileInputStream(new File(filename));
    Response r = wc.post(new Attachment(Constants.UPLOAD_FILE_DESCRIPTOR,
        MediaType.APPLICATION_OCTET_STREAM, is));
}

コードは機能的には問題なく動作します。パフォーマンスに関しては、ハンドラー (receiveStream() メソッド) がストリームから最初のバイトを取得する前に、実際にストリーム全体が一時ファイルに (CachedOutputStream を使用して) 保持されることに気付きました。残念ながら、これは私の目的には受け入れられません。

  • 私のハンドラーは単純に着信バイトをバックエンド ストレージ システム (仮想マシン ディスク リポジトリ) に渡します。ディスク全体がキャッシュに書き込まれ、再度読み取られるまで待機すると、多くのリソースが占有され、多くの時間がかかります。スループットを低下させます。
  • アプリはクラウドで実行されており、ブロックの読み取り/書き込みごとにクラウド プロバイダーが課金するため、ブロックの書き込みと再度の読み取りに関連するコストが発生します。
  • すべてのバイトがローカル ディスクに書き込まれるため、サービス VM には、アップロードされるすべてのストリームの合計サイズに対応できる十分なディスク容量が必要です (つまり、それぞれ 100 GB のアップロードが 10 回ある場合、キャッシュするためだけに 1 TB のディスクが必要です)コンテンツ)。サービス VM のサイズが劇的に増大し、クラウド プロバイダーはプロビジョニングされたディスク サイズに対しても課金するため、これも余分な費用がかかります。

これらすべてを考慮して、HTTP InputStream (またはできるだけそれに近い) を使用して、そこから添付ファイルを直接読み取り、後で処理する方法を探しています。私は質問が次のいずれかに変換されると思います:

ここで同様の質問を見つけました。解決策には、CXF 2.2.3 以降を使用するように記載されていますが、私は 2.4.4 を使用しています (そして 2.7.0 で試しました) 運がありません。

ありがとう。

4

2 に答える 2

3

論理的には不可能だと思います(CXFでも他の場所でも)。を呼び出しています。getAllAttachements()これは、サーバーが HTTP 入力ストリームからそれらに関する情報を収集する必要があることを意味します。これは、 MIME解析のためにストリーム全体をメモリに格納する必要があることを意味します。

あなたの場合、ストリームを直接操作し、MIME 解析を自分で行う必要があります。

public Response receiveStream(InputStream input) {

これで、入力を完全に制御できるようになり、バイト単位でメモリに取り込むことができます。

于 2012-11-28T20:56:34.653 に答える
1

私は洗練されていない方法で問題を解決することになりましたが、うまくいくので、私の経験を共有したいと思いました. 「標準」またはより良い方法があれば教えてください。

私はサーバー側を書いているので、送信された順序ですべての添付ファイルにアクセスし、ストリーミングされたときにそれらを処理していることを知っていました。サーバー側で「@SequentialAttachmentProcessing」という新しいアノテーションを作成し、上記のメソッドにアノテーションを付けました。

また、リンクされたリストのように機能する SequentialAttachment と呼ばれる、Attachment のサブクラスを作成しました。現在の添付ファイルをスキップする skip() メソッドがあり、添付ファイルが終了すると、 hasMore() メソッドが別の添付ファイルがあるかどうかを通知します。

次に、次のように動作するカスタム multipart/form-data プロバイダーを作成しました。ターゲット メソッドが上記のように注釈付けされている場合は添付ファイルを処理し、そうでない場合はデフォルト プロバイダーを呼び出して処理を行います。私のプロバイダーによって処理されると、常に多くても 1 つの添付ファイルが返されます。したがって、疑いのない処理方法に誤解を招く可能性があります。ただし、サーバーの作成者はメソッドに「@SequentialAttachmentProcessing」という注釈を付けている必要があるため、それが何を意味するかを知っている必要があるため、許容できると思います。

その結果、 receiveStream() メソッドの実装は次のようになります。

@POST
@SequentialAttachmentProcessing
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Path("/doupload")
public Response receiveStream(MultipartBody multipart) {
    List<Attachment> allAttachments = body.getAllAttachments();
    Assert.isTrue(allAttachments.size() <= 1);
    if (allAttachment.size() > 0) {
        Attachment head = allAttachments.get(0);
        Assert.isTrue(head instanceof SequentialAttachment);
        SequentialAttachment att = (SequentialAttachment) head;
        while (att != null) {
            DataHandler dh = att.getDataHandler();
            InputStream is = dh.getInputStream();
            byte[] buf = new byte[65536];
            int n;
            OutputStream os = getOutputStream();
            while ((n = is.read(buf)) > 0) {
                os.write(buf, 0, n);
            }
            if (att.hasMore()) {
                att = att.next();
            }
        }
    }
}

これで当面の問題は解決しましたが、これを行う標準的な方法が必要であると私はまだ信じています。これが誰かに役立つことを願っています。

于 2012-12-07T16:55:51.970 に答える