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

Comments

Siderite

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.

Siderite

Siderite

This comment has been removed by the author.

Siderite

Anonymous

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

Anonymous

Siderite

Arghh, VB on my blog! Quick, call a priest! An exorcism must be performed :) Thanks for the code, Evan!

Siderite

Evan

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

Evan

serializer

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.

serializer

Post a comment