Owin component : Modifying owin response


Writing Owin component is really easy. Changing response's body content can be tricky.

Let's say you need to strip out confidential information from your http/https response before passing it to the client. Sounds pretty easy but tricky at the same time.

Key points to note is that :-

1. Must invoke the next owin component in the pipeline before reading response as string. (maybe that's obvious)

Try to use filter to control flow to your owin component. As you can see below, code 21, i running a check on Request.Path to prevent application to flow through (modifying anything in my Owin component).

2.When modifying content, you need to set context.Response.ContentType, StatusCode and ContentLength to the size of data you're trying to push across.

I used memorystream to channel my output to context.response.body

3. Must use code starting from Line 69 to copy content to context.Response.Body. Your owin pipeline can get pretty unable if you don't do this.

Full source for my implementation is given here.



public class CustomerCifCleaner : OwinMiddleware
{
private const int HTTP_OK_Status = 200;
private const string JSONContentType = "application/json";
private const string AccessViolationExceptionConst = "AccessViolationException";
private const string TaskCanceledExceptionConst = "TaskCanceledException";
private const string ExceptionConst = "Exception";
private ILogger logger = null;
public CustomerCifCleaner(OwinMiddleware next) : base(next)
{
// logger = new LoggerConfiguration().WriteTo.RollingFile(@"c:\logs\owin-log-{Date}.log").CreateLogger();
logger = new LoggerConfiguration().ReadFrom.AppSettings().CreateLogger();
}
public async override Task Invoke(IOwinContext context)
{
var correllationId = System.Guid.NewGuid();
// stripping the raw cif is only required on proxied api calls
if (context.Request.Path.HasValue && !context.Request.Path.Value.Contains("proxy/intermediated/customers"))
{
logger.Debug("[{1}] CIF Middleware skipped processing {0}", context.Request.Path.Value, correllationId);
await this.Next.Invoke(context);
return;
}
try
{
// hold a reference to what will be the outbound/processed response stream object
var stream = context.Response.Body;
// create a stream that will be sent to the response stream before processing
using (var buffer = new MemoryStream())
{
// set the response stream to the buffer to hold the unaltered response
context.Response.Body = buffer;
// allow other middleware to respond
await this.Next.Invoke(context);
// we have the unaltered response, go to start
buffer.Seek(0, SeekOrigin.Begin);
// read the stream
var reader = new StreamReader(buffer);
string responseBody = reader.ReadToEnd();
byte[] byteArray = null;
//
if (!string.IsNullOrEmpty(responseBody) &&
responseBody.Contains("externalCustomerReferences") &&
responseBody.Contains(Constants.CustomerId))
{
logger.Debug("[{1}] CIF Middleware returned altered response for {0}", context.Request.Path.Value, correllationId);
byteArray = Encoding.ASCII.GetBytes(RemoveTopLevelExternalReferencesCustomerId(responseBody));
}
else
{
logger.Debug("[{1}] CIF Middleware return unaltered response for {0}", context.Request.Path.Value, correllationId);
byteArray = Encoding.ASCII.GetBytes(responseBody);
}
context.Response.ContentType = JSONContentType;
context.Response.StatusCode = HTTP_OK_Status;
context.Response.ContentLength = byteArray.Length;
buffer.SetLength(0);
buffer.Write(byteArray, 0, byteArray.Length);
buffer.Seek(0, SeekOrigin.Begin);
buffer.CopyTo(stream);
}
}
catch (System.AccessViolationException vex)
{
context.Response.StatusCode = 500;
logger.Fatal(AccessViolationExceptionConst + vex.Message + vex.StackTrace);
throw;
}
catch (TaskCanceledException cex)
{
context.Response.StatusCode = 500;
logger.Fatal(TaskCanceledExceptionConst + cex.Message + cex.StackTrace);
throw;
}
catch (System.Exception ex)
{
context.Response.StatusCode = 500;
logger.Fatal(ExceptionConst + ex.Message + ex.StackTrace);
throw;
}
finally
{
logger.Debug("[{1}] CIF Middleware finally executed for {0}", context.Request.Path.Value, correllationId);
}
}
/// <summary>
/// Remove external customer references that is a direct child of root.
/// </summary>
/// <param name="responseBody"></param>
/// <returns></returns>
private string RemoveTopLevelExternalReferencesCustomerId(string responseBody)
{
JObject responseContent = null;
responseContent = JObject.Parse(responseBody);
JToken externalCustomerReferencesToken;
if (responseContent.TryGetValue("externalCustomerReferences", out externalCustomerReferencesToken))
{
if (externalCustomerReferencesToken.Type == JTokenType.Array)
{
// This will remove raw customerID (cif)
JArray externalReferenceList = externalCustomerReferencesToken as JArray;
if (externalReferenceList != null)
{
for (int i = 0; i < externalReferenceList.Count; i++)
{
JObject jObject = externalReferenceList[i] as JObject;
if (jObject.GetValue(Constants.Namespace).ToString().Equals(Constants.CustomerId))
{
jObject.Remove();
break;
}
}
}
return responseContent.ToString();
}
}
return responseBody;
}
}




Comments

Popular posts from this blog

The specified initialization vector (IV) does not match the block size for this algorithm

NodeJS: Error: spawn EINVAL in window for node version 20.20 and 18.20