I want to insert a sleep (aka throttle, delay, tarpit, dwell) in an ASP.net application (imagine something like failed logon attempt escalating delay).
protected void Page_Load(object sender, EventArgs e)
{
Int32 sleepyTime = GetSleepyTime(Request);
if (sleepyTime > 0)
System.Threading.Thread.Sleep(sleepyTime);
//...Continue normal processing
}
I want all remaining processing to continue as normal; i just want the user agent to suffer.
The problem is that ASP.net uses a ThreadPool to handle requests. If i were to Sleep
for 5, 10, 30 seconds, i would be eating up a valuable limited resource.
I assume it needs to be something like:
protected void Page_Load(object sender, EventArgs e)
{
Int32 sleepyTime = GetSleepyTime(Request);
if (sleepyTime > 0)
ABetterKindOfSleep(sleepyTime);
//...Continue normal processing
}
private void ABetterKindOfSleep(int milliseconds)
{
await SleepAsync(milliseconds);
}
private async void SleepAsync(int milliseconds)
{
System.Threading.Thread.Sleep(milliseconds);
}
But never having written any async/await code, and not understanding the logic behind where the async
and await
go, or why, or even if it can be used to run async code: i don't know if it can be used to run async code.
It's easy enough.
First you create an IHttpModule
class:
class TarpitHttpModule : IHttpModule
{
}
And then you let IIS know about this module by registering it in web.config
:
<configuration>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="Tarpit" type="TarpitHttpModule"/>
If you are Cassini, add it to:
<configuration>
<system.web>
<httpModules>
<add name="Tarpit" type="TarpitHttpModule"/>
Whenever an http request comes in, IIS will call your .Init
method. This is where you will register your async event handler using:
BeginRequest
event to the collection of asynchronous BeginRequest
event handlers for the current request.Code:
public void Init(HttpApplication application)
{
//This is the synchronous event handler; which we don't want
//application.BeginRequest += new EventHandler(this.Application_BeginRequest);
//EventHandlerTaskAsyncHelper requires .NET 4.5
//https://brockallen.com/2013/07/27/implementing-async-http-modules-in-asp-net-using-tpls-task-api/
// Archive: http://archive.is/Cdvle
//
//Normally you'd have to write a pair of methods:
// application.AddOnBeginRequestAsync(OnBegin, OnEnd);
//
//and then we'd have to write an OnBegin which returns IAsyncResult, and then OnEnd which takes the IAsyncResult.
//The modern way is to use Tasks, and use the IAsyncResult that a Task **is**.
//Fortunately the .NET team wrote a handy class that wraps up the boilerplate catching faults, etc,
//and created the EventHandlerTaskAsyncHelper class
var beginTaskHelper = new EventHandlerTaskAsyncHelper(BeginRequestAsync);
application.AddOnBeginRequestAsync(beginTaskHelper.BeginEventHandler, beginTaskHelper.EndEventHandler);
}
So now we have to supply the BeginRequestAsync asynchronous handler:
async Task BeginRequestAsync(object sender, EventArgs e)
{
var application = (HttpApplication)sender;
var context = application.Context;
// In reality i would use the context.Request to come up with a unique key
// for this user agent e.g.
String key = SHA256(UserHostAddress+UserAgent+AcceptTypes+UserLanguages).ToBase64();
// And use that as a cache key store information about this user agent
Object tarpitInfo = context.Cache.Get(agentIdentity);
if (ti == null)
return;
// But in this SO demo, i'm just going to unconditionally sleep
Boolean waitPerformed = await PerformDelay(context, tarpitInfo);
if (waitPerformed)
{
context.Response.StatusCode = 429;
context.Response.StatusDescription = "Too Many Requests";
context.Response.End();
return;
}
}
And then the work of sleeping:
async Task<Boolean> PerformDelay(HttpContext context, TarInfo ti)
{
int delayMs = 3000;
Task delay = Task.Delay(delayMs);
await delay;
return true;
}