Я пытаюсь использовать какое-то пользовательское промежуточное ПО 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.
Любые идеи?
Вы когда-нибудь узнавали причину этого? У меня такая же проблема при использовании context.response.WriteAsync (string). Изменение его на response.Write работает. – Anders
любой Идеально, почему эта ошибка возникает? Я хотел бы знать, каковы корни. Может быть, это может быть исправлено в библиотеке? – Misiu
Я не совсем уверен в первопричине, но я уверен, что это связано с вызовом Async, не ожидающим завершения копирования (по дизайну, конечно). Таким образом, копия запускается, но затем метод заканчивается, и MemoryStream сразу же выходит из сферы действия, собирается мусор, а затем происходит AccessViolation. Это также объясняет, почему ошибка проявлялась по-разному - она зависела от того, действительно ли GC фактически очистил объект или был ли он просто загрязнен. – fdmillion