C#: modifying Owin response stream causes AccessViolationException - c#

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?

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.

Related

How to reset PipeReader to be able to re-read request body in ASP.NET Core

I have some ASP.NET Core middleware (an AuthorizationHandler) that needs to access the body of the request to perform an authorization check. I use the new pipelines APIs to access the request body. The code looks like this:
PipeReader bodyReader = httpContext.Request.BodyReader;
ReadResult readResult = await bodyReader.ReadAsync();
ReadOnlySequence<byte> readResultBuffer = readResult.Buffer;
var utf8JsonReader = new Utf8JsonReader(bytes);
while (utf8JsonReader.Read()) { ... }
When this code has run, the body stream is read. The controller that I want to call throws a validation error because it doesn't see a request body because it was already read.
So how do I reset the PipeReader so that the request body can be re-read?
I know that when you do not use BodyReader but Body, you can use EnableBuffering to enable request re-reads. However, when using pipelines, this no longer works (or I'm doing something else wrong).
I create an issue yesterday. See jkotalik's comment :
By passing in readResult.Buffer.Start for consumed, you are saying that you haven't consumed any of the ReadResult. By passing in readResult.Buffer.End, you are saying you have examined everything, which tells the Pipe to not free any buffers and the next time ReadAsync returns, more data would be in the buffer
This inspires me to invoke bodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.Start); to reset the examined position.
For example, your middleware can be written as below :
async Task IMiddleware.InvokeAsync(HttpContext httpContext, RequestDelegate next)
{
if(httpContext.Request.Method != HttpMethods.Post){
return;
}
var bodyReader = httpContext.Request.BodyReader; // don't get it by `PipeReader.Create(httpContext.Request.Body);`
// demo: read all the body without consuming it
ReadResult readResult;
while(true){
readResult = await bodyReader.ReadAsync();
if(readResult.IsCompleted) { break; }
// don't consume them, but examine them
bodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
}
// now all the body payload has been read into buffer
var buffer = readResult.Buffer;
Process(ref buffer); // process the payload (ReadOnlySequence)
// Finally, reset the EXAMINED POSITION here
bodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.Start);
await next(httpContext);
}
void Process(ref ReadOnlySequence<byte> buffer){
var utf8JsonReader = new Utf8JsonReader(buffer);
while (utf8JsonReader.Read()) {
Console.WriteLine($"{utf8JsonReader.TokenType}");
...
}
}
(The Key is the AdvanceTo() can not only forward the consumed position, but also can change the examined position)
I've worked on reading and resetting the request body in a controller's OnActionExecuting method. This is what worked for me:
httpContext.Request.Body.Position = 0;
Maybe this does also work in a pipeline?
Easiest solution that can be applied in Middleware, for instance:
(tested on .Net Core 5)
context.Request.EnableBuffering();
//var formParseSuccessful = context.Request.Body read....
context.Request.Body.Seek(0, SeekOrigin.Begin);

Testing response.WriteAsync() in custom middleware

I have an ASP.NET Core API that I have written custom middleware for so that I can handle exceptions and write logs in a single spot. The middleware works as required when debugging via Kestrel and submitting a request from a browser or postman however in my test the response body is always a null stream.
Below is the middleware class and the test that I have written, the context.Response.WriteAsync(result) doesn't seem to flush the stream for some reason but I don't know why. Is anyone able to explain?
using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using Microsoft.Extensions.Logging;
using System.IO;
namespace APP.API.Middleware
{
public class ExceptionHandler
{
private readonly RequestDelegate request;
private readonly ILogger logger;
public ExceptionHandler(RequestDelegate request, ILogger<ExceptionHandler> logger)
{
this.request = request;
this.logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await request(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception ex)
{
HttpStatusCode statusCode = HttpStatusCode.InternalServerError;
logger.LogError(ex, "Fatal exception");
var result = JsonConvert.SerializeObject(new { error = ex.Message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)statusCode;
return context.Response.WriteAsync(result);
}
}
}
using System.IO;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace APP.Testing.Middleware
{
[TestClass]
public class ExceptionHandler
{
[TestMethod]
public void HandleException()
{
var exceptionHandler = new API.Middleware.ExceptionHandler((innerHttpContext) =>
{
throw new System.Exception("Test exception");
}, new NullLogger<API.Middleware.ExceptionHandler>());
var context = new DefaultHttpContext();
exceptionHandler.Invoke(context).Wait();
context.Response.Body.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(context.Response.Body);
var text = reader.ReadToEnd();
}
}
}
Welcome to Stack Overflow!
Your response body is empty, because you are writing to a NullStream (not to be confused with null value).
"A Stream with no backing store. Use Null to redirect output to a stream that will not consume any operating system resources. When the methods of Stream that provide writing are invoked on Null, the call simply returns, and no data is written. Null also implements a Read method that returns zero without reading data." - Docs
Default value of Body property of HttpResponse is precisely
the NullStream. In a real scenario when an HTTP request arrives, the NullStream is replaced with HttpResponseStream. You won't be able to use it on your own as its accessibility level is set to internal.
Solution
As unit testing is only simulating real scenario, you can just replace the NullStream with any type of stream you want, for example the MemoryStream:
var exceptionHandler = new ExceptionHandler((innerHttpContext) =>
{
throw new Exception("Test exception");
}, new NullLogger<ExceptionHandler>());
var context = new DefaultHttpContext();
context.Response.Body = new MemoryStream(); // <== Replace the NullStream
await exceptionHandler.Invoke(context);
context.Response.Body.Seek(0, SeekOrigin.Begin);
var reader = new StreamReader(context.Response.Body);
var text = reader.ReadToEnd();
Don't forget to add some asserts at the end of your unit tests. After all, you want to perform some checks, right?
Assert.IsFalse(string.IsNullOrEmpty(text));
EDIT #1
As #nkosi pointed out, unless you have a really good reason, you should always call asynchronous methods with await keyword:
await exceptionHandler.Invoke(context);
and mark the method definition with async and make it return a Task:
public async Task HandleException()
That way you are avoiding deadlocks.
Something also worth pointing out (but not a necessity) is a naming convention for testing classes. Obviously, you can name it how you like, but keep it mind that when your testing class have the same name as the class you want to test, you end up with unnecessary name ambiguity. Of course you can write full name with namespace (as you did), but with my lazy nature, that's just too much so I'm using different name for testing class, for example ExceptionHandlerTests.

.net core web application - what's using all this memory? Small enumerable vs big enumerable vs stream response

I'm trying to understand server memory usage when a web application sends a big response.
I generated a ~100 megabyte text file on disk just to act as some data to send. Note that I may not actually want to send a file back (ie, I won't always want to use a FileStreamResult in practise). The file is just a source of data for my experiment.
I have exposed three different get requests.
Small returns a tiny ActionResult<IEnumerable<string>>. This is my control.
Big returns the file contents as an ActionResult<IEnumerable<string>>.
Stream implements a custom IActionResult, and writes lines directly to the Response.Body.
When I run the web application I see the following results in visual studio diagnostic tools:
On startup, the process memory is 87 meg.
Hitting the /small url -> still ~87 meg.
Hitting the/big url -> quickly jumps to ~120 meg.
Hitting the /stream url -> slowly climbs to ~110 meg.
As far as I understand, kestrel has a response buffer of 64kb, and I think I have correctly decorated the methods to disable response caching. So, what is causing the memory consumption of the server to increase so much, and is there something I can do to ensure that the server doesn't have to use all this extra memory? I was hoping I could truly "stream" results down to the client without having server memory usage climb so much.
Also, I'm pretty new to asp net core mvc web application API's (what a mouthful!), so any other tips are welcome.
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace WebApplication1.Controllers
{
public static class EnumerableFile
{
public static IEnumerable<string> Lines
{
get
{
using (var sr = new StreamReader(#"g:\data.dat"))
{
string s = null;
while ((s = sr.ReadLine()) != null)
{
yield return s;
}
}
}
}
}
public class StreamResult : IActionResult
{
public Task ExecuteResultAsync(ActionContext context)
{
return Task.Run(
() =>
{
using (var sr = new StreamReader(#"g:\data.dat"))
{
string s = null;
while ((s = sr.ReadLine()) != null)
{
context.HttpContext.Response.Body.Write(System.Text.Encoding.UTF8.GetBytes(s));
}
}
}
);
}
}
[ApiController]
public class ValuesController : ControllerBase
{
[Route("api/[controller]/small")]
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
public ActionResult<IEnumerable<string>> Small()
{
return new string[] { "value1", "value2" };
}
[Route("api/[controller]/big")]
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
public ActionResult<IEnumerable<string>> Big()
{
return Ok(EnumerableFile.Lines);
}
[Route("api/[controller]/stream")]
[ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)]
public IActionResult Stream()
{
return new StreamResult();
}
}
}

ASP.Net WebApi do not return form data via Content

I don't know if it's a bug but i'm unable to get raw request on server side.
Consider following controller method:
[AllowAnonymous]
[Route("api/sayHello")]
[HttpPost]
public string SayHello([FromBody] string userName)
{
return $"Hello, {userName}.";
}
I call it via cUrl:
curl -X POST 'https://localhost:809/api/sayHello' --insecure -d "=userName"
It works fine.
Now I'm trying to add some logging. I add a global filter which is doing following:
public async Task LogFilterAction(HttpActionContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
LogFilterAction(context.ActionDescriptor?.ControllerDescriptor?.ControllerType,
context.ActionDescriptor?.ActionName,
context.Request?.RequestUri,
await GetDataAsString(context.Request?.Content),
context.Response?.StatusCode
);
}
private static async Task<string> GetDataAsString(HttpContent content)
{
if (content == null)
return null;
var contentBytes = await content.ReadAsByteArrayAsync();
return Encoding.UTF8.GetString(contentBytes);
}
But here is the problem: for unknown reason reason contentBytes are always an empty array. I see that it's actual length is 9 (it's length of =userName string)
Or Even
As you can see, ASP.Net has successfully request arguments, however, it doesn't return it's in raw manner. Stream has position=0, contentConsumed=false, and everything else is just fine. But i can't read data passed to the controller.
What's wrong here?
ASP.NET Web API reads the content only once, so at the time that you access the content stream, it has already been read and the stream is positioned at its end. Another attempt to read the content will return nothing.
However, in a small sample I was able to reset the stream and read it again:
private async Task<string> GetDataAsString(HttpContent content)
{
if (content == null)
return null;
using (var str = await content.ReadAsStreamAsync())
{
if (str.CanSeek)
str.Seek(0, System.IO.SeekOrigin.Begin);
using (var rdr = new StreamReader(str))
{
return rdr.ReadToEnd();
}
}
}
However, in order to avoid side effects, you might want to consider to use the ActionArguments property of the ActionContext. You can use this property to retrieve the values that are handed to the action for logging. This does not interfere with the internal plumbing of ASP.NET Web API.

Unit Testing Amazon S3

I have a fairly simple class that I'm trying to unit test. I'm very new to unit testing in general, and I'm not sure what I should be testing here.
The only test case that I can figure out how to code is a null argument of stream. Besides that, I'm not sure how to test the results of a PutObjectRequest or what else. If I should be using mocks here, how?
public class AmazonS3Service : IAmazonS3Service
{
private readonly Uri baseImageUrl;
private readonly Uri s3BaseUrl;
private readonly string imageBucket;
public AmazonS3Service()
{
imageBucket = ConfigurationManager.AppSettings["S3.Buckets.Images"];
s3BaseUrl = new Uri(ConfigurationManager.AppSettings["S3.BaseAddress"]);
baseImageUrl = new Uri(s3BaseUrl, imageBucket);
}
public Image UploadImage(Stream stream)
{
if (stream == null) throw new ArgumentNullException("stream");
var key = string.Format("{0}.jpg", Guid.NewGuid());
var request = new PutObjectRequest
{
CannedACL = S3CannedACL.PublicRead,
Timeout = -1,
ReadWriteTimeout = 600000, // 10 minutes * 60 seconds * 1000 milliseconds
InputStream = stream,
BucketName = imageBucket,
Key = key
};
using (var client = new AmazonS3Client())
{
using (client.PutObject(request))
{
}
}
return new Image
{
UriString = Path.Combine(baseImageUrl.AbsoluteUri, key)
};
}
}
You are having trouble unit testing UploadImage because it is coupled to many other external services and state. Static calls including (new) tightly couple the code to specific implementations. Your goal should be to refactor those so that you can more easily unit test. Also, keep in mind that after unit testing this class, you will still need to do the big tests involving actually using the Amazon S3 service and making sure the upload happened correctly without error or fails as expected. By unit testing thoroughly, hopefully you reduce the number of these big and possibly expensive tests.
Removing the coupling to the AmazonS3Client implementation is probably going to give you the biggest bang for your testing buck. We need to refactor by pulling out the new AmazonS3Client call. If there is not already an interface for this class, then I would create one to wrap it. Then you need to decide how to inject the implementation. There are a number of options, including as a method parameter, constructor parameter, property, or a factory.
Let's use the factory approach because it is more interesting than the others, which are straight-forward. I've left out some of the details for clarity and read-ability.
interface IClientFactory
{
IAmazonS3Client CreateAmazonClient();
}
interface IAmazonS3Client
{
PutObjectResponse PutObject(PutObjectRequest request); // I'm guessing here for the signature.
}
public class AmazonS3Service : IAmazonS3Service
{
// snip
private IClientFactory factory;
public AmazonS3Service(IClientFactory factory)
{
// snip
this.factory = factory;
}
public Image UploadImage(Stream stream)
{
if (stream == null) throw new ArgumentNullException("stream");
var key = string.Format("{0}.jpg", Guid.NewGuid());
var request = new PutObjectRequest
{
CannedACL = S3CannedACL.PublicRead,
Timeout = -1,
ReadWriteTimeout = 600000, // 10 minutes * 60 seconds * 1000 milliseconds
InputStream = stream,
BucketName = imageBucket,
Key = key
};
// call the factory to provide us with a client.
using (var client = factory.CreateAmazonClient())
{
using (client.PutObject(request))
{
}
}
return new Image
{
UriString = Path.Combine(baseImageUrl.AbsoluteUri, key)
};
}
}
A unit test might look like this in MSTest:
[TestMethod]
public void InputStreamSetOnPutObjectRequest()
{
var factory = new TestFactory();
var service = new AmazonS3Service(factory);
using (var stream = new MemoryStream())
{
service.UploadImage(stream);
Assert.AreEqual(stream, factory.TestClient.Request.InputStream);
}
}
class TestFactory : IClientFactory
{
public TestClient TestClient = new TestClient();
public IAmazonS3Client CreateClient()
{
return TestClient;
}
}
class TestClient : IAmazonS3Client
{
public PutObjectRequest Request;
public PutObjectResponse Response;
public PutObjectResponse PutObject(PutObjectRequest request)
{
Request = request;
return Response;
}
}
Now, we have one test verifying that the correct input stream is sent over in the request object. Obviously, a mocking framework would help cut down on a lot of boilerplate code for testing this behavior. You could expand this by starting to write tests for the other properties on the request object. Error cases are where unit testing can really shine because often they can be difficult or impossible to induce in production implementation classes.
To fully unit test other scenarios of this method/class, there are other external dependencies here that would need to be passed in or mocked. The ConfigurationManager directly accesses the config file. Those settings should be passed in. Guid.NewGuid is basically a source of uncontrolled randomness which is also bad for unit testing. You could define an IKeySource to be a provider of key values to various services and mock it or just have the key passed from the outside.
Finally, you should be weighing all the time taken for testing/refactoring against how much value it is giving you. More layers can always be added to isolate more and more components, but there are diminishing returns for each added layer.
Things I would look at:
Mock your configuration manager to return invalid data for the bucket and the URL. (null, invalid urls, invalid buckets)
Does S3 support https ? If so mock it, if not, mock it and verify you get a valid error.
Pass different kinds of streams in (Memory, File, other types).
Pass in streams in different states (Empty streams, streams that have been read to the
end, ...)
I would allow the timeouts to be set as parameters, so you can test with really low
timeouts and see what errors you get back.
I would also test with duplicate keys, just to verify the error message. Even though you are using guids, you are storing to an amazon server where someone else could use the S3 API to store documents and could theoretically create a file that appears to be a guid, but could create a conflict down the road (unlikely, but possible)

Categories