C#: modifying Owin response stream causes AccessViolationException

余生长醉 提交于 2019-12-12 14:16:25

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!