Logging raw and compressed HTTP responses in ASP.NET & IIS7 - asp.net

Along the lines of this question I want to create a HttpModule that will do some custom logging of requests and responses for us. Using the code in the most popular answer for that question I've got a HttpModule up and running which does indeed work:
class PortalTrafficModule : IHttpModule
{
public void Dispose()
{
// Do Nothing
}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
context.EndRequest += new EventHandler(context_EndRequest);
}
private void context_BeginRequest(object sender, EventArgs e)
{
HttpContext context = ((HttpApplication)sender).Context;
// Create and attach the OutputFilterStream to the Response and store it for later retrieval.
OutputFilterStream filter = new OutputFilterStream(context.Response.Filter);
context.Response.Filter = filter;
context.Items.Add("OutputFilter", filter);
// TODO: If required the request headers and content could be recorded here
}
private void context_EndRequest(object sender, EventArgs e)
{
HttpContext context = ((HttpApplication)sender).Context;
OutputFilterStream filter = context.Items["OutputFilter"] as OutputFilterStream;
if (filter != null)
{
// TODO: Log here - for now just debug.
Debug.WriteLine("{0},{1},{2}",
context.Response.Status,
context.Request.Path,
filter.ReadStream().Length);
}
}
}
(note that the OutputFilterStream class referred to in the code is in the referenced question).
However, the responses seem to be missing some HTTP headers that I see in Fiddler (like "Date") and more importantly, when I turn on compression the responses I'm logging aren't compressed whereas what I see in Fiddler is.
So my question - is it possible to log the compressed content or is this happening in a subsequent step my module can't hook in to?
For the record, I've also tried handling the PreSendRequestContent event and the response is still uncompressed.

Hi although I cannot answer your questions directly I have had cause to do similar things in the past and have found the following resource extremely helpful and enlightening. In the end I managed to acheive what I needed for raw soap headers by configuring the System.Diagnostics node within web config and create a trace log of traffic. I understand that your needs may be more granular than that however I believe this resource may still help.
http://msdn.microsoft.com/en-us/library/ms731859
Of particular interest may be the message log configuration and viewing message logs links from the one above.

Related

Add Authorization header to all requests in Xamarin Forms Android WebView

I'm trying to add custom http headers to a webview client (for authorization).
It seems to work in some cases, I'am able to login to a webpage without entering username and password, and I get redirected to another page. But when the page is calling other resources to get elements populated with data an error is thrown and OnReceivedHttpError is invoked. The error I'm getting is 401 unauthorized and when i look through the headers on the IWebResourceRequest i can't see the authorization headers at all.
Am I missing something or have anyone had same problems ?
Using Xamarin Forms 2.3.3.180 and targeting API 21 (Android 5.0 Lollipop), compile with Android 7.1 Nougat.
I've tried in postman to add headers to request and it works perfectly.
Renderer:
public class MyWebViewRenderer : WebViewRenderer
{
private MyWebViewClient _webViewClient;
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
if(_webViewClient == null)
_webViewClient = new MyWebViewClient();
Control.SetWebViewClient(_webViewClient);
Control.LongClickable = false;
Control.HapticFeedbackEnabled = false;
Control.Settings.JavaScriptEnabled = true;
var data = Encoding.GetEncoding("ISO-8859-1").GetBytes("username:password");
var base64string = Base64.EncodeToString(data, Base64Flags.NoWrap);
var headers = new Dictionary<string, string>();
headers.Add("Authorization", $"Basic {base64string}")
Control.LoadUrl(Control.Url, headers);
}
}
WebViewClient:
public override bool ShouldOverrideUrlLoading(WebView view, string url)
{
WebView.SetWebContentsDebuggingEnabled(true);
var data = Encoding.GetEncoding("ISO-8859-1").GetBytes("username:password");
var base64string = Base64.EncodeToString(data, Base64Flags.NoWrap);
var headers = new Dictionary<string, string>();
headers.Add("Authorization", $"Basic {base64string}")
view.LoadUrl(url, headers);
return true;
}
public override WebResourceResponse ShouldInterceptRequest(WebView view, IWebResourceRequest urlResource)
{
//headers does not always contains authorization header, so let's add it.
if (!urlResource.RequestHeaders.ContainsKey("authorization") && !urlResource.RequestHeaders.ContainsKey("Authorization"))
{
var data = Encoding.GetEncoding("ISO-8859-1").GetBytes("username:password");
var base64string = Base64.EncodeToString(data, Base64Flags.NoWrap);
urlResource.RequestHeaders.Add("Authorization", $"{base64string}");
}
return base.ShouldInterceptRequest(view, urlResource);
}
public override void OnReceivedHttpError(WebView view, IWebResourceRequest request, WebResourceResponse errorResponse)
{
base.OnReceivedHttpError(view, request, errorResponse);
}
If you only need the headers on the get requests, the code below will work. However POST requests are a different issue. I needed to do a similar thing (with all requests, not just GET), and all I can say is that there's not straightforward solution, at least not one that I've found (and I've tried everything short of writing my own network driver). I've tried so many methods (ShouldOverrideUrlLoading, ShouldInterceptRequest, custom LoadUrl and PostUrl etc.) and none of them give a 100% solution. There is a lot of misinformation about this so I think some clarification is needed since I've spent two days on this without success.
So here's what I've learned:
If you only need the headers in the GET requests, that's trivial. Simply create an implementation of WebViewClient and override ShouldOverrideUrlLoading like this:
[assembly: ExportRenderer(typeof(Xamarin.Forms.WebView), typeof(App.Android.HybridWebViewRenderer))]
namespace App.Android
{
public class HybridWebViewRenderer : WebViewRenderer
{
public HybridWebViewRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
Control.SetWebViewClient(new CustomWebViewClient());
}
}
public class CustomWebViewClient : WebViewClient
{
public override bool ShouldOverrideUrlLoading(Android.Webkit.WebView view, string url)
{
Dictionary<string, string> headers = new Dictionary<string, string>
{
["Name"] = "value"
};
view.LoadUrl(url, headers);
return true;
}
}
}
If, however, you need the headers in other requests (specifically POST requests) there really isn't a perfect solution. Many answers tell you to override ShouldInterceptRequest but this is unlikely to help. ShouldInterceptRequest provides an IWebResourceRequest which contains the URL of the request, the method (i.e. POST) and the headers. There are answers out there which state that adding the headers by doing request.Headers.Add("Name", "Value") is a viable solution but this is wrong. The IWebResourceRequest is not used by the WebView's internal logic so modifying it is useless!
You can write your own HTTP client in ShouldInterceptRequest which includes your own headers to perform the requests and return a WebResourceResponse object. Again, this works for GET requests, but the problem with this is that even though we can intercept a POST request, we cannot determine the content in the request as the request content is not included in the IWebResourceRequest object. As a result, we cannot accurately perform the request manually. So, unless the content of the POST request is unimportant or can somehow be fetched, this method is not viable.
An additional note on this method: returning null tells the WebView to handle the request for us. In other words 'I don't want to intercept the request'. If the return is not null however, the WebView will display whatever is in the WebResourceResponse object.
I also tried overriding the PostUrl and LoadUrl methods in the WebView itself. These methods are not called by the internal logic, so unless you are calling them yourself, this does not work.
So what can be done? There are a few hacky solutions (see github.com/KeejOow/android-post-webview) to get around this problem, but they rely on javascript and are not suitable in all cases (I have read that they don't work with forms). If you want to use them in Xamarin, you're going to need to adapt the code for C# anyway, and there is no guarantee that it will solve your problem.
I'm writing this so no one else has to waste countless hours finding a solution that doesn't really exist.
If only the Android devs had decided to include the POST content in the IWebResourceRequest object...
And apologies for the length, if you've read to this point, you're probably as desperate as I was.

Asp.net global output cache

Last few days I thinkin about output cache in asp.net. In my task I need to implement output cache for the very big project. After hours of searching I did not find any examples.
Most popular way to use output cache is declarative, in this case you need to write something like this on the page which you want to cache.
But if you need to cache whole site you must write this on all pages or master pages on project. It is madness. In this case you cant store all configuration in one place. All page have his own configurations..
Global.asax could help me, but my site contains about 20 web progects and ~20 global.asax files. And i don't want copy same code to each project.
For these reasons, i made decision to create HTTPModule.
In Init method i subscribe to two events :
public void Init(HttpApplication app)
{
app.PreRequestHandlerExecute += new EventHandler(OnApplicationPreRequestHandlerExecute);
app.PostRequestHandlerExecute += new EventHandler(OnPostRequestHandlerExecute);
}
In method "OnPostRequestHandlerExecute" I set up output caching parameters for each new request :
public void OnPostRequestHandlerExecute(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
HttpCachePolicy policy = app.Response.Cache;
policy.SetCacheability(HttpCacheability.Server);
policy.SetExpires(app.Context.Timestamp.AddSeconds((double)600));
policy.SetMaxAge(new TimeSpan(0, 0, 600));
policy.SetValidUntilExpires(true);
policy.SetLastModified(app.Context.Timestamp);
policy.VaryByParams.IgnoreParams = true;
}
In "OnApplicationPreRequestHandlerExecute" method I set calback method to cache validation:
public void OnApplicationPreRequestHandlerExecute(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
app.Context.Response.Cache.AddValidationCallback(new HttpCacheValidateHandler(Validate), app);
}
And last part - callback validation method :
public void Validate(HttpContext context, Object data, ref HttpValidationStatus status)
{
if (context.Request.QueryString["id"] == "5")
{
status = HttpValidationStatus.IgnoreThisRequest;
context.Response.Cache.AddValidationCallback(new HttpCacheValidateHandler(Validate), "somecustomdata");
}
else
{
status = HttpValidationStatus.Valid;
}
}
To attach my HttpModule I use programmatically attach method :
[assembly: PreApplicationStartMethod(typeof(OutputCacheModule), "RegisterModule")]
This method works perfectly, but I want to know is there other ways to do this.
Thanks.
Try seeing if IIS caching provides what you need.
http://www.iis.net/configreference/system.webserver/caching

HttpModule is breaking PostBack events

I'm trying to setup a simple HttpModule to handle authentication between my single sign on server. I've included code for the module below. The module is hitting my SSO and properly authenticating; however, on pages with forms the postback events are not occurring properly (e.g. isPostBack value is always false even though a POST occurred, button click events don't get hit, etc.).
public sealed class MyAuthenticationModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.AuthenticateRequest += OnAuthenticateRequest;
}
public void Dispose()
{
}
public static void OnAuthenticateRequest(object sender, EventArgs e)
{
FormsAuthentication.Initialize();
HttpContext context = HttpContext.Current;
HttpRequest request = context.Request;
HttpResponse response = context.Response;
// Validate the ticket coming back from the authentication server
if (!string.IsNullOrEmpty(request["ticket"]))
{
// I can include code for this if you want, but it appears to be
// working correct as whenever I get a ticket from my SSO it is processed
// correctly. I only get a ticket after coming from the SSO server and
// then it is removed from the URL so this only gets hit once.
MyAuthentication.ProcessTicketValidation();
}
if (!request.IsAuthenticated)
{
// redirect to the login server
response.Redirect("https://sso.example.com/login.aspx" + "?" + "service=" +
HttpUtility.UrlEncode(context.Request.Url.AbsoluteUri), false);
}
}
}
EDIT
I would also like to note that if I change the line:
if (!string.IsNullOrEmpty(request["ticket"]))
to:
if (!string.IsNullOrEmpty(request.QueryString["ticket"]))
the problem goes away.
Is it possible that your postbacks have a duplicate form data variable, "ticket"? That would seem to explain the behavior to me.
Aside from that, this line is suspicous:
FormsAuthentication.Initialize();
The FormsAuthentication class uses the "Provider" pattern, which means it's a singleton. You should not re-initialize. From the msdn documentation:
The Initialize method is called when the FormsAuthenticationModule
creates an instance of the FormsAuthentication class. This method is
not intended to be called from your code.

Using an HttpModule with Application_BeginRequest, how do I check only the first request?

I'm using the following HttpModule to stop a couple of IPs that constantly attempt to spam my contact form. I don't get the spam as they trigger System.Web.HttpRequestValidationException but I do get the Exception report in my email inbox. It's not quite as annoying but almost.
I eventually want to test against either a list of IPs from the database or maybe implement the HttpBL api to test against known blacklisted IPs but doing this on every request seems to be overkill. Either way I do it, whether using IPs in the database or making requests to an external blacklist at every page request surely seems unnecessary. Can you point me in the direction of checking this once and if the IP passes the test the first time to stop checking?
using System;
using System.Web;
namespace DomainModel.Services
{
public class BlockIPModule : IHttpModule
{
public void Dispose() {}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(Application_BeginRequest);
}
private void Application_BeginRequest(object sender, EventArgs e)
{
HttpContext context = ((HttpApplication)sender).Context;
string currentIP = context.Request.UserHostAddress;
if (!IsIpValid(currentIP))
{
context.Response.StatusCode = 403;
}
}
private bool IsIpValid(string checkIP)
{
return (checkIP != "213.5.70.205" && checkIP != "188.92.75.82");
}
}
}
updated code removed - terrible idea.
HTTP is stateless, and therefore, you really do have to check each request. You could cache the blacklist of IP addresses so you don't have to load it each time, but you'll still need to always run some test.
If you have control over it, the other option would be to do some filtering on your router. That would free your code from having to do it.

How to support compressed HTTP requests in Asp.Net 4.0 / IIS7?

For an ASP.NET 4.0 / IIS7 web app, I would like to support compressed HTTP requests. Basically, I would like to support clients that would add Content-Encoding: gzip in the request headers, and compress the body accordingly.
Does anyone known how I achieve such a behavior?
Ps: concerning, I have multiple endpoints REST and SOAP, and it feels a better solution to support compression at the HTTP level rather than custom encoders for each endpoint.
For those who might be interested, the implementation is rather straightforward with an IHttpModule that simply filters incoming requests.
public class GZipDecompressModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += BeginRequest;
}
void BeginRequest(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
if ("gzip" == app.Request.Headers["Content-Encoding"])
{
app.Request.Filter = new GZipStream(
app.Request.Filter, CompressionMode.Decompress);
}
}
public void Dispose()
{
}
}
Update: It appears that this approach trigger a problem in WCF, as WCF relies on the original Content-Length and not the value obtained after decompressing.
Try Wiktor's answer to my similar question here:
How do I enable GZIP compression for POST (upload) requests to a SOAP WebService on IIS 7?
...but please note his implementation on his blog contained a couple of bugs / compatibility issues, so please try my patched version of the HttpCompressionModule class posted on the same page.
Although hacky, you can get around WCF using the original Content-Length even after the request has been decompressed by setting the private _contentLength field in the HttpRequest class using reflection. Using Joannes Vermorel's code:
void BeginRequest(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
if ("gzip" == app.Request.Headers["Content-Encoding"])
{
app.Request.Filter = new GZipStream(
app.Request.Filter, CompressionMode.Decompress);
// set private _contentLength field with new content length after the request has been decompressed
var contentLengthProperty = typeof(HttpRequest).GetField("_contentLength", BindingFlags.NonPublic | BindingFlags.Instance);
contentLengthProperty.SetValue(app.Request, (Int32)app.Request.InputStream.Length);
}
}

Resources