Changing the response object from OWIN Middleware - c#

My OWIN middleware is like this. (Framework is ASP.NET Web API).
public class MyMiddleware : OwinMiddleware
{
public MyMiddleware(OwinMiddleware next) : base(next) { }
public override async Task Invoke(OwinRequest request, OwinResponse response)
{
var header = request.GetHeader("X-Whatever-Header");
await Next.Invoke(request, response);
response.SetHeader("X-MyResponse-Header", "Some Value");
response.StatusCode = 403;
}
}
Questions:
Is it the recommended practice to derive from OwinMiddleware? I see that in Katana source, some of the middleware classes derive from OwinMiddleware and some do not.
I can see the request headers okay. Setting response header or status code after Next.Invoke in my middleware has no effect on the response returned to the client. But if I set the response header or status before the Next.Invoke call, the response with headers and the status that I set is returned to the client. What is the right way of setting these?

Yes, deriving from OwinMiddleware is recommended. The reason some middleware classes don't derive from OwinMiddleware is that either they haven't switched over yet because the class was introduced recently. Or to avoid having the assembly take a dependency on the Microsoft.Owin assembly for some reason.
The probable reason setting stuff on the response after calling Invoke on Next doesn't work is that the response HTTP header gets sent as soon as anyone starts writing to the response body stream. So any changes to status code or HTTP headers after a middleware component starts writing to the response body won't have any effect.
What you can try doing is to use the OnSendingHeaders callback that OWIN provides. Here's how you can use it:
public override async Task Invoke(IOwinContext context)
{
var response = context.Response;
var request = context.Request;
response.OnSendingHeaders(state =>
{
var resp = (OwinResponse)state;
resp.Headers.Add("X-MyResponse-Header", "Some Value");
resp.StatusCode = 403;
resp.ReasonPhrase = "Forbidden";
}, response);
var header = request.Headers["X-Whatever-Header"];
await Next.Invoke(context);
}
Credit to biscuit314 for updating my answer.

I tried to edit Youssef's excellent answer to correct a minor bug and update the example with how the OwinMiddleware now works.
The edit was rejected (well, approved by one, rejected by one for being too minor, and rejected by two for being too major).
Here is that version of Youssef's code:
public override async Task Invoke(IOwinContext context)
{
var response = context.Response;
var request = context.Request;
response.OnSendingHeaders(state =>
{
var resp = (OwinResponse)state;
resp.Headers.Add("X-MyResponse-Header", "Some Value");
resp.StatusCode = 403;
resp.ReasonPhrase = "Forbidden"; // if you're going to change the status code
// you probably should also change the reason phrase
}, response);
var header = request.Headers["X-Whatever-Header"];
await Next.Invoke(context);
}

I used this code to get the time taken by every request.
appBuilder.Use(async (context, next) =>
{
var watch = new Stopwatch();
watch.Start();
await next();
watch.Stop();
context.Response.Headers.Set("ResponseTime", watch.Elapsed.Seconds.ToString());
});

Related

Having 'the response ended prematurely' exception on client side

There is exception being occurred at client side saying Error while copying the stream content and below that the response ended prematurely.
I cant figure out the solution. In the server side I have asp.net core web api which modify response stream. It actually reads what the controller send and encrypts it to a string and then writes to the response stream.
Also when when the content type is text/plain the response is shown on the Postman but when the content-type is application/json the content is not shown but in the header I can see the content length having some numbers. And for client side, the above exception occurs for both the content-type.
So what i am missing in my middleware code?? I know this is causing issue because when i comment out MyMiddleware in startup.cs, the normal flow works.
Below is the Invoke function in middleware on the server side
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
string reponseContent = string.Empty;
// Store the "pre-modified" response stream.
var existingBody = context.Response.Body;
using (var newBody = new MemoryStream())
{
// We set the response body to our stream so we can read after the chain of middlewares have been called.
context.Response.Body = newBody;
await next(context);
// Set the stream back to the original.
context.Response.Body = existingBody;
newBody.Seek(0, SeekOrigin.Begin);
//reading the content
var contentReader = new StreamReader(newBody);
reponseContent = await contentReader.ReadToEndAsync();
string encryptedData = _cryptoService.Encrypt(reponseContent);
// Send our modified content to the response body.
await context.Response.WriteAsync(encryptedData);
}
I am not sure how you are calling your MiddleWare. I have successfully reproduce the issue and get the response accordingly.
Middle Ware Class:
public class CustomMiddleware
{
private readonly RequestDelegate next;
public CustomMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task InvokeAsync(HttpContext context)
{
string reponseContent = string.Empty;
// Store the "pre-modified" response stream.
var existingBody = context.Response.Body;
using (var newBody = new MemoryStream())
{
// We set the response body to our stream so we can read after the chain of middlewares have been called.
context.Response.Body = newBody;
await next(context);
// Set the stream back to the original.
context.Response.Body = existingBody;
newBody.Seek(0, SeekOrigin.Begin);
//reading the content
var contentReader = new StreamReader(newBody);
reponseContent = await contentReader.ReadToEndAsync();
// string encryptedData = _cryptoService.Encrypt(reponseContent);
// Send our modified content to the response body.
await context.Response.WriteAsync(reponseContent);
}
}
}
Note: You should use constructor to invoke your RequestDelegate like this way. But you have designed this with two parameter, not sure how you are passing the argument while calling.
Startup.cs:
Calling Middleware In Startup.cs under Configure like this way
app.UseMiddleware<CustomMiddleware>();
Request From Postman:
I have tested with simple plain text and application/json type. Sent request to my controller and modify the argument on controller body, and the changed implemented on the middleware.
Controller:
[HttpPost]
public IActionResult MiddlewareReStream([FromBody] Plans plan)
{
plan.PlanName = "New Text";
return Ok(plan);
}
MiddlWare Output:
Note: Notice that I have invoke the request with "PlanName":"Test Plan" and modified the parameter which middleware successfully invoked the changes.
PostMan:
Note: Make sure you have called or implemented the InvokeAsync Middleware accordingly. Because I got the response as you are expecting.
Im thinking that the "Content-Length" header of the response represents the size of the data before you encrypt it and that you need to recalculate the size based on the new encrypted data and reset the header. Im also thinking that different servers are reacting to this missmatch differently, That it might work on IIS but not Kestrel.
In short make sure your "Content-Length" and "Content-Type" headers are matching what you are actually sending.

Providing a custom 401 response message on OpenId OnRedirectToIdentityProvider

I'm using AddOpenIdConnect and need to modify the response in case the OnRedirectToIdentityProvider event is raised. Within this event the response status is modified to 401 and I would like to set a custom message. To write this custom message, I've created the SetResponseBody method.
The solution of this post is used to set the response status, but I need to modify the Body as well.
I'm calling the SetResponseBody method (a custom method which I implemented) in order to modify the response body as soon as the OnRedirectToIdentityProvider event is raised from AddOpenIdConnect.'
As mentioned in one of the comments by #Panagiotis Kanavos in the post, the SetResponseBody method doesn't seem to be a correct solution (despite the response actually contains valid json). Could you provide an alternative?
Summarized: I would like to return a custom response besides the status code 401.
OnRedirectToIdentityProvider = async e =>
{
// e is of type RedirectContext
if (e.Request.Path.StartsWithSegments("/api")))
{
if (e.Response.StatusCode == (int)HttpStatusCode.OK)
{
e.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
// TestMessage is a const
// e.Response is readonly (get) so it's not possible to set it directly.
await ResponseRewriter.SetResponseBody(e.Response, TestMessage);
}
e.HandleResponse();
}
await Task.CompletedTask;
}
with ResponseRewriter.SetResponseBody defined as follows
public static async Task SetResponseBody(HttpResponse response, string message)
{
var originalResponseBody = response.Body;
var responseBody = new MemoryStream();
response.Body = responseBody;
response.ContentType = "application/json";
var body = new { Message = message };
string json = JsonSerializer.Serialize(body);
await response.WriteAsync(json);
response.Body.Seek(0, SeekOrigin.Begin);
await responseBody.CopyToAsync(originalResponseBody);
}
The last two lines are implemented as written in this post.

Custom response header not sent

I have developed some OWIN middleware to appends a custom header to the response. However, in my integration tests (which uses OWIN TestServer), I cannot see the custom header in the response object.
I notice that I do see the location header which I populate for POST requests.
I also notice that the header is appearing when I make real requests to the service.
Does anyone know why I can't see the custom header in case of TestServer? Is there settings I need to make to allow these?
Here is the OWIN middleware:
private async Task CalculateTimeToProcess(IOwinContext context)
{
var sw = new Stopwatch();
sw.Start();
await Next.Invoke(context);
sw.Stop();
var response = context.Response;
response.Headers.Add("x-timetoprocessmilliseconds",
new[] { sw.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture) });
}
This is how I am trying to retrieve the header in my test:
var header = _restContext.HttpResponseMessage.Headers.SingleOrDefault(x => x.Key == "x-timetoprocessmilliseconds");
I don't know what the difference is between your live setup and unit-test, but you should be aware that if any previous middleware starts writing to the response.Body, the headers are getting sent before the OWIN pipeline returns to your middleware (see Note below).
What you can do is attaching a callback to OnSendingHeaders before you invoke the next middleware.
private async Task CalculateTimeToProcess(IOwinContext context)
{
var sw = new Stopwatch();
sw.Start();
context.Response.OnSendingHeaders(state =>
{
sw.Stop();
var response = (IOwinResponse)state;
response.Headers.Add("x-timetoprocessmilliseconds", new[] { sw.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture) });
}, context.Response);
await Next.Invoke(context);
}
Note: By sending the headers first, they can transmit whatever gets written into the body stream directly to the socket without having to buffer that in memory. This also means that your measurement will be incorrect in case other middleware already writes to the output stream while still processing things...

Rewrite the response in Owin Middleware

In my OWIN app, I register a middleware to intercept the outcome of other middlewares down in the pipeline and if a condition is met, I need to totally change the response from whatever it is (could be 302 or 200) to 403 (forbidden).
There is ofcourse a not-very-clean-way and that is to manually clear all the headers, content type, etc. and set the statusCode to 403 but this feels to me a wrong approach.
public override async Task Invoke(IOwinContext context)
{
await this.Next.Invoke(context);
if (someCondition(context))
{
var headers = context.Response.Headers;
headers.Keys.ForEach(k => headers.Remove(k));
context.Response.StatusCode = 403;
context.Response.ContentType = string.Empty;
context.Response.ContentLength = null;
await context.Response.WriteAsync(string.Empty);
}
}
Plus the fact that this approach doesn't work when overwriting a 200 response (when it hits the line where we set StatusCode, it jumps out and flushes the response).
I'm new to OWIN and I may be misunderstanding the way it works.
Is there any other way you would do this?
Here is what I discovered.
If you try to change the response headers after you reached the controller, the headers might already have been sent.
That's why you should subscribe to the OnSendingHeaders(Action<object> callback, object state) before continuing the pipeline.
Example:
...
context.Response.OnSendingHeaders(obj => { /* Do stuff */ }, new object());
// Then call the next middleware
await this.Next.Invoke(context);
...
You should call Next.Invoke only if someCondition(context) is false. I think you'll find this blog post helpful.

How to update ALL requests when a request property has changed, when using Async/Await?

I'm using async/await to make multiple requests per second to a service using their API. The problem I am running into is when I need to refresh the token (it expries every hour). After the token has expired, I get a 401 unauthorized error back from the service. That's when I refresh the token, and retry the failed request again. The token gets refreshed fine, but what I'm finding is that even after the token is refreshed, a lot of subsequent requests are still sent with the old token. Following are the methods that are used in this functionality. Wondering if anything standouts as being the culprit for this unintended behavior.
public void Process(id)
{
var tasks = items.Select(async item =>
{
var response = await SendRequestAsync(() => CreateRequest(item.Url));
//do something with response
await Process(item.subId); //recursive call to process sub items.
}).ToList();
if (tasks.Any())
await Task.WhenAll(tasks);
}
public HttpRequestMessage CreateRequest(string url)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("Authorization", "Bearer " + AppSettings.AccessToken);
return request;
}
public async Task<HttpResponseMessage> SendRequestAsync(Func<HttpRequestMessage> funcReq)
{
var response = await ExecuteRequestAsync(funcReq());
while (response.StatusCode == HttpStatusCode.Unauthorized)
{
await RefreshTokenAsync();
return await ExecuteRequestAsync(funcReq()); //assuming func ensures that CreateRequest is called each time, so I'll always have a new request with the updated token.
}
return response;
}
private async Task<HttpResponseMessage> ExecuteRequestAsync(HttpRequestMessage request)
{
var client = new HttpClient();
var response = await client.SendAsync(request);
return response;
}
public async Task RefreshTokenAsync()
{
await semaphoreSlim.WaitAsync();
try
{
if ((DateTime.Now - refreshTime).TotalMinutes < 60) //tokens last for an hour, so after the refresh is made by the first request that failed, subsequent requests should have the latest token.
return;
Token newToken = GetNewToken();
AppSettings.AccessToken = newToken.AccessToken //AppSettings is a singleton wrapper class for app.cofig app settings
refreshTime = DateTime.Now
}
finally
{
semaphoreSlim.Release();
}
}
This is not an answer. It's just I don't have where to post the code discussed in one of the comments.
Prabhu, I think something like this should work in order to have the token updated prior to getting a 401. This works only if you can make an assumption on how often tokens expire.
public HttpRequestMessage CreateRequest(string url)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("Authorization", "Bearer " + GetUpToDateAccessToken());
return request;
}
private Token GetUpToDateAccessToken()
{
_readWriteLockSlim.EnterReadLock();
try
{
return _latestToken;
}
finally
{
_readWriteLockSlim.ExitReadLock();
}
}
Updating the token can be done with a Timer every 60 minutes. Synchronization is done with a read-write lock. This would be the Timer's tick handler (you can use a System.Timers.Timer).
private void UpdateToken()
{
_readWriteLockSlim.EnterWriteLock();
try
{
if ((DateTime.Now - refreshTime).TotalMinutes >= 60)
{
Token newToken = GetNewToken();
_latestToken = newToken.AccessToken;
refreshTime = DateTime.Now;
}
}
finally
{
_readWriteLockSlim.ExitWriteLock();
}
}
As you mentioned, if the 60 minute expiry period is not guaranteed then this won't work as expected. Maybe you can re-generate the token every 5 minutes or so, to make sure you don't make requests with invalid tokens.
Finally, to handle 401s because they can still occur, you can modify the while loop in SendRequestAsync to:
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
UpdateToken();
return await ExecuteRequestAsync(funcReq());
}
I will suggest the following workflow (added to the currently suggested from Marcel N) (pseudo code) :
// Manage the expiration token yourself in the Application or Db
var token = GetTokenFromDbOrApplicationWithExpirationDateTime()
// Expiration on your application can be a little bit less than real, so instead of 60 can be 50 minutes.
if (token.isExpired)
token = RequestNewToken()
Db.SaveChanges(token);
}
CallMethod1Async(token)
CallMethod2Async(token)
CallMethod3Async(token)
you also may want to check if the CallMethodAsync returns a response with invalid token as Marcel N provided.

Categories