I need to implement CDN in an ASP.NET project. The best Idea I could come up with is creating a custom HttpModule which will handle the PostRequestHandlerExecute event. I'm replacing all the static URLs domain with the CDN's zone url before the response is sent to the client.
Here's my module code:
using System;
using System.Configuration;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
public class CdnModule : IHttpModule
{
public static string CdnUrl;
public CdnModule()
{
CdnUrl = ConfigurationManager.AppSettings["CdnUrl"];
}
public String ModuleName
{
get { return "CdnModule"; }
}
// register handlers
public void Init(HttpApplication app)
{
app.PostRequestHandlerExecute += PostRequestHandlerExecuteHandler;
}
private void PostRequestHandlerExecuteHandler(object sender, EventArgs e)
{
var response = HttpContext.Current.Response;
if (response.ContentType == "text/html")
{
response.Filter = new UrlFilter(response.Filter);
}
}
public void Dispose() { }
}
public class UrlFilter : Stream
{
private Stream _responseStream;
StringBuilder _responseHtml;
public UrlFilter(Stream inputStream)
{
_responseStream = inputStream;
_responseHtml = new StringBuilder();
}
#region Filter overrides
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Close()
{
_responseStream.Close();
}
public override void Flush()
{
_responseStream.Flush();
}
public override long Length
{
get { return 0; }
}
public override long Position { get; set; }
public override long Seek(long offset, SeekOrigin origin)
{
return _responseStream.Seek(offset, origin);
}
public override void SetLength(long length)
{
_responseStream.SetLength(length);
}
public override int Read(byte[] buffer, int offset, int count)
{
return _responseStream.Read(buffer, offset, count);
}
#endregion
//Here I'm replacing URLs
public override void Write(byte[] buffer, int offset, int count)
{
var html = Encoding.UTF8.GetString(buffer, offset, count);
//replace relative UploadFile urls with absolute CDN zone
html = html.Replace("\"/UploadFile/", "\"" + CdnModule.CdnUrl + "/UploadFile/");
buffer = Encoding.UTF8.GetBytes(html);
_responseStream.Write(buffer, 0, buffer.Length);
}
#endregion
}
Below piece of code is responsible for intercepting and modifying the response text:
public override void Write(byte[] buffer, int offset, int count)
{
var html = Encoding.UTF8.GetString(buffer, offset, count);
//replace relative UploadFile urls with absolute CDN zone
html = html.Replace("\"/UploadFile/", "\"" + CdnModule.CdnUrl + "/UploadFile/");
buffer = Encoding.UTF8.GetBytes(html);
_responseStream.Write(buffer, 0, buffer.Length);
}
Am I doing it right? or is there any better way of doing this?
Thanks!
Related
I am developing a program that continually sends a stream of data in the background and I want to allow the user to set a cap for both upload and download limit.
I have read up on the token bucket and leaky bucket alghorhithms, and seemingly the latter seems to fit the description since this is not a matter of maximizing the network bandwidth but rather being as unobtrusive as possible.
I am however a bit unsure on how I would implement this. A natural approach is to extend the abstract Stream class to make it simple to extend existing traffic, but would this not require the involvement of extra threads to send the data while simultaneously receiving (leaky bucket)? Any hints on other implementations that do the same would be appreciated.
Also, although I can modify how much data the program receives, how well does bandwidth throttling work at the C# level? Will the computer still receive the data and simply save it, effectively canceling the throttling effect or will it wait until I ask to receive more?
EDIT: I am interested in throttling both incoming and outgoing data, where I have no control over the opposite end of the stream.
Based on #0xDEADBEEF's solution I created the following (testable) solution based on Rx schedulers:
public class ThrottledStream : Stream
{
private readonly Stream parent;
private readonly int maxBytesPerSecond;
private readonly IScheduler scheduler;
private readonly IStopwatch stopwatch;
private long processed;
public ThrottledStream(Stream parent, int maxBytesPerSecond, IScheduler scheduler)
{
this.maxBytesPerSecond = maxBytesPerSecond;
this.parent = parent;
this.scheduler = scheduler;
stopwatch = scheduler.StartStopwatch();
processed = 0;
}
public ThrottledStream(Stream parent, int maxBytesPerSecond)
: this (parent, maxBytesPerSecond, Scheduler.Immediate)
{
}
protected void Throttle(int bytes)
{
processed += bytes;
var targetTime = TimeSpan.FromSeconds((double)processed / maxBytesPerSecond);
var actualTime = stopwatch.Elapsed;
var sleep = targetTime - actualTime;
if (sleep > TimeSpan.Zero)
{
using (var waitHandle = new AutoResetEvent(initialState: false))
{
scheduler.Sleep(sleep).GetAwaiter().OnCompleted(() => waitHandle.Set());
waitHandle.WaitOne();
}
}
}
public override bool CanRead
{
get { return parent.CanRead; }
}
public override bool CanSeek
{
get { return parent.CanSeek; }
}
public override bool CanWrite
{
get { return parent.CanWrite; }
}
public override void Flush()
{
parent.Flush();
}
public override long Length
{
get { return parent.Length; }
}
public override long Position
{
get
{
return parent.Position;
}
set
{
parent.Position = value;
}
}
public override int Read(byte[] buffer, int offset, int count)
{
var read = parent.Read(buffer, offset, count);
Throttle(read);
return read;
}
public override long Seek(long offset, SeekOrigin origin)
{
return parent.Seek(offset, origin);
}
public override void SetLength(long value)
{
parent.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
Throttle(count);
parent.Write(buffer, offset, count);
}
}
and some tests that just take some milliseconds:
[TestMethod]
public void ShouldThrottleReading()
{
var content = Enumerable
.Range(0, 1024 * 1024)
.Select(_ => (byte)'a')
.ToArray();
var scheduler = new TestScheduler();
var source = new ThrottledStream(new MemoryStream(content), content.Length / 8, scheduler);
var target = new MemoryStream();
var t = source.CopyToAsync(target);
t.Wait(10).Should().BeFalse();
scheduler.AdvanceTo(TimeSpan.FromSeconds(4).Ticks);
t.Wait(10).Should().BeFalse();
scheduler.AdvanceTo(TimeSpan.FromSeconds(8).Ticks - 1);
t.Wait(10).Should().BeFalse();
scheduler.AdvanceTo(TimeSpan.FromSeconds(8).Ticks);
t.Wait(10).Should().BeTrue();
}
[TestMethod]
public void ShouldThrottleWriting()
{
var content = Enumerable
.Range(0, 1024 * 1024)
.Select(_ => (byte)'a')
.ToArray();
var scheduler = new TestScheduler();
var source = new MemoryStream(content);
var target = new ThrottledStream(new MemoryStream(), content.Length / 8, scheduler);
var t = source.CopyToAsync(target);
t.Wait(10).Should().BeFalse();
scheduler.AdvanceTo(TimeSpan.FromSeconds(4).Ticks);
t.Wait(10).Should().BeFalse();
scheduler.AdvanceTo(TimeSpan.FromSeconds(8).Ticks - 1);
t.Wait(10).Should().BeFalse();
scheduler.AdvanceTo(TimeSpan.FromSeconds(8).Ticks);
t.Wait(10).Should().BeTrue();
}
I came up with a different implementation of the ThrottledStream-Class mentioned by arul. My version uses a WaitHandle and a Timer with a 1s Interval:
public ThrottledStream(Stream parentStream, int maxBytesPerSecond=int.MaxValue)
{
MaxBytesPerSecond = maxBytesPerSecond;
parent = parentStream;
processed = 0;
resettimer = new System.Timers.Timer();
resettimer.Interval = 1000;
resettimer.Elapsed += resettimer_Elapsed;
resettimer.Start();
}
protected void Throttle(int bytes)
{
try
{
processed += bytes;
if (processed >= maxBytesPerSecond)
wh.WaitOne();
}
catch
{
}
}
private void resettimer_Elapsed(object sender, ElapsedEventArgs e)
{
processed = 0;
wh.Set();
}
Whenever the bandwidth-limit exceeds the Thread will sleep until the next second begins. No need to calculate the optimal sleep duration.
Full Implementation:
public class ThrottledStream : Stream
{
#region Properties
private int maxBytesPerSecond;
/// <summary>
/// Number of Bytes that are allowed per second
/// </summary>
public int MaxBytesPerSecond
{
get { return maxBytesPerSecond; }
set
{
if (value < 1)
throw new ArgumentException("MaxBytesPerSecond has to be >0");
maxBytesPerSecond = value;
}
}
#endregion
#region Private Members
private int processed;
System.Timers.Timer resettimer;
AutoResetEvent wh = new AutoResetEvent(true);
private Stream parent;
#endregion
/// <summary>
/// Creates a new Stream with Databandwith cap
/// </summary>
/// <param name="parentStream"></param>
/// <param name="maxBytesPerSecond"></param>
public ThrottledStream(Stream parentStream, int maxBytesPerSecond=int.MaxValue)
{
MaxBytesPerSecond = maxBytesPerSecond;
parent = parentStream;
processed = 0;
resettimer = new System.Timers.Timer();
resettimer.Interval = 1000;
resettimer.Elapsed += resettimer_Elapsed;
resettimer.Start();
}
protected void Throttle(int bytes)
{
try
{
processed += bytes;
if (processed >= maxBytesPerSecond)
wh.WaitOne();
}
catch
{
}
}
private void resettimer_Elapsed(object sender, ElapsedEventArgs e)
{
processed = 0;
wh.Set();
}
#region Stream-Overrides
public override void Close()
{
resettimer.Stop();
resettimer.Close();
base.Close();
}
protected override void Dispose(bool disposing)
{
resettimer.Dispose();
base.Dispose(disposing);
}
public override bool CanRead
{
get { return parent.CanRead; }
}
public override bool CanSeek
{
get { return parent.CanSeek; }
}
public override bool CanWrite
{
get { return parent.CanWrite; }
}
public override void Flush()
{
parent.Flush();
}
public override long Length
{
get { return parent.Length; }
}
public override long Position
{
get
{
return parent.Position;
}
set
{
parent.Position = value;
}
}
public override int Read(byte[] buffer, int offset, int count)
{
Throttle(count);
return parent.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
return parent.Seek(offset, origin);
}
public override void SetLength(long value)
{
parent.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
Throttle(count);
parent.Write(buffer, offset, count);
}
#endregion
}
I'm using HttpContext.Current.Response.Filter in HttpModule to replace some strings on my pages.
public class RplModule : IHttpModule
{
public void Init(HttpApplication app)
{
app.BeginRequest += new EventHandler(context_ReleaseRequestState);
}
public void context_ReleaseRequestState(object sender, EventArgs e)
{
HttpContext.Current.Response.Filter = new HtmlFilter(HttpContext.Current.Response.Filter);
}
public void Dispose() { }
}
and here is HtmlFilter Class:
public class HtmlFilter : MemoryStream
{
private readonly Stream _outputStream;
public HtmlFilter(Stream outputStream)
{
_outputStream = outputStream;
}
public override void Write(byte[] buffer, int offset, int count)
{
var contentInBuffer = Encoding.UTF8.GetString(buffer);
contentInBuffer = contentInBuffer.Replace("href=\"Res", "href=\"User/Res");
_outputStream.Write(Encoding.UTF8.GetBytes(contentInBuffer), offset, Encoding.UTF8.GetByteCount(contentInBuffer));
_outputStream.Flush();
}
}
It works fine and will replace every occurrence, But the problem is that it will break some lines and some texts and codes will be mixed up and the page can not be load well. Sometimes it will add some strange string in some places too!
Where is the problem?
I am using LibCurlNet to transfer files over the net. I was looking for something like bandwidth throttling using LibCurlNet. Does anyone has any Idea about it?
I have found that we can do it using "curlopt_max_recv_speed_large". Didn't find it in libcurlnet.
Please note I am using it in C#.
Thanks
you can find information about bandwidth throttling here a modified excerpt from that website
using System;
using System.IO;
using System.Threading;
using System.Diagnostics;
namespace Born2Code.Net
{
/// <summary>
/// Class for streaming data with throttling support.
/// </summary>
public class ThrottledStream : Stream
{
public const long Infinite = 0;
#region Private members
private Stream _baseStream;
private long _maximumBytesPerSecond;
private long _byteCount;
private long _start;
#endregion
#region Properties
protected long CurrentMilliseconds
{
get
{
return Environment.TickCount;
}
}
public long MaximumBytesPerSecond
{
get
{
return _maximumBytesPerSecond;
}
set
{
if (MaximumBytesPerSecond != value)
{
_maximumBytesPerSecond = value;
Reset();
}
}
}
public override bool CanRead
{
get
{
return _baseStream.CanRead;
}
}
public override bool CanSeek
{
get
{
return _baseStream.CanSeek;
}
}
public override bool CanWrite
{
get
{
return _baseStream.CanWrite;
}
}
public override long Length
{
get
{
return _baseStream.Length;
}
}
public override long Position
{
get
{
return _baseStream.Position;
}
set
{
_baseStream.Position = value;
}
}
#endregion
#region Ctor
public ThrottledStream(Stream baseStream)
: this(baseStream, ThrottledStream.Infinite)
{
// Nothing todo.
}
public ThrottledStream(Stream baseStream, long maximumBytesPerSecond)
{
if (baseStream == null)
{
throw new ArgumentNullException("baseStream");
}
if (maximumBytesPerSecond < 0)
{
throw new ArgumentOutOfRangeException("maximumBytesPerSecond",
maximumBytesPerSecond, "The maximum number of bytes per second can't be negatie.");
}
_baseStream = baseStream;
_maximumBytesPerSecond = maximumBytesPerSecond;
_start = CurrentMilliseconds;
_byteCount = 0;
}
#endregion
#region Public methods
public override void Flush()
{
_baseStream.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
Throttle(count);
return _baseStream.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
return _baseStream.Seek(offset, origin);
}
public override void SetLength(long value)
{
_baseStream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
Throttle(count);
_baseStream.Write(buffer, offset, count);
}
public override string ToString()
{
return _baseStream.ToString();
}
#endregion
#region Protected methods
protected void Throttle(int bufferSizeInBytes)
{
if (_maximumBytesPerSecond <= 0 || bufferSizeInBytes <= 0)
{
return;
}
_byteCount += bufferSizeInBytes;
long elapsedMilliseconds = CurrentMilliseconds - _start;
if (elapsedMilliseconds > 0)
{
long bps = _byteCount * 1000L / elapsedMilliseconds;
if (bps > _maximumBytesPerSecond)
{
long wakeElapsed = _byteCount * 1000L / _maximumBytesPerSecond;
int toSleep = (int)(wakeElapsed - elapsedMilliseconds);
if (toSleep > 1)
{
try
{
Thread.Sleep(toSleep);
}
catch (ThreadAbortException)
{
}
Reset();
}
}
}
}
protected void Reset()
{
long difference = CurrentMilliseconds - _start;
if (difference > 1000)
{
_byteCount = 0;
_start = CurrentMilliseconds;
}
}
#endregion
}
}
I've developed custom HTTP module that processes .aspx page (it just sets app.Response.Filter for doing some simple string replacing) after it is rendered by ASP.NET. It is working perfectly, but I am running into one small problem - OutputCache HTTP module will not cache changes I'm doing with app.Response.Filter.
Because of performance benefit I would love if it would be somehow possible to inverse String Replacing and Output Caching.
So, is there a way to do this? Would using HttpHandlers be the way to go?
Here is the current source code of replacer:
public class StringReplaceModule : IHttpModule
{
void IHttpModule.Dispose()
{
// Nothing to dispose;
}
void IHttpModule.Init(HttpApplication context)
{
context.PreSendRequestHeaders +=
(sender, e) => HttpContext.Current.Response.Headers.Remove("Server");
context.BeginRequest += new EventHandler(context_BeginRequest);
}
void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
string url = app.Request.RawUrl.ToLower();
if (!url.Contains(".aspx/") &&
(url.Contains(".aspx") || url.Contains(".css") || url.Contains("/shorturl/")))
{
app.Response.Filter = new StringReplaceFilter(app.Response.Filter);
}
}
#region Stream filter
private class StringReplaceFilter : Stream
{
public StringReplaceFilter(Stream sink)
{
_sink = sink;
}
private Stream _sink;
private static string[] find;
private static string[] replace;
static StringReplaceFilter()
{
var config = StringReplaceModuleConfig.CurrentConfigSection();
find = config.Find.ToArray();
replace = config.Replace.ToArray();
}
public override void Write(byte[] buffer, int offset, int count)
{
byte[] data = new byte[count];
Buffer.BlockCopy(buffer, offset, data, 0, count);
string html = System.Text.Encoding.Default.GetString(buffer);
for (int i = 0; i < find.Length; i++)
{
html = html.Replace(find[i], replace[i]);
}
byte[] outdata = System.Text.Encoding.Default.GetBytes(html);
_sink.Write(outdata, 0, outdata.GetLength(0));
}
#region Less Important
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush()
{
_sink.Flush();
}
public override long Length
{
get { return 0; }
}
private long _position;
public override long Position
{
get { return _position; }
set { _position = value; }
}
public override int Read(byte[] buffer, int offset, int count)
{
return _sink.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
return _sink.Seek(offset, origin);
}
public override void SetLength(long value)
{
_sink.SetLength(value);
}
public override void Close()
{
_sink.Close();
}
#endregion
}
#endregion
}
Can you paste some sample code of what you are doing here?
It sounds like you are trying to find a way of replacing any instance of a certain word/keyword with something else if you find it in the output?
You can try doing this http://forums.asp.net/t/1123505.aspx and adding a explicit expiration rather than using the output cache facilities of asp.net
using System;
using System.Web;
namespace TT.Web.HttpModules
{
/// <summary>
/// HttpModule to prevent caching
/// </summary>
public class NoCacheModule : IHttpModule
{
public NoCacheModule()
{
}
#region IHttpModule Members
public void Init(HttpApplication context)
{
context.EndRequest += (new EventHandler(this.Application_EndRequest));
}
public void Dispose()
{
}
private void Application_EndRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Cache.SetLastModified(DateTime.Now);
context.Response.Cache.SetExpires(DateTime.Now.AddMinutes(GetExpiryTime()));
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.AppendCacheExtension("post-check=7200");
//The pre-check is in seconds and the value configured in web.config is in minutes. So need to multiply it with 60
context.Response.Cache.AppendCacheExtension("pre-check=" + (GetExpiryTime() * 60).ToString());
context.Response.CacheControl = "public";
}
#endregion
}
}
Also - Is this page being loaded only once? Or on a postback?
Response.Filter filters the outgoing stream so by that time the page has already been generated and is (cached and) ready to send out.
You either have to add custom caching to your filter (would affect performance on every hit) or have your module hook into an event earlier in the lifecycle, such as UpdateRequestCache, so that the string replacing only happens once before the content is cached.
I am developing a program that continually sends a stream of data in the background and I want to allow the user to set a cap for both upload and download limit.
I have read up on the token bucket and leaky bucket alghorhithms, and seemingly the latter seems to fit the description since this is not a matter of maximizing the network bandwidth but rather being as unobtrusive as possible.
I am however a bit unsure on how I would implement this. A natural approach is to extend the abstract Stream class to make it simple to extend existing traffic, but would this not require the involvement of extra threads to send the data while simultaneously receiving (leaky bucket)? Any hints on other implementations that do the same would be appreciated.
Also, although I can modify how much data the program receives, how well does bandwidth throttling work at the C# level? Will the computer still receive the data and simply save it, effectively canceling the throttling effect or will it wait until I ask to receive more?
EDIT: I am interested in throttling both incoming and outgoing data, where I have no control over the opposite end of the stream.
Based on #0xDEADBEEF's solution I created the following (testable) solution based on Rx schedulers:
public class ThrottledStream : Stream
{
private readonly Stream parent;
private readonly int maxBytesPerSecond;
private readonly IScheduler scheduler;
private readonly IStopwatch stopwatch;
private long processed;
public ThrottledStream(Stream parent, int maxBytesPerSecond, IScheduler scheduler)
{
this.maxBytesPerSecond = maxBytesPerSecond;
this.parent = parent;
this.scheduler = scheduler;
stopwatch = scheduler.StartStopwatch();
processed = 0;
}
public ThrottledStream(Stream parent, int maxBytesPerSecond)
: this (parent, maxBytesPerSecond, Scheduler.Immediate)
{
}
protected void Throttle(int bytes)
{
processed += bytes;
var targetTime = TimeSpan.FromSeconds((double)processed / maxBytesPerSecond);
var actualTime = stopwatch.Elapsed;
var sleep = targetTime - actualTime;
if (sleep > TimeSpan.Zero)
{
using (var waitHandle = new AutoResetEvent(initialState: false))
{
scheduler.Sleep(sleep).GetAwaiter().OnCompleted(() => waitHandle.Set());
waitHandle.WaitOne();
}
}
}
public override bool CanRead
{
get { return parent.CanRead; }
}
public override bool CanSeek
{
get { return parent.CanSeek; }
}
public override bool CanWrite
{
get { return parent.CanWrite; }
}
public override void Flush()
{
parent.Flush();
}
public override long Length
{
get { return parent.Length; }
}
public override long Position
{
get
{
return parent.Position;
}
set
{
parent.Position = value;
}
}
public override int Read(byte[] buffer, int offset, int count)
{
var read = parent.Read(buffer, offset, count);
Throttle(read);
return read;
}
public override long Seek(long offset, SeekOrigin origin)
{
return parent.Seek(offset, origin);
}
public override void SetLength(long value)
{
parent.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
Throttle(count);
parent.Write(buffer, offset, count);
}
}
and some tests that just take some milliseconds:
[TestMethod]
public void ShouldThrottleReading()
{
var content = Enumerable
.Range(0, 1024 * 1024)
.Select(_ => (byte)'a')
.ToArray();
var scheduler = new TestScheduler();
var source = new ThrottledStream(new MemoryStream(content), content.Length / 8, scheduler);
var target = new MemoryStream();
var t = source.CopyToAsync(target);
t.Wait(10).Should().BeFalse();
scheduler.AdvanceTo(TimeSpan.FromSeconds(4).Ticks);
t.Wait(10).Should().BeFalse();
scheduler.AdvanceTo(TimeSpan.FromSeconds(8).Ticks - 1);
t.Wait(10).Should().BeFalse();
scheduler.AdvanceTo(TimeSpan.FromSeconds(8).Ticks);
t.Wait(10).Should().BeTrue();
}
[TestMethod]
public void ShouldThrottleWriting()
{
var content = Enumerable
.Range(0, 1024 * 1024)
.Select(_ => (byte)'a')
.ToArray();
var scheduler = new TestScheduler();
var source = new MemoryStream(content);
var target = new ThrottledStream(new MemoryStream(), content.Length / 8, scheduler);
var t = source.CopyToAsync(target);
t.Wait(10).Should().BeFalse();
scheduler.AdvanceTo(TimeSpan.FromSeconds(4).Ticks);
t.Wait(10).Should().BeFalse();
scheduler.AdvanceTo(TimeSpan.FromSeconds(8).Ticks - 1);
t.Wait(10).Should().BeFalse();
scheduler.AdvanceTo(TimeSpan.FromSeconds(8).Ticks);
t.Wait(10).Should().BeTrue();
}
I came up with a different implementation of the ThrottledStream-Class mentioned by arul. My version uses a WaitHandle and a Timer with a 1s Interval:
public ThrottledStream(Stream parentStream, int maxBytesPerSecond=int.MaxValue)
{
MaxBytesPerSecond = maxBytesPerSecond;
parent = parentStream;
processed = 0;
resettimer = new System.Timers.Timer();
resettimer.Interval = 1000;
resettimer.Elapsed += resettimer_Elapsed;
resettimer.Start();
}
protected void Throttle(int bytes)
{
try
{
processed += bytes;
if (processed >= maxBytesPerSecond)
wh.WaitOne();
}
catch
{
}
}
private void resettimer_Elapsed(object sender, ElapsedEventArgs e)
{
processed = 0;
wh.Set();
}
Whenever the bandwidth-limit exceeds the Thread will sleep until the next second begins. No need to calculate the optimal sleep duration.
Full Implementation:
public class ThrottledStream : Stream
{
#region Properties
private int maxBytesPerSecond;
/// <summary>
/// Number of Bytes that are allowed per second
/// </summary>
public int MaxBytesPerSecond
{
get { return maxBytesPerSecond; }
set
{
if (value < 1)
throw new ArgumentException("MaxBytesPerSecond has to be >0");
maxBytesPerSecond = value;
}
}
#endregion
#region Private Members
private int processed;
System.Timers.Timer resettimer;
AutoResetEvent wh = new AutoResetEvent(true);
private Stream parent;
#endregion
/// <summary>
/// Creates a new Stream with Databandwith cap
/// </summary>
/// <param name="parentStream"></param>
/// <param name="maxBytesPerSecond"></param>
public ThrottledStream(Stream parentStream, int maxBytesPerSecond=int.MaxValue)
{
MaxBytesPerSecond = maxBytesPerSecond;
parent = parentStream;
processed = 0;
resettimer = new System.Timers.Timer();
resettimer.Interval = 1000;
resettimer.Elapsed += resettimer_Elapsed;
resettimer.Start();
}
protected void Throttle(int bytes)
{
try
{
processed += bytes;
if (processed >= maxBytesPerSecond)
wh.WaitOne();
}
catch
{
}
}
private void resettimer_Elapsed(object sender, ElapsedEventArgs e)
{
processed = 0;
wh.Set();
}
#region Stream-Overrides
public override void Close()
{
resettimer.Stop();
resettimer.Close();
base.Close();
}
protected override void Dispose(bool disposing)
{
resettimer.Dispose();
base.Dispose(disposing);
}
public override bool CanRead
{
get { return parent.CanRead; }
}
public override bool CanSeek
{
get { return parent.CanSeek; }
}
public override bool CanWrite
{
get { return parent.CanWrite; }
}
public override void Flush()
{
parent.Flush();
}
public override long Length
{
get { return parent.Length; }
}
public override long Position
{
get
{
return parent.Position;
}
set
{
parent.Position = value;
}
}
public override int Read(byte[] buffer, int offset, int count)
{
Throttle(count);
return parent.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
return parent.Seek(offset, origin);
}
public override void SetLength(long value)
{
parent.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
Throttle(count);
parent.Write(buffer, offset, count);
}
#endregion
}