アップストリームサービス(Azure Blobサービス)を呼び出してデータをOutputStreamにプッシュする必要があります。次に、それを反転させて、それをクライアントにプッシュし、akkaを介してクライアントに戻す必要があります。 akkaがなければ(そしてサーブレットコードだけ)、ServletOutputStreamを取得してAzureサービスのメソッドに渡します。
私がつまずくことを試みることができる最も近い、そして明らかにこれは間違っている、このようなものです
Source<ByteString, OutputStream> source = StreamConverters.asOutputStream().mapMaterializedValue(os -> {
blobClient.download(os);
return os;
});
ResponseEntity resposeEntity = HttpEntities.create(ContentTypes.APPLICATION_OCTET_STREAM, preAuthData.getFileSize(), source);
sender().tell(new RequestResult(resposeEntity, StatusCodes.OK), self());
アイデアは、blobClient.download(os);を呼び出すことによって出力ストリームが入力されるように、上流のサービスを呼び出すことです。
ラムダ関数が呼び出されて戻るように見えますが、その後データや何かがないために失敗します。そのラムダ関数が機能するようになっているはずではないかのように、おそらく機能するオブジェクトを返すのでしょうか?わからない。
どうやってこれを行うのですか?
ここでの実際の問題は、Azure APIがバックプレッシャー用に設計されていないことです。出力ストリームがデータを追加する準備ができていないことをAzureに通知する方法はありません。別の言い方をすると、Azureがデータを消費するよりも速くデータをプッシュする場合、どこかに醜いバッファオーバーフローエラーが発生する必要があります。
この事実を受け入れて、私たちにできる次善の策は次のとおりです。
Source.lazySource
を使用して、ダウンストリームの要求があるときにのみデータのダウンロードを開始します(別名、ソースが実行されており、データが要求されています)。download
呼び出しを他のスレッドに入れて、ソースが返されないようにブロックせずに実行を継続するようにします。これを行う1つの方法は、Future
を使用することです(Javaのベストプラクティスが何であるかはわかりませんが、どちらの方法でも問題なく動作するはずです)。 、system.dispatcher
以外の実行コンテキストを選択する必要がある場合があります。これはすべて、download
がブロックされているかどうかによって異なります。Javaコードの形式が正しくない-ScalaでAkkaを使用しているため、これはすべてAkka Java APIおよびJava構文リファレンス。
ResponseEntity responseEntity = HttpEntities.create(
ContentTypes.APPLICATION_OCTET_STREAM,
preAuthData.getFileSize(),
// Wait until there is downstream demand to intialize the source...
Source.lazySource(() -> {
// Pre-materialize the outputstream before the source starts running
Pair<OutputStream, Source<ByteString, NotUsed>> pair =
StreamConverters.asOutputStream().preMaterialize(system);
// Start writing into the download stream in a separate thread
Futures.future(() -> { blobClient.download(pair.first()); return pair.first(); }, system.getDispatcher());
// Return the source - it should start running since `lazySource` indicated demand
return pair.second();
})
);
sender().tell(new RequestResult(responseEntity, StatusCodes.OK), self());
この場合のOutputStream
はSource
の「実体化された値」であり、ストリームが実行された(または実行中のストリームに「実体化された」)場合にのみ作成されます。 Source
をAkka HTTPに渡すと、後で実際にソースが実行されるため、実行は制御できません。
.mapMaterializedValue(matval -> ...)
は通常、実体化された値を変換するために使用されますが、実体化の一部として呼び出されるため、それを使用して、メッセージでmatvalを送信するなどの副作用を実行できます。ファンキーに見えても、必ずしも問題はありません。ラムダが完了するまで、ストリームは実体化を完了せず、実行状態になることを理解することが重要です。これは、download()
が別のスレッドでいくつかの作業を分岐してすぐに戻るのではなく、ブロックしている場合に問題が発生することを意味します。
ただし、別の解決策があります:Source.preMaterialize()
、ソースを具体化し、具体化された値のPair
と、すでに開始されたものを消費するために使用できる新しいSource
を提供しますソース:
_Pair<OutputStream, Source<ByteString, NotUsed>> pair =
StreamConverters.asOutputStream().preMaterialize(system);
OutputStream os = pair.first();
Source<ByteString, NotUsed> source = pair.second();
_
コードで考えるべき追加の事項がいくつかあることに注意してください。最も重要なのは、blobClient.download(os)
呼び出しが完了するまでブロックし、アクターからそれを呼び出す場合です。その場合、アクターがディスパッチャを枯渇させず、アプリケーションの他のアクターの実行を停止しません(Akkaのドキュメントを参照: https://doc.akka.io/docs/akka/current/typed/dispatchers.html#blocking-needs-careful -management )。