Friday, June 24, 2011

HTTP Listener

I put this together to play with the possibility of receiving the XML logs that the SmoothStreamingMediaElement can send. I wasn't thrilled with some of the limitations that the built-in log receiver in IIS had. This HTTP listener was very focused and shouldn't be confused with a true webserver. This was built to eat POSTs as quickly as it could and deal with them as a secondary job.

You could have your worker thread dumping them to MQ or doing bulk inserts into something like InfoBright or MySQL.

It's been so long since I looked at this that I can't remember how many requests per second I was handling - I think it was a measly 10,000 or something. Either way, if you're looking for an asynchronous HTTP client in VB.NET - look no further.

As an afterthought, I'm not sure that you need to synclock the jobQueue object. Since it was created as a shared object, I believe that will handle the synclocking for you.




Imports System.Net.HttpListener
Imports System.Threading
Imports System.Net
Imports System
Imports System.Text
Imports System.IO



Public Class httpListen
Dim parentListener As Net.HttpListener
Public Shared jobQueue As New Queue(10, 1.6)
Dim accessPolicy As Byte() = System.Text.Encoding.UTF8.GetBytes("<?xml version=""1.0"" encoding=""utf-8""?><access-policy><cross-domain-access><policy><allow-from http-request-headers=""*""><domain uri=""*""/></allow-from><grant-to><resource path=""/"" include-subpaths=""true""/></grant-to></policy></cross-domain-access></access-policy>")
Dim readyForNextReq As System.Threading.AutoResetEvent = New System.Threading.AutoResetEvent(False)

Sub Dispose()

End Sub

Sub abort()
parentListener.Abort()
End Sub

Sub start()
parentListener = New Net.HttpListener
parentListener.Prefixes.Add("http://*/")
parentListener.Start()
Try
Dim logWriter As New logWriter
Catch ex As Exception
EventLog.WriteEntry(My.Application.Info.AssemblyName, ex.ToString)
End Try

System.Threading.ThreadPool.QueueUserWorkItem(New System.Threading.WaitCallback(AddressOf ProcessRequest))
End Sub

Sub postJobCount()
Console.WriteLine(jobQueue.Count)
End Sub

Public Sub ProcessRequest()
While parentListener.IsListening
parentListener.BeginGetContext(New AsyncCallback(AddressOf ListenerCallback), parentListener)
readyForNextReq.WaitOne()
End While
End Sub



Sub ListenerCallback(ByVal result As IAsyncResult)
Dim context As System.Net.HttpListenerContext = TryCast(result.AsyncState, System.Net.HttpListener).EndGetContext(result)
Dim requestString() As Byte

readyForNextReq.Set()
Dim request As System.Net.HttpListenerRequest = context.Request
Try
If request.HttpMethod = "POST" Then
If request.HasEntityBody Then
Using requestReader As New BinaryReader(context.Request.InputStream)
Try

requestString = combineBytes(convertAddress(request.RemoteEndPoint.Address.ToString, request.Url.Segments.Last.ToString.Split(".")(0)), requestReader.ReadBytes(5096))

Catch ne As System.Net.HttpListenerException
'The request stream was cut off for some reason
requestReader.Close()
requestString = Nothing
request = Nothing
context = Nothing
Exit Sub
End Try

'Lock the queue so we can drop a logline into it.
SyncLock jobQueue.SyncRoot
jobQueue.Enqueue(requestString)
End SyncLock
requestReader.Close()
requestString = Nothing

Using response As System.Net.HttpListenerResponse = context.Response
'All done, send back a 200
responseCode(context.Response, 200)
End Using
End Using
Else
context.Response.ContentLength64 = accessPolicy.Length
context.Response.OutputStream.Write(accessPolicy, 0, accessPolicy.Length)
'This wasn't an HTTP post.. ignore it.
responseCode(context.Response, 200)
End If
Else
context.Response.ContentLength64 = accessPolicy.Length
context.Response.OutputStream.Write(accessPolicy, 0, accessPolicy.Length)
'This wasn't an HTTP post.. ignore it.
responseCode(context.Response, 200)
End If


request = Nothing
context = Nothing
Catch ex As Exception
EventLog.WriteEntry(My.Application.Info.AssemblyName, "Error in async operation: " & ex.ToString)
End Try

End Sub

Private Function responseCode(ByVal context As System.Net.HttpListenerResponse, ByVal returnCode As Integer) As Boolean
Using response As System.Net.HttpListenerResponse = context
Try
'This wasn't a HTTP POST, ignore it.
response.StatusCode = returnCode
response.OutputStream.Close()
Catch sn As System.Net.HttpListenerException
'The request was terminated before I could close it myself - avoid a crash in this case.
End Try
End Using
End Function

Public Function convertAddress(ByVal ipAddress As String, ByVal uriRequest As String) As Byte()
Dim ipLong As Long = 0
Dim ipBytes As String()

ipBytes = ipAddress.Split("."c)
For i As Integer = ipBytes.Length - 1 To 0 Step -1
ipLong += ((Integer.Parse(ipBytes(i)) Mod 256) * Math.Pow(256, (3 - i)))
Next
'Add a bell character to the string so we can split out the client IP later for DB insertion.
Return System.Text.Encoding.ASCII.GetBytes(ipLong.ToString & Chr(7) & uriRequest & Chr(7))
End Function

Public Shared Function combineBytes(ByVal array1 As Byte(), ByVal array2 As Byte()) As Byte()
Dim bytes As Byte() = New Byte(array1.Length + (array2.Length - 1)) {}
Array.Copy(array1, bytes, array1.Length)
Array.Copy(array2, 0, bytes, array1.Length, array2.Length)
Return bytes
End Function




End Class

1 comment:

  1. Hello Austin,

    As a total newbie I greatly appreciate this script!

    Could you please enlighten how to pass post data pointed to the httplistener into variables for further processing?
    I do not know where to start.

    By thanking you in advance!

    BKS

    ReplyDelete