C#:修改 Owin 响应流导致 AccessViolationException
C#: modifying Owin response stream causes AccessViolationException
我正在尝试使用一些自定义的 Owin 中间件来修改(在本例中,完全替换)特定情况下的响应流。
每当我调用 确实 触发我的中间件来替换响应时,一切正常。仅当我进行中间件未更改的调用时才会出现问题。此外,我只能在被替换为 not 的 API 调用返回手动创建的 HttpResponseMessage 对象时发生错误。
例如调用这个 API:
public class testController : ApiController
{
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK,new { message = "It worked." });
}
}
工作正常,但是这个 class:
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(或 w3wp.exe,如果 运行 在实际 IIS 中)因访问冲突而崩溃。
抛出一个 AccessViolationException
,Visual Studio 捕获但无法执行任何操作,因为它出现在外部代码中。当 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.
显然,如果我不启用我的中间件,我就完全没有问题。此外,我只能在手动创建和返回 HttpResponseMessage 对象时导致问题发生,如第二个 class 所示。
这是我的中间件 class。它目前设置为只要有人请求端点 /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 测试(一切都很好),并在另一个系统上进行了尝试(它也发生在那里)。
此外,错误并不一致 - 大约有一半的时间会出现。换句话说,我经常可以通过一两个成功的请求,但最终还是出错了。
最后,如果我在中间件中的内存流复制之前在我的程序中放置一个断点,然后慢慢地单步执行代码,就不会出现错误。这向我表明我一定遇到了某种竞争条件,而且它必须与我正在玩 MemoryStreams 的事实有关。
有什么想法吗?
天啊
我不确定更改此设置是否正确,但它确实解决了问题:
await ms.CopyToAsync(outStream);
至
ms.CopyTo(outStream);
我唯一的猜测是应用程序在异步调用完成复制 MemoryStream 之前以某种方式关闭了它,这是有道理的。
我正在尝试使用一些自定义的 Owin 中间件来修改(在本例中,完全替换)特定情况下的响应流。
每当我调用 确实 触发我的中间件来替换响应时,一切正常。仅当我进行中间件未更改的调用时才会出现问题。此外,我只能在被替换为 not 的 API 调用返回手动创建的 HttpResponseMessage 对象时发生错误。
例如调用这个 API:
public class testController : ApiController
{
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK,new { message = "It worked." });
}
}
工作正常,但是这个 class:
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(或 w3wp.exe,如果 运行 在实际 IIS 中)因访问冲突而崩溃。
抛出一个
AccessViolationException
,Visual Studio 捕获但无法执行任何操作,因为它出现在外部代码中。当 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.
显然,如果我不启用我的中间件,我就完全没有问题。此外,我只能在手动创建和返回 HttpResponseMessage 对象时导致问题发生,如第二个 class 所示。
这是我的中间件 class。它目前设置为只要有人请求端点 /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 测试(一切都很好),并在另一个系统上进行了尝试(它也发生在那里)。
此外,错误并不一致 - 大约有一半的时间会出现。换句话说,我经常可以通过一两个成功的请求,但最终还是出错了。
最后,如果我在中间件中的内存流复制之前在我的程序中放置一个断点,然后慢慢地单步执行代码,就不会出现错误。这向我表明我一定遇到了某种竞争条件,而且它必须与我正在玩 MemoryStreams 的事实有关。
有什么想法吗?
天啊
我不确定更改此设置是否正确,但它确实解决了问题:
await ms.CopyToAsync(outStream);
至
ms.CopyTo(outStream);
我唯一的猜测是应用程序在异步调用完成复制 MemoryStream 之前以某种方式关闭了它,这是有道理的。