カスタム Owin ミドルウェアを使用して、特定の状況で応答ストリームを変更 (この場合は完全に置き換え) しようとしています。
応答を置き換えるためにミドルウェアをトリガーする呼び出しを行うときはいつでも、すべてが正常に機能します。この問題は、ミドルウェアが変更を加えない呼び出しを行った場合にのみ発生します。さらに、置き換えられていないAPI 呼び出しが手動で作成された HttpResponseMessage オブジェクトを返す場合にのみ、エラーが発生するようになりました。
たとえば、次の API を呼び出します。
public class testController : ApiController
{
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK,new { message = "It worked." });
}
}
正常に動作しますが、このクラス:
public class testController : ApiController
{
public HttpResponseMessage Get()
{
HttpResponseMessage m = Request.CreateResponse();
m.StatusCode = HttpStatusCode.OK;
m.Content = new StringContent("It worked.", System.Text.Encoding.UTF8, "text/plain");
return m;
}
}
エラーが発生します。(どちらの場合も、http://localhost:<port>/test
が呼び出されています。)
このエラーにより、次のいずれかが発生します。
- iisexpress.exe (実際の IIS で実行されている場合は w3wp.exe) がアクセス違反でクラッシュします。
Visual Studio がキャッチする
AccessViolationException
をスローしますが、外部コードで発生するため何も実行できません。Visual Studio が例外をキャッチすると、次のように表示されます。An unhandled exception of type 'System.AccessViolationException' occurred in System.Web.dll Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
明らかに、ミドルウェアを有効にしない場合、問題はまったくありません。また、2 番目のクラスに示すように、手動で HttpResponseMessage オブジェクトを作成して返すときにのみ、問題を発生させることができました。
これが私のミドルウェアクラスです。現在/replace
、パイプライン内の他の何かがエンドポイントに何かを行ったかどうかに関係なく、誰かがエンドポイントを要求するたびに、応答ストリーム全体を単純に置き換えるように設定されています。
using Microsoft.Owin;
using Owin;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Newtonsoft.Json;
using AppFunc = System.Func<
System.Collections.Generic.IDictionary<string, object>,
System.Threading.Tasks.Task
>;
namespace TestOwinAPI
{
public class ResponseChangeMiddleware
{
AppFunc _next;
public ResponseChangeMiddleware(AppFunc next, ResponseChangeMiddlewareOptions opts)
{
_next = next;
}
public async Task Invoke(IDictionary<string,object> env)
{
var ctx = new OwinContext(env);
// create a new memory stream which will replace the default output stream
using (var ms = new MemoryStream())
{
// hold on to a reference to the actual output stream for later use
var outStream = ctx.Response.Body;
// reassign the context's output stream to be our memory stream
ctx.Response.Body = ms;
Debug.WriteLine(" <- " + ctx.Request.Path);
// allow the rest of the middleware to do its job
await _next(env);
// Now the request is on the way out.
if (ctx.Request.Path.ToString() == "/replace")
{
// Now write new response.
string json = JsonConvert.SerializeObject(new { response = "true", message = "This response will replace anything that the rest of the API might have created!" });
byte[] jsonBytes = System.Text.Encoding.UTF8.GetBytes(json);
// clear everything else that anything might have put in the output stream
ms.SetLength(0);
// write the new data
ms.Write(jsonBytes, 0, jsonBytes.Length);
// set parameters on the response object
ctx.Response.StatusCode = 200;
ctx.Response.ContentLength = jsonBytes.Length;
ctx.Response.ContentType = "application/json";
}
// In all cases finally write the memory stream's contents back to the actual response stream
ms.Seek(0, SeekOrigin.Begin);
await ms.CopyToAsync(outStream);
}
}
}
public static class AppBuilderExtender
{
public static void UseResponseChangeMiddleware(this IAppBuilder app, ResponseChangeMiddlewareOptions options = null )
{
if (options == null)
options = new ResponseChangeMiddlewareOptions();
app.Use<ResponseChangeMiddleware>(options);
}
}
public class ResponseChangeMiddlewareOptions
{
}
}
私は明らかなことをしました-一晩中RAMテストを行い(すべて問題ありません)、別のシステムを試しました(そこでも発生しました)。
さらに、エラーは一定ではなく、約半分の確率で発生します。つまり、多くの場合、1 つまたは 2 つの成功した要求を取得できますが、最終的にはエラーが発生します。
最後に、ミドルウェアのメモリ ストリーム コピーの直前にプログラムにブレークポイントを配置し、コードをゆっくりとステップ実行すると、エラーは発生しません。これは、ある種の競合状態に陥っているに違いないことを示しており、MemoryStreams で遊んでいるという事実に関連している必要があります。
何か案は?