HttpHandlers make it easy to handle certain file types with a simple interface that anyone can write code for.
Few things are more annoying than checking your server logs to find that an image is being served hundreds, maybe thousands of times a day to other sites. It's called bandwidth-leeching, and aside from the obvious copyright issues, it eats the bandwidth that you're paying good money for. Sure, bandwidth is getting cheaper, but you're not a free hosting service.
Fortunately, ASP.NET makes it very easy to perform some checks on image requests before the image is served through the use of an HttpHandler. You may not realize it, but your server is already handing off requests to HttpHandlers. For example, .aspx files are being handled by the System.Web.UI.PageHandlerFactory. Your .cs or .vb files are being handled by System.Web.HttpForbiddenHandler to generate an error for snooping users. It stands to reason then that you can write your own code to handle .jpg images.
To make this work, you'll first have to set up IIS to handle requests for .jpg files. In your site's properties, click the "home directory tab" and click the "configuration" button. You'll get a form like this:
Find the entry for .aspx, click edit, and copy the path in the executable field. This is the aspnet_isapi.dll for the current version of the framework that is responsible for the plumbing between IIS and the ASP.NET engine. Cancel out of that dialog and click "add." Paste the path into the executable, use the extension .jpg and set your verbs limited to "GET, POST, HEAD" like this:
Now any request for a .jpg file on the site will be handled by ASP.NET. Since the server-wide machine.config file doesn't specify what class should handle the request, a default handler is used unless we add a few lines to our web.config file. We'll get to that in a moment.
Let's write our HttpHandler. HttpHandlers must implement the IHttpHandler interface. We're lucky, because this interface only requires two members, the ProcessRequest(HttpContext) method and the Boolean property IsReusable. ProcessRequest(HttpContext) decides what to do with the request. Understand that there is no automatic plumbing that will retrieve a file for any request that is to be processed by an HttpHandler, so we'll have to write some. IsReusable does exactly what it sounds like it should, it tells the server whether or not the handler object can be reused.
Here's the code:
C#
using System; using System.Web; public class JpgHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { string FileName = context.Server.MapPath(context.Request.FilePath); if (context.Request.ServerVariables["HTTP_REFERER"] == null) { context.Response.ContentType = "image/JPEG"; context.Response.WriteFile("/no.jpg"); } else { if (context.Request.ServerVariables["HTTP_REFERER"].IndexOf("mydomain.com") > 0) { context.Response.ContentType = "image/JPEG"; context.Response.WriteFile(FileName); } else { context.Response.ContentType = "image/JPEG"; context.Response.WriteFile("/no.jpg"); } } } public bool IsReusable { get { return true; } } }
VB .Net
Imports System Imports System.Web Public Class JpgHandler Implements IHttpHandler Public Sub ProcessRequest(context As HttpContext) Dim FileName As String = context.Server.MapPath(context.Request.FilePath) If context.Request.ServerVariables("HTTP_REFERER") Is Nothing Then context.Response.ContentType = "image/JPEG" context.Response.WriteFile("/no.jpg") Else If context.Request.ServerVariables("HTTP_REFERER").IndexOf("mydomain.com") > 0 Then context.Response.ContentType = "image/JPEG" context.Response.WriteFile(FileName) Else context.Response.ContentType = "image/JPEG" context.Response.WriteFile("/no.jpg") End If End If End Sub Public ReadOnly Property IsReusable() As Boolean Get Return True End Get End Property End Class
The first thing we do in the ProcessRequest(HttpContext) method is get the name of the image that is being requested. The context object gives us a reference to all of the things we're used to seeing in our normal page code, in this case we want to know what file is being requested and wrap it in a MapPath call to get its position on the disk.
Next we check to see if there's a value in the request's HTTP referrer value. I like to use this because I don't want people seeing my images unless it's in the context of a page on my site (where they can see the ad and I can get paid for the impression). If someone puts the URL of the image into a new browser or follows a link from an e-mail, there won't be a referrer. If that's the case, I set the content type to "image/JPEG" and serve them an image called "no.jpg," which has a friendly message in it saying they can't see what they're looking for.
One side note about this. Some so-called "security" software strips out the referrer from every request, which is really paranoid and stupid. People using this software will only see the leech notice image.
Assuming there is a referrer, the next thing to check is if it's a page on our site. Our analysis of the referrer string isn't complex, we just want to make sure that there's some instance of our domain name in it. The IndexOf() method of the string object works perfectly. If our domain is there, we serve the image they were looking for. If not, the alternate image is all they'll see.
You'll need to compile this class into an assembly that you'll put in the application's /bin folder. If you're using Visual Studio or one of the other IDE's, this will be taken care of for you. If not, you'll have to use the command line compiler in a command window. (Search the .Net SDK or Google to find out how to use the .Net command-line compilers.)
There's one more step to make this work. Your web.config file has to tell the server how to handle the .jpg requests. Drilling down through the file's schema, you'll need to add the httpHandlers section if it's not already there, then place an add tag.
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.web> <httpHandlers> <add verb="*" path="*.jpg" type="JpgHandler, MyDll"/> </httpHandlers> </system.web> </configuration>
The type is the name of the class, JpgHandler, followed by the assembly in your /bin folder that contains the class.
That's all it takes! Now bandwidth leechers will only see a nasty message and maybe provide free advertising for you on someone else's site.