2016-07-08 3 views
3

Я пытаюсь использовать какое-то пользовательское промежуточное ПО Owin для изменения (в данном случае, полностью заменить) потока ответов в определенных обстоятельствах.C#: изменение потока отклика Owin вызывает AccessViolationException

В любое время, когда я звоню, делает запускает мое промежуточное программное обеспечение, чтобы заменить ответ, все работает правильно. Проблема возникает только тогда, когда я делаю вызов, с которым мое промежуточное ПО не вносит изменений. Кроме того, мне удалось получить ошибку только тогда, когда вызов 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 
    { 

    } 
} 

Я сделал очевидное - полная ночь тестирования оперативной памяти (все хорошо), и пытаются на другую систему (это произошло там тоже).

Кроме того, ошибка не согласуется - это происходит примерно в половине случаев. Другими словами, часто я могу получить один или два успешных запроса, но, в конце концов, возникает ошибка.

Наконец, если я поставил точку останова в своей программе прямо перед тем, как поток памяти будет копироваться в моем промежуточном программном обеспечении, и медленно пройдите через код, ошибка никогда не произойдет. Это указывает на то, что я должен бить какое-то состояние гонки, и это должно быть связано с тем, что я играю с MemoryStreams.

Любые идеи?

ответ

4

О, мой.

Я не уверен, если изменение это право вещь, чтобы сделать, но это определенно устранило проблему:

await ms.CopyToAsync(outStream); 

к

ms.CopyTo(outStream); 

Моя только догадка, что как-то приложение закрывало MemoryStream до того, как асинхронный вызов завершил его копирование, что имеет смысл.

+0

Вы когда-нибудь узнавали причину этого? У меня такая же проблема при использовании context.response.WriteAsync (string). Изменение его на response.Write работает. – Anders

+0

любой Идеально, почему эта ошибка возникает? Я хотел бы знать, каковы корни. Может быть, это может быть исправлено в библиотеке? – Misiu

+0

Я не совсем уверен в первопричине, но я уверен, что это связано с вызовом Async, не ожидающим завершения копирования (по дизайну, конечно). Таким образом, копия запускается, но затем метод заканчивается, и MemoryStream сразу же выходит из сферы действия, собирается мусор, а затем происходит AccessViolation. Это также объясняет, почему ошибка проявлялась по-разному - она ​​зависела от того, действительно ли GC фактически очистил объект или был ли он просто загрязнен. – fdmillion