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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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