问题
I'm attempting to use some custom Owin middleware to modify (in this case, completely replace) the response stream in specific circumstances.
Anytime I make a call that does trigger my middleware to replace the response, everything works properly. The problem only occurs when I make a call that my middleware does not make changes to. Additionally, I have only been able to get the error to occur when the API call that is not being replaced is returning a manually created HttpResponseMessage object.
For example calling this API:
public class testController : ApiController
{
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK,new { message = "It worked." });
}
}
works fine, but this 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;
}
}
causes the error to occur. (In both cases, http://localhost:<port>/test
is being called.)
The error causes either of the following:
- Causes iisexpress.exe (or w3wp.exe if running in actual IIS) to crash with an Access Violation.
Throws an
AccessViolationException
that Visual Studio catches but is unable to do anything with as it occurs in external code. When Visual Studio does catch the exception, I see: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.
Obviously if I do not enable my middleware I do not have the issue at all. Also I have only been able to cause the issue to occur when manually creating and returning an HttpResponseMessage object as shown in the second class.
Here is my middleware class. Its currently set to simply replace the entire response stream any time someone requests the endpoint /replace
regardless of if anything else in the pipeline has done anything to it.
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
{
}
}
I've done the obvious - a full night of RAM testing (all good), and trying on another system (it occurred there too).
Additionally, the error is not consistent - it occurs about half the time. In other words, often I can get one or two successful requests through, but eventually, the error occurs.
Finally, if I put a breakpoint in my program right before the memory stream copy in my middleware, and slowly step through the code, the error never occurs. This indicates to me that I must be hitting some kind of race condition, and it has to be related to the fact that I'm playing with MemoryStreams.
Any ideas?
回答1:
Oh, my.
I'm not sure if changing this is the right thing to do, but it definitely fixed the problem:
await ms.CopyToAsync(outStream);
to
ms.CopyTo(outStream);
My only guess is that somehow the app was closing the MemoryStream before the async call had completed copying it, which would make sense.
来源:https://stackoverflow.com/questions/38275886/c-modifying-owin-response-stream-causes-accessviolationexception