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.
Related
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.
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...
I've created a custom MessageHandler as such:
public class WebAPICustomMessageHandler : DelegatingHandler {
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
//Parse QueryString
NameValueCollection queryString = HttpUtility.ParseQueryString(request.RequestUri.Query);
if (queryString != null) {
//Find my token
String token = queryString.Get("qsVariable");
if (!String.IsNullOrWhiteSpace(token)) {
//Remove token so it doesn't impact future handlers / controllers / etc.
queryString.Remove("qsVariable");
request.RequestUri.Query = queryString.ToString(); //How can we modify the querystring? apparently it's readonly?
//Append token as custom header to the request
request.Headers.Add("token", new String[] { token });
}
}
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
return response;
}
}
It appears that I can't directly change the QueryString, which is a bit odd as I thought that the entire point of custom message handlers in the Web API pipeline was to allow this exact sort of thing. The ability issue pre and post operations on request/response, including things like data scrubbing / injection etc.
I already know I can do what I want very easily utilizing OWIN (as I've already done it), but now I'm trying to do this without OWIN. Am I going to have to create an entirely new HttpRequestMessage in order to just change the QueryString? From the looks of it, I'll have to build a new Uri, then construct the HttpRequestMessage, then copy over each and every other piece from the original.
Is that the only way to do this? or is there a better way that I'm just not aware of?
Please note, that other routines later in the pipeline are setup to use a token found in the header of the request, but setting the header is not possible if the request came from a submission to an iframe, which is where the process above comes into place. The token is added to the querystring, then converted to a header to prevent changes to the rest of the pipeline.
You're correct, the query string is read-only. Just use a standard redirect with the substituted query string values.
public class WebAPICustomMessageHandler : DelegatingHandler {
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
var token = query["qsVariable"];
// just 'short-circuit' if our token was not found...
if (token == null)
return await base.SendAsync(request, cancellationToken);
token = "newValue"; // modify your value here...
query["token"] = token;
// redirect with new query string...
var response = request.CreateResponse(HttpStatusCode.Redirect);
var uri = request.RequestUri;
var ub = new UriBuilder(uri.Scheme,
uri.Host,
uri.Port,
uri.AbsolutePath);
ub.Query = query.ToString();
response.Headers.Location = ub.Uri;
return response;
}
}
For interest sake, it is possible to modify the query string even though it is read-only but I would avoid this practice.
I use ServiceStack to create an API. I want to perform authentication in a Request Filter. I have created a class that inherits RequestFilterAttribute and overrides method
void Execute(IRequest req, IResponse res, object requestDto)
In the override when the authentication fails, I want to prevent the request to continue with the service method and I want to return a specific message. For ending the request I can use one of these methods:
res.Close();
res.End();
res.EndHttpHandlerRequest();
res.EndRequest();
res.EndRequestWithNoContent();
They prevent the execution of the method in the service. When ending the response I want to have a specific DTO with a message. So before ending the response, I assign a DTO object to the property
res.Dto = myResponseDto;
However, the result from the API call is without any data. Can anybody help with preventing in the filter a request to reach the ServiceStack service implementation, but returning the desired DTO response object?
With filters you have to handle the raw response. So if you want to end the response in the filter and return a DTO response, you will need to Write the DTO object to the response before calling the EndRequest method.
If you are sending an Unauthorized error then setting the status code to HttpStatusCode.Unauthorized (401) is usually all that is needed by a client to recognise their request failed.
public override void Execute(IRequest req, IResponse res, object requestDto)
{
// Perform you filter actions
if(authorised)
return;
// Not authorised, return some object
var responseDto = new {
SomeValue = "You are not authorised to do that."
};
// Set the status code
res.StatusCode = (int)HttpStatusCode.Unauthorized;
// You may need to handle other return types based on `req.AcceptTypes`
// This example assumes JSON response.
// Set the content type
res.ContentType = "application/json";
// Write the object
res.Write(responseDto.toJson());
// End the request
req.EndRequest();
}
Hope that helps.
You can use the following instructions, where res.Dto can be changed to whatever you feel like:
res.ContentType = req.ResponseContentType;
res.StatusCode = (int)HttpStatusCode.Unauthorized;
res.Dto = DtoUtils.CreateResponseDto(requestDto, new ResponseStatus("401", "Unauthorized"));
res.EndRequest();
If you do not require to embed data in the response, you can simply use throw new AuthenticationException("Error reason"); in your filter.
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());
});