2016-07-08 1 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; 
    } 
} 

오류가 발생됩니다. 액세스 위반으로 충돌

  • 원인 iisexpress.exe (또는 실제 IIS에서 실행되는 경우 W3WP.EXE) :

    이 오류는 다음 중 하나가 발생합니다. (두 경우 모두, http://localhost:<port>/test가 호출되고) .

  • AccessViolationException이 발생하지만 Visual Studio에서는이를 catch하지만 외부 코드에서 발생하는 모든 작업을 수행 할 수 없습니다. 비주얼 스튜디오는 예외를 잡을 않습니다 때, 나는 참조 : 내 미들웨어를 사용하지 않는 경우에 분명히

    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로 놀고 있다는 사실과 관련이 있어야한다.

아이디어가 있으십니까?

답변

4

오, 내.

내가이 변화하는 것은 할 수있는 권리 일이 있는지 확실하지 않습니다,하지만 확실히 문제 해결 :

await ms.CopyToAsync(outStream); 

에를
ms.CopyTo(outStream); 

내 유일한 생각은 어떻게 든을 그 응용 프로그램은 비동기 호출이 복사를 완료하기 전에 MemoryStream을 닫는 중이었습니다.

+0

이 문제의 근본 원인을 찾아 냈습니까? context.response.WriteAsync (string)을 사용할 때도 동일한 문제가 발생합니다. 그것을 response.Write로 바꾸면됩니다. – Anders

+0

any이 오류가 발생하는 이유는 무엇입니까? 나는 뿌리가 무엇인지 알고 싶습니다. 라이브러리에서 수정할 수 있습니까? – Misiu

+0

근본 원인을 정확하게 알 수는 없지만 (물론 설계 상으로는) 완료 될 때까지 기다리지 않고 비동기 호출과 관련이 있다고 확신합니다. 따라서 복사본이 시작되지만 메서드가 끝나고 MemoryStream이 즉시 범위를 벗어나 가비지 수집되어 AccessViolation이 발생합니다. 이는 또한 오류가 다른 방식으로 나타나는 이유를 설명합니다. GC가 실제로 개체를 지웠는 지 아니면 더러워 졌는지에 따라 달라집니다. – fdmillion