Wednesday 13 February 2008

Bandwidth Throttle for ASP.Net

We were working on a project for a company that suddenly started complaining of slow ASP.Net pages. I optimised what I could, but it seemed to me that it ran pretty fast. Then I find out that some of the customers use a slow Internet connection. The only way to test this was to simulate a slow connection.

But how can one do that on IIS 5.1, the Windows XP web server? After a while of searching I realised that it was the wrong question. I don't need this for other projects and if I did I certainly wouldn't want to slow the entire web server to check it out. Because yes, changing the metadata of the server can, supposedly, change the maximum speed the pages are delivered. But it was simply too much hassle and it wasn't a reusable solution.

My way was to create a Filter for the Response of all pages. Response.Filter is supposed to be a Stream that receives as parameter the previous Response.Filter (which at the very start is Response.OutputStream) and does something to the output of the page. So I've created a BandwidthThrottleFilter object and added it in the MasterPage Page_Load:
Response.Filter=new BandwidthThrottleFilter(Response.Fitler,10000);
. It worked.

Now for the code. Follow these steps:
  1. Create a BandwidthThrottleFilter class that inherits from the abstract class Stream
  2. Add a constructor that receives as parameters a Stream and an integer
  3. Add fields that will get instantiated from these two parameters
  4. Implement all abstract methods of the Stream object and use the same methods from the Stream field
  5. Change the Write method to also call a Delay method that receives as parameter the count parameter of the Write method


That's it. You need only create the Delay method which will do a Thread.Sleep for the duration of time it normally should take to transfer that amount of bytes. Of course, that assumes that the normal speed of transfer is negligeable.

Click to see the whole class code

5 comments:

  1. Thanks! This was incredibly useful. I'm implementing an AJAX-like file upload and needed to artificially throttle my local bandwidth, to test the progress bar.

    Your article also pointed me towards a better solution to my original problem: how to actually tell how much of the file has been uploaded.

    By implementing a filter on the Request object (instead of Response) I was able to both keep track of the upload progress (simply by incrementing my counter as the data passed through), as well as being able to slow the upload down and actually see my progress bar working even on localhost.

    ReplyDelete
  2. Here it is in VB.Net

    The line to put in the MasterPage
    Response.Filter = New BandwidthThrottleFilter(Response.Filter, 10000)



    Imports System.IO
    Imports System.Threading

    Public Class BandwidthThrottleFilter
    Inherits Stream
    Private ReadOnly _bytesPerSecond As Integer
    Private ReadOnly _sink As Stream

    Public Sub New(ByVal sink As Stream, ByVal bytesPerSecond As Integer)
    _sink = sink
    _bytesPerSecond = bytesPerSecond
    End Sub


    Private Sub Delay(ByVal length As Integer)
    Dim milliseconds As Integer = length * 1000 / _bytesPerSecond
    Thread.Sleep(milliseconds)
    End Sub

    Public Overloads Overrides ReadOnly Property CanRead() As Boolean
    Get
    Return _sink.CanRead
    End Get
    End Property

    Public Overloads Overrides ReadOnly Property CanSeek() As Boolean
    Get
    Return _sink.CanSeek
    End Get
    End Property

    Public Overloads Overrides ReadOnly Property CanWrite() As Boolean
    Get
    Return _sink.CanWrite
    End Get
    End Property

    Public Overloads Overrides ReadOnly Property Length() As Long
    Get
    Return _sink.Length
    End Get
    End Property

    Public Overloads Overrides Property Position() As Long
    Get
    Return _sink.Position
    End Get
    Set(ByVal value As Long)
    _sink.Position = value
    End Set
    End Property

    Public Overloads Overrides Sub Flush()
    _sink.Flush()
    End Sub

    Public Overloads Overrides Function Seek(ByVal offset As Long, ByVal origin As SeekOrigin) As Long
    Return _sink.Seek(offset, origin)
    End Function

    Public Overloads Overrides Sub SetLength(ByVal value As Long)
    _sink.SetLength(value)
    End Sub

    Public Overloads Overrides Function Read(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer) As Integer
    Return _sink.Read(buffer, offset, count)
    End Function

    Public Overloads Overrides Sub Write(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer)
    Delay(count)
    _sink.Write(buffer, offset, count)
    End Sub
    End Class

    ReplyDelete
  3. Arghh, VB on my blog! Quick, call a priest! An exorcism must be performed :)

    Thanks for the code, Evan!

    ReplyDelete
  4. The first thing I did to this was make it accept Kilobits instead of Bytes, so developers can use familiar numbers like 56 for a 56K modem.

    Now my question is, when it comes to bandwidth, is it actually Kilobits (1000 bits), or Kibibits (1024 bits)?

    Also, if you try to download a small file with a high bandwidth setting, you could get milliseconds=0, which would call Thread.Sleep(0) and suspend the thread!

    Thanks,

    - Sharpzy

    ReplyDelete
  5. Sorry, I was not paying attention when answering your last comment. First of all thank you for pointing out that Thread.Sleep(0) blocked the thread.

    Then the answer to your question is 1000. 56K means 56000 bits. However, that doesn't mean that you can just divide the size into 125 bytes, since 56kbits refers to the entire communication in a physical device, including data, communication information, stop bits, error correction, etc.

    ReplyDelete