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 MasterPagePage_Load:
Create a BandwidthThrottleFilter class that inherits from the abstract class Stream
Add a constructor that receives as parameters a Stream and an integer
Add fields that will get instantiated from these two parameters
Implement all abstract methods of the Stream object and use the same methods from the Stream field
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.
Comments
<p>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. <br><br>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.</p>
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. <br><br>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
<p>This comment has been removed by the author.</p>
Siderite
This comment has been removed by the author.
Siderite
<p>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.<br><br>Now my question is, when it comes to bandwidth, is it actually Kilobits (1000 bits), or Kibibits (1024 bits)?<br><br>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!<br><br>Thanks,<br><br>- Sharpzy</p>
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.<br><br>Now my question is, when it comes to bandwidth, is it actually Kilobits (1000 bits), or Kibibits (1024 bits)?<br><br>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!<br><br>Thanks,<br><br>- Sharpzy
Anonymous
<p>Arghh, VB on my blog! Quick, call a priest! An exorcism must be performed :)<br><br> Thanks for the code, Evan!</p>
Siderite
Arghh, VB on my blog! Quick, call a priest! An exorcism must be performed :)<br><br> Thanks for the code, Evan!
Siderite
<p>Here it is in VB.Net<br><br>The line to put in the MasterPage<br>Response.Filter = New BandwidthThrottleFilter(Response.Filter, 10000)<br><br><br><br>Imports System.IO<br>Imports System.Threading<br><br>Public Class BandwidthThrottleFilter<br> Inherits Stream<br> Private ReadOnly _bytesPerSecond As Integer<br> Private ReadOnly _sink As Stream<br><br> Public Sub New(ByVal sink As Stream, ByVal bytesPerSecond As Integer)<br> _sink = sink<br> _bytesPerSecond = bytesPerSecond<br> End Sub<br><br><br> Private Sub Delay(ByVal length As Integer)<br> Dim milliseconds As Integer = length * 1000 / _bytesPerSecond<br> Thread.Sleep(milliseconds)<br> End Sub<br><br> Public Overloads Overrides ReadOnly Property CanRead() As Boolean<br> Get<br> Return _sink.CanRead<br> End Get<br> End Property<br><br> Public Overloads Overrides ReadOnly Property CanSeek() As Boolean<br> Get<br> Return _sink.CanSeek<br> End Get<br> End Property<br><br> Public Overloads Overrides ReadOnly Property CanWrite() As Boolean<br> Get<br> Return _sink.CanWrite<br> End Get<br> End Property<br><br> Public Overloads Overrides ReadOnly Property Length() As Long<br> Get<br> Return _sink.Length<br> End Get<br> End Property<br><br> Public Overloads Overrides Property Position() As Long<br> Get<br> Return _sink.Position<br> End Get<br> Set(ByVal value As Long)<br> _sink.Position = value<br> End Set<br> End Property<br><br> Public Overloads Overrides Sub Flush()<br> _sink.Flush()<br> End Sub<br><br> Public Overloads Overrides Function Seek(ByVal offset As Long, ByVal origin As SeekOrigin) As Long<br> Return _sink.Seek(offset, origin)<br> End Function<br><br> Public Overloads Overrides Sub SetLength(ByVal value As Long)<br> _sink.SetLength(value)<br> End Sub<br><br> Public Overloads Overrides Function Read(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer) As Integer<br> Return _sink.Read(buffer, offset, count)<br> End Function<br><br> Public Overloads Overrides Sub Write(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer)<br> Delay(count)<br> _sink.Write(buffer, offset, count)<br> End Sub<br>End Class</p>
Evan
Here it is in VB.Net<br><br>The line to put in the MasterPage<br>Response.Filter = New BandwidthThrottleFilter(Response.Filter, 10000)<br><br><br><br>Imports System.IO<br>Imports System.Threading<br><br>Public Class BandwidthThrottleFilter<br> Inherits Stream<br> Private ReadOnly _bytesPerSecond As Integer<br> Private ReadOnly _sink As Stream<br><br> Public Sub New(ByVal sink As Stream, ByVal bytesPerSecond As Integer)<br> _sink = sink<br> _bytesPerSecond = bytesPerSecond<br> End Sub<br><br><br> Private Sub Delay(ByVal length As Integer)<br> Dim milliseconds As Integer = length * 1000 / _bytesPerSecond<br> Thread.Sleep(milliseconds)<br> End Sub<br><br> Public Overloads Overrides ReadOnly Property CanRead() As Boolean<br> Get<br> Return _sink.CanRead<br> End Get<br> End Property<br><br> Public Overloads Overrides ReadOnly Property CanSeek() As Boolean<br> Get<br> Return _sink.CanSeek<br> End Get<br> End Property<br><br> Public Overloads Overrides ReadOnly Property CanWrite() As Boolean<br> Get<br> Return _sink.CanWrite<br> End Get<br> End Property<br><br> Public Overloads Overrides ReadOnly Property Length() As Long<br> Get<br> Return _sink.Length<br> End Get<br> End Property<br><br> Public Overloads Overrides Property Position() As Long<br> Get<br> Return _sink.Position<br> End Get<br> Set(ByVal value As Long)<br> _sink.Position = value<br> End Set<br> End Property<br><br> Public Overloads Overrides Sub Flush()<br> _sink.Flush()<br> End Sub<br><br> Public Overloads Overrides Function Seek(ByVal offset As Long, ByVal origin As SeekOrigin) As Long<br> Return _sink.Seek(offset, origin)<br> End Function<br><br> Public Overloads Overrides Sub SetLength(ByVal value As Long)<br> _sink.SetLength(value)<br> End Sub<br><br> Public Overloads Overrides Function Read(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer) As Integer<br> Return _sink.Read(buffer, offset, count)<br> End Function<br><br> Public Overloads Overrides Sub Write(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer)<br> Delay(count)<br> _sink.Write(buffer, offset, count)<br> End Sub<br>End Class
Evan
<p>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.<br><br>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.<br><br>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.</p>
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.<br><br>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.<br><br>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.
Comments
<p>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. <br><br>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.</p>
SideriteSorry, 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. <br><br>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<p>This comment has been removed by the author.</p>
SideriteThis comment has been removed by the author.
Siderite<p>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.<br><br>Now my question is, when it comes to bandwidth, is it actually Kilobits (1000 bits), or Kibibits (1024 bits)?<br><br>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!<br><br>Thanks,<br><br>- Sharpzy</p>
AnonymousThe 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.<br><br>Now my question is, when it comes to bandwidth, is it actually Kilobits (1000 bits), or Kibibits (1024 bits)?<br><br>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!<br><br>Thanks,<br><br>- Sharpzy
Anonymous<p>Arghh, VB on my blog! Quick, call a priest! An exorcism must be performed :)<br><br> Thanks for the code, Evan!</p>
SideriteArghh, VB on my blog! Quick, call a priest! An exorcism must be performed :)<br><br> Thanks for the code, Evan!
Siderite<p>Here it is in VB.Net<br><br>The line to put in the MasterPage<br>Response.Filter = New BandwidthThrottleFilter(Response.Filter, 10000)<br><br><br><br>Imports System.IO<br>Imports System.Threading<br><br>Public Class BandwidthThrottleFilter<br> Inherits Stream<br> Private ReadOnly _bytesPerSecond As Integer<br> Private ReadOnly _sink As Stream<br><br> Public Sub New(ByVal sink As Stream, ByVal bytesPerSecond As Integer)<br> _sink = sink<br> _bytesPerSecond = bytesPerSecond<br> End Sub<br><br><br> Private Sub Delay(ByVal length As Integer)<br> Dim milliseconds As Integer = length * 1000 / _bytesPerSecond<br> Thread.Sleep(milliseconds)<br> End Sub<br><br> Public Overloads Overrides ReadOnly Property CanRead() As Boolean<br> Get<br> Return _sink.CanRead<br> End Get<br> End Property<br><br> Public Overloads Overrides ReadOnly Property CanSeek() As Boolean<br> Get<br> Return _sink.CanSeek<br> End Get<br> End Property<br><br> Public Overloads Overrides ReadOnly Property CanWrite() As Boolean<br> Get<br> Return _sink.CanWrite<br> End Get<br> End Property<br><br> Public Overloads Overrides ReadOnly Property Length() As Long<br> Get<br> Return _sink.Length<br> End Get<br> End Property<br><br> Public Overloads Overrides Property Position() As Long<br> Get<br> Return _sink.Position<br> End Get<br> Set(ByVal value As Long)<br> _sink.Position = value<br> End Set<br> End Property<br><br> Public Overloads Overrides Sub Flush()<br> _sink.Flush()<br> End Sub<br><br> Public Overloads Overrides Function Seek(ByVal offset As Long, ByVal origin As SeekOrigin) As Long<br> Return _sink.Seek(offset, origin)<br> End Function<br><br> Public Overloads Overrides Sub SetLength(ByVal value As Long)<br> _sink.SetLength(value)<br> End Sub<br><br> Public Overloads Overrides Function Read(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer) As Integer<br> Return _sink.Read(buffer, offset, count)<br> End Function<br><br> Public Overloads Overrides Sub Write(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer)<br> Delay(count)<br> _sink.Write(buffer, offset, count)<br> End Sub<br>End Class</p>
EvanHere it is in VB.Net<br><br>The line to put in the MasterPage<br>Response.Filter = New BandwidthThrottleFilter(Response.Filter, 10000)<br><br><br><br>Imports System.IO<br>Imports System.Threading<br><br>Public Class BandwidthThrottleFilter<br> Inherits Stream<br> Private ReadOnly _bytesPerSecond As Integer<br> Private ReadOnly _sink As Stream<br><br> Public Sub New(ByVal sink As Stream, ByVal bytesPerSecond As Integer)<br> _sink = sink<br> _bytesPerSecond = bytesPerSecond<br> End Sub<br><br><br> Private Sub Delay(ByVal length As Integer)<br> Dim milliseconds As Integer = length * 1000 / _bytesPerSecond<br> Thread.Sleep(milliseconds)<br> End Sub<br><br> Public Overloads Overrides ReadOnly Property CanRead() As Boolean<br> Get<br> Return _sink.CanRead<br> End Get<br> End Property<br><br> Public Overloads Overrides ReadOnly Property CanSeek() As Boolean<br> Get<br> Return _sink.CanSeek<br> End Get<br> End Property<br><br> Public Overloads Overrides ReadOnly Property CanWrite() As Boolean<br> Get<br> Return _sink.CanWrite<br> End Get<br> End Property<br><br> Public Overloads Overrides ReadOnly Property Length() As Long<br> Get<br> Return _sink.Length<br> End Get<br> End Property<br><br> Public Overloads Overrides Property Position() As Long<br> Get<br> Return _sink.Position<br> End Get<br> Set(ByVal value As Long)<br> _sink.Position = value<br> End Set<br> End Property<br><br> Public Overloads Overrides Sub Flush()<br> _sink.Flush()<br> End Sub<br><br> Public Overloads Overrides Function Seek(ByVal offset As Long, ByVal origin As SeekOrigin) As Long<br> Return _sink.Seek(offset, origin)<br> End Function<br><br> Public Overloads Overrides Sub SetLength(ByVal value As Long)<br> _sink.SetLength(value)<br> End Sub<br><br> Public Overloads Overrides Function Read(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer) As Integer<br> Return _sink.Read(buffer, offset, count)<br> End Function<br><br> Public Overloads Overrides Sub Write(ByVal buffer As Byte(), ByVal offset As Integer, ByVal count As Integer)<br> Delay(count)<br> _sink.Write(buffer, offset, count)<br> End Sub<br>End Class
Evan<p>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.<br><br>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.<br><br>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.</p>
serializerThanks! 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.<br><br>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.<br><br>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