Creating a Background Thread to Log IP Information

Trolling the ASP.NET forums this afternoon I came across this thread about creating a custom httpHandler to log IP information to a database. What the requester was trying to do was call a remote site's (www.ShowMyIP.com) REST API to get information about the visitor's IP address, such as location and store it in the database. Unfortunately I cannot seem to find documentation about the REST API, but that is not important to this example.

I responded that using an httpHandler in this case would not actually solve the developer's issue, instead I think it could actually harm the performance of his site. This is because what he is doing is actually calling the handler from the page, like an image file. This actually ties up one of the two threads that browsers open up to retrieve content from a web site upon request. If you go to one of my presentations on front-end performance you will learn more about this. The goal being to reduce the number of objects being requested to build the actual page in the browser.

To actually solve the problem of retrieving the detailed information about the visitor's IP address is to create a background thread to make the call to the REST API and process the resulting XML. I am not going to get into the details of processing the XML content in this case, nor am I going to store it in a database. Instead I am going to just store the information in a text file corresponding to the IP address, [IPAddress].txt.

The first step is to create a method to log the IP information. In this case I decided to pass in a custom class as its only parameter. This is because it is hard to transport the IP and physical path to use in the logging operation. The method calls the ShowMyIp.com service by creating a new XDocument object via the Load method. The method has to be shared (static in C#) to be able to work in a thread.

Public Shared Sub

StoreIPInfo(

ByVal

args

As

StoreIpArgs)

Dim

doc

As

XDocument = _ XDocument.Load(

"http://www.showmyip.com/xml/?ip="

& args.UserHost)

Dim

lPath

As String

= Path.Combine(args.AppPath, _

String

.Format(

"{0}.txt"

, args.UserHost))

If

File.Exists(lPath)

Then

File.Delete(lPath)

End If My

.Computer.FileSystem.WriteAllText( _ lPath, doc.ToString(),

True

)

End Sub

It then logs the XML content to the file system, in this case in the root of the Web site. Again this is a simple demonstration.

The next step is to create the thread and perform the normal page operations. In this case you create a new thread and pass the address of the method to be executed by the new thread. In this case it is the StoreIPInfo method created in the previous step.

 

Protected Sub

Page_Load(

ByVal

sender

As Object

, _

ByVal

e

As

System.EventArgs)

Handles Me

.Load

Dim

t

As New

Threading.Thread(

AddressOf

StoreIPInfo)

Dim

args

As New

StoreIpArgs args.AppPath = Request.PhysicalApplicationPath args.UserHost = Request.UserHostAddress t.Start(args) ltlIPAddress.Text = Request.UserHostAddress.ToString()

End Sub

The next thing to do is to create a new instance of a StoreIpArgs class, a custom class I created to hold the value of the user's IP address and the path to store the log file. This class contains two public properties, UserHost and AppPath.

Public Class

StoreIpArgs

Private

_userHost

As String Public Property

UserHost()

As String Get Return

_userHost

End Get Set

(

ByVal

Value

As String

) _userHost = Value

End Set End Property Private

_appPath

As String Public Property

AppPath()

As String Get Return

_appPath

End Get Set

(

ByVal

Value

As String

) _appPath = Value

End Set End PropertyEnd Class

Both of these values can be gathered from the Request object, the UserHostAddress and PhysicalApplicationPath properties. The new new StoreIPArgs object is passed to the Start method of the thread object. This will pass the object to the method, StoreIPInfo, designated when the thread was instantiated.

The other pieces of the page simple output the IP address to the page along with the time the page was rendered to the browser. This is done in the PreRender event handler.

 

Private Sub

Page_PreRender(

ByVal

sender

As Object

, _

ByVal

e

As

System.EventArgs)

Handles Me

.PreRender ltlIPAddress.Text =

String

.Format(

"{0} - {1}"

, _ ltlIPAddress.Text, TimeString.ToString)

End Sub

I did this so I could test to make sure the page was actually rendered before the background thread was completed. I found the call to the external web service was relatively quick and was making it hard to see if it was really completing after the page was rendered. If you download the sample project there should be a couple of breakpoints set so you can step through and see when the methods are called. You will see the PreRender event gets called before the IP information is logged to the file.

This example performs the operation in a page. But I really think this will work great in a custom httpModule. I may revisit the topic, but I think you could just modify my Blog on creating a background thread in a custom httpModule, but instead of just doing it in the Init method you would wire up an event handler to the BeginRequest event and repeat the same basic steps there.

The resulting XML is logged and starts off like the following snippet:

<ip_address>
  <lookups>
    <lookup_ip>127.0.0.1</lookup_ip>
    <lookup_ip_long>2130706433</lookup_ip_long>
    <lookup_ip_is_valid>yes</lookup_ip_is_valid>
    <lookup_ip_is_private>yes</lookup_ip_is_private>
    <lookup_is_ipv6>no</lookup_is_ipv6>
    <lookup_host>private</lookup_host>
    <lookup_reverse_host></lookup_reverse_host>
    <lookup_isp>provided to subscribers only</lookup_isp>
    <lookup_org>provided to subscribers only</lookup_org>

So while it might look like a custom httpHandler is the answer to something like this, it really is not. It would actually cause the page to not render efficiently and probably cause it to render much slower because it would hog one of the browser's download threads. A background thread can make this process more efficient without affecting the rendering of your page in the visitor's browser.

Source Project

Share This Article With Your Friends!