【问题标题】:C#: modifying Owin response stream causes AccessViolationExceptionC#:修改 Owin 响应流导致 AccessViolationException
【发布时间】:2016-07-08 21:53:21
【问题描述】:

我正在尝试使用一些自定义的 Owin 中间件来修改(在这种情况下,完全替换)特定情况下的响应流。

每当我调用 确实 触发我的中间件来替换响应时,一切正常。仅当我拨打中间件未对其进行更改时才会出现此问题。此外,我只能在 not 被替换的 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(或 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 对象时导致问题发生,如第二类所示。

这是我的中间件类。它目前设置为在有人请求端点 /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 的事实有关。

有什么想法吗?

【问题讨论】:

    标签: c# owin owin-middleware


    【解决方案1】:

    哦,天哪。

    我不确定更改此设置是否正确,但它确实解决了问题:

    await ms.CopyToAsync(outStream);
    

    ms.CopyTo(outStream);
    

    我唯一的猜测是,在异步调用完成复制之前,应用程序以某种方式关闭了 MemoryStream,这是有道理的。

    【讨论】:

    • 你有没有找到这个问题的根本原因?使用 context.response.WriteAsync(string) 时我遇到了同样的问题。将其更改为 response.Write 有效。
    • 任何理想为什么会出现这个错误?我想知道根是什么。也许这可以在图书馆修复?
    • 我不太确定根本原因,但我很确定这与 Async 调用不等待复制完成有关(当然是设计使然)。所以复制开始了,但是方法结束了,MemoryStream 立即超出范围,被垃圾收集,然后发生 AccessViolation。这也可以解释为什么错误以不同的方式表现出来——它取决于 GC 是否真的清除了对象,或者它是否只是被标记为脏。
    • 工作就像一个魅力!谢谢。
    猜你喜欢
    • 2022-07-04
    • 1970-01-01
    • 1970-01-01
    • 2015-01-01
    • 2011-06-19
    • 1970-01-01
    • 1970-01-01
    • 2020-07-02
    • 1970-01-01
    相关资源
    最近更新 更多