Odata プロトコルを使用して WebAPI 経由でコンテンツを一括エクスポートする必要があります。PushStreamContent を使用して、結果をデータベースから直接ストリーミングしようとしています。ローカル IIS インスタンスでサービスを実行するとうまく動作しますが、これをサーバーにプッシュすると、データがストリーミングされ、最後の 2 キロバイトで一時停止して「ハング」します。
ファイルサイズを追跡することでそれを確認しました。ローカルで実行すると 9094KB のファイルが取得され、同じコードをサーバーにデプロイすると 9092KB になり、接続が開いたままになり、転送が停止します。クライアントを強制終了してファイルを見ると、ストリームであった json が書き込みの途中で切断されていることがわかります。さらに、IIS で開いている接続を調べて、接続がまだアクティブであることを確認できます。
とにかく、PushStreamContent がデータの送信を停止し、ストリームを閉じないように見えるのはなぜでしょうか? エラーが発生した場合、ストリームと接続は閉じられます。
public HttpResponseMessage GetBulkExport(ODataQueryOptions<vwBulkExport> options)
{
var reportData = options.ApplyTo(dbContext.vwBulkExport, new ODataQuerySettings() { EnsureStableOrdering = false });
return new ResponseStreamer(Request).StreamAsync(reportData);
}
public class ResponseStreamer
{
private HttpRequestMessage request;
public ResponseStreamer(HttpRequestMessage request)
{
this.request = request;
}
public HttpResponseMessage StreamAsync(IQueryable data)
{
HttpResponseMessage response = request.CreateResponse();
response.Content = new PushStreamContent(
async (outputStream, httpContent, transportContext) =>
{
try
{
int counter = 0;
foreach (var item in data)
{
counter++;
string json = JsonConvert.SerializeObject(item);
var buffer = Encoding.UTF8.GetBytes(json);
await outputStream.WriteAsync(buffer, 0, buffer.Length);
if (counter == 10)
{
counter = 0;
await outputStream.FlushAsync();
}
}
}
finally
{
await outputStream.FlushAsync();
outputStream.Close();
outputStream.Dispose();
}
});
return response;
}
}
これが私のクライアントコードです
using (var writer = File.OpenWrite("C:\\temp\\" + Guid.NewGuid().ToString()))
{
var client = new RestClient("http://localhost");
var url = "/odata/BulkExport";
var request = new RestRequest(url);
request.AddHeader("authorization", string.Format("Bearer {0}", authToken));
request.ResponseWriter = (responseStream) => responseStream.CopyTo(writer);
var response = client.DownloadData(request);
}
アップデート
私は広範なテストを行ってきましたが、ストリームが閉じられない (したがって、最後のチャンクが送信されない) ことが起こっていると思います。上記のデータ反復を次のように変更することで、この結論に達しました。
for (int count = 0; count < 1000; count++) //foreach (var item in data)
{
string json = JsonConvert.SerializeObject(count.ToString()) + Environment.NewLine;
var buffer = Encoding.Default.GetBytes(json);
await outputStream.WriteAsync(buffer, 0, buffer.Length);
}
私が見ているのは、600の「行」しか返らないということです。ここでも 2 KB が不足しているように見えました。次に、ループをに変更し、count <601
ストリーム全体が転送されましたが、ストリームは決して閉じません。私が考えているのは、内部バッファのサイズが 4K 前後 (出力された 0 ~ 600 の数字) であり、ストリームが閉じていないため、最後の数バイトが受信されないことです。それは理にかなっていますか?
とにかく、なぜストリームが閉じないのでしょうか? 最終的にそれを持っていますが、スローされたエラーは見られません。
アップデート
より多くの情報を明らかにしました。HTTP 1.1 仕様では、チャンク ストリームは長さ 0 のチャンクで終了する必要があると規定されています。少し掘り下げた後、それが起こっているはずであることがわかりましたが、何らかの理由でそうではありません。私のクライアントでは、Connection: Keep-Alive
ヘッダーを削除して置き換えましたConnection: Close
が、同じ問題がありますが、(テストアプリをシャットダウンして) 接続を強制的に閉じるとすぐに、最後の数バイトがディスクに書き込まれ、すべて問題ありません。これが、長さゼロのチャンクが送信されていないことを知る方法です。
だから今、質問はなりました。ストリームを閉じたときに最後の長さ 0 のチャンクが送信されないのはなぜですか? 私が読んだことから、呼び出しHttpContext.Current.ApplicationInstance.CompleteRequest();
はリクエストを強制的に終了させ、そのチャンクを書き出す必要があります。これを finally ブロックの最後の行として追加し、デバッガーを使用して実行中であることを確認しました。ただし、そのチャンクはまだ設定されていません。
これらはすべて、IIS でホストされている私の開発マシンでは機能しますが、Web サーバーでは機能しないことを覚えておいてください。
私のマシンはWindows 10を実行しており、asp.net 5がインストールされており、IIS7はすべてデフォルトです。
WebサーバーはWindowsサーバーです(バージョンはわかりません)IIS8を実行していますが、asp.net 4.5しかインストールされていません。私の最初の予感は、これが問題であるということでした.asp.netフレームワークのバージョンが異なりますが、確認したところ、プロジェクトはASP.NET 4.5のみを対象としています。私はまだサーバーを更新しようとしていますが、4.5 をターゲットにしているので、何の役にも立たないと思います。