├── Fetch ├── App.ico ├── EntryPoint.cs ├── AssemblyInfo.cs ├── Fetch.csproj ├── MainForm.cs └── MainForm.resx ├── Example ├── blowery.gif ├── NoCompress.aspx ├── Default.aspx ├── ExistingImage.ashx ├── ExceptionThrowingHandler.cs ├── DefaultController.cs ├── Image.ashx ├── web.config └── Example.csproj ├── HttpCompress ├── app.config ├── SectionHandler.cs ├── Enums.cs ├── GZipFilter.cs ├── DeflateFilter.cs ├── CompressingFilter.cs ├── HttpOutputFilter.cs ├── AssemblyInfo.cs ├── Settings.cs ├── HttpCompress.csproj └── HttpModule.cs ├── Vendor └── ICSharpCode.SharpZipLib │ └── ICSharpCode.SharpZipLib.dll ├── Tests ├── Utility.cs ├── XmlTestDocuments.xsd ├── AssemblyInfo.cs ├── XmlTestDocuments.resx ├── SettingsTests.cs └── Tests.csproj ├── license.txt ├── HttpCompress.sln ├── readme.txt └── Contrib └── jporter └── HttpCompressionModule.cs /Fetch/App.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blowery/HttpCompress/HEAD/Fetch/App.ico -------------------------------------------------------------------------------- /Example/blowery.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blowery/HttpCompress/HEAD/Example/blowery.gif -------------------------------------------------------------------------------- /HttpCompress/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Vendor/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blowery/HttpCompress/HEAD/Vendor/ICSharpCode.SharpZipLib/ICSharpCode.SharpZipLib.dll -------------------------------------------------------------------------------- /Example/NoCompress.aspx: -------------------------------------------------------------------------------- 1 | <%@ Page %> 2 | 3 | 4 | A non-compressed page! 5 | 6 | 7 |

This page was not compressed! Hopefully.

8 | 9 | -------------------------------------------------------------------------------- /Example/Default.aspx: -------------------------------------------------------------------------------- 1 | <%@ Page Inherits="Example.DefaultController"%> 2 | 4 | 5 | 6 | a test of compression 7 | 8 | 9 | Hi there pokey!
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Fetch/EntryPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | 5 | using System.Windows.Forms; 6 | 7 | namespace Fetch { 8 | class EntryPoint { 9 | 10 | /// 11 | /// The main entry point for the application. 12 | /// 13 | [STAThread] 14 | static void Main(string[] args) { 15 | Application.Run(new MainForm()); 16 | } 17 | 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/Utility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Resources; 3 | 4 | namespace blowery.Web.HttpCompress.Tests { 5 | /// Some utility functions for the tests 6 | sealed class Utility { 7 | private Utility() { } 8 | 9 | public static System.Resources.ResourceManager GetResourceManager(string nameOfResouce) { 10 | return new ResourceManager(Constants.Namespace + "." + nameOfResouce, System.Reflection.Assembly.GetExecutingAssembly()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Example/ExistingImage.ashx: -------------------------------------------------------------------------------- 1 | <%@ WebHandler Language="C#" Class="ExistingImage" %> 2 | 3 | using System.Web; 4 | using System.Drawing; 5 | using System.Drawing.Imaging; 6 | using System.IO; 7 | 8 | public class ExistingImage : IHttpHandler 9 | { 10 | 11 | public void ProcessRequest (HttpContext context) 12 | { 13 | context.Response.ContentType = "image/gif"; 14 | context.Response.WriteFile("blowery.gif"); 15 | } 16 | 17 | 18 | public bool IsReusable 19 | { 20 | get { return true; } 21 | } 22 | } -------------------------------------------------------------------------------- /Example/ExceptionThrowingHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web; 3 | 4 | namespace Example 5 | { 6 | /// 7 | /// An HttpHandler that just throws exceptions. 8 | /// 9 | public class ExceptionThrowingHandler : IHttpHandler 10 | { 11 | public void ProcessRequest(HttpContext context) { 12 | //context.Response.Write("foo"); 13 | throw new Exception("My custom exception."); 14 | } 15 | 16 | public bool IsReusable { 17 | get { 18 | return true; 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /HttpCompress/SectionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Xml; 3 | using System.Xml.Serialization; 4 | using System.Configuration; 5 | 6 | namespace blowery.Web.HttpCompress 7 | { 8 | /// 9 | /// This class acts as a factory for the configuration settings. 10 | /// 11 | public sealed class SectionHandler : IConfigurationSectionHandler 12 | { 13 | /// 14 | /// Create a new config section handler. This is of type 15 | /// 16 | object IConfigurationSectionHandler.Create(object parent, object configContext, XmlNode configSection) { 17 | Settings settings; 18 | if(parent == null) 19 | settings = Settings.Default; 20 | else 21 | settings = (Settings)parent; 22 | settings.AddSettings(configSection); 23 | return settings; 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Example/DefaultController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web.UI; 3 | using System.Web.UI.WebControls; 4 | 5 | namespace Example { 6 | /// 7 | /// This class acts as a controller for the default.aspx page. 8 | /// It handles Page events and maps events from 9 | /// the classes it contains to event handlers. 10 | /// 11 | public class DefaultController : Page { 12 | 13 | /// 14 | /// A label on the form 15 | /// 16 | protected Label MyLabel; 17 | 18 | /// 19 | /// Override of OnLoad that adds some processing. 20 | /// 21 | /// 22 | protected override void OnLoad(EventArgs e) { 23 | try { 24 | MyLabel.Text = "Right Now: " + DateTime.Now.ToString(); 25 | }finally { 26 | base.OnLoad(e); // be sure to call base to fire the event 27 | } 28 | } 29 | 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Example/Image.ashx: -------------------------------------------------------------------------------- 1 | <%@ WebHandler Language="C#" Class="ImageMaker" %> 2 | 3 | using System.Web; 4 | using System.Drawing; 5 | using System.Drawing.Imaging; 6 | using System.IO; 7 | 8 | public class ImageMaker : IHttpHandler 9 | { 10 | public void ProcessRequest (HttpContext context) 11 | { 12 | using(Bitmap myFoo = new Bitmap(200,200,PixelFormat.Format24bppRgb)) { 13 | using(Graphics g = Graphics.FromImage(myFoo)) { 14 | 15 | g.FillRectangle(Brushes.Gray,0,0,200,200); 16 | g.FillPie(Brushes.Yellow,100,100,100,100,0,90); 17 | 18 | } 19 | context.Response.ContentType = "image/png"; 20 | 21 | // have to do this crap because saving 22 | // png directly to HttpResponse.OutputStream 23 | // is broken in the 1.0 bits (at least up to sp2) 24 | // should just be 25 | // myFoo.Save(context.Response.OutputStream, ImageFormat.Png); 26 | MemoryStream ms = new MemoryStream(); 27 | myFoo.Save(ms, ImageFormat.Png); 28 | context.Response.OutputStream.Write(ms.ToArray(), 0, (int)ms.Length); 29 | } 30 | } 31 | 32 | public bool IsReusable 33 | { 34 | get { return true; } 35 | } 36 | } -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007, Ben Lowery (httpcompress@blowery.org) 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | * Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 12 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 13 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 14 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 15 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 16 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 17 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 18 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 19 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 20 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /HttpCompress/Enums.cs: -------------------------------------------------------------------------------- 1 | namespace blowery.Web.HttpCompress 2 | { 3 | /// 4 | /// The available compression algorithms to use with the HttpCompressionModule 5 | /// 6 | public enum Algorithms { 7 | /// Use the Deflate algorithm 8 | Deflate, 9 | /// Use the GZip algorithm 10 | GZip, 11 | /// Use the default algorithm (picked by client) 12 | Default=-1 13 | } 14 | 15 | /// 16 | /// The level of compression to use with deflate 17 | /// 18 | public enum CompressionLevels { 19 | /// Use the default compression level 20 | Default = -1, 21 | /// The highest level of compression. Also the slowest. 22 | Highest = 9, 23 | /// A higher level of compression. 24 | Higher = 8, 25 | /// A high level of compression. 26 | High = 7, 27 | /// More compression. 28 | More = 6, 29 | /// Normal compression. 30 | Normal = 5, 31 | /// Less than normal compression. 32 | Less = 4, 33 | /// A low level of compression. 34 | Low = 3, 35 | /// A lower level of compression. 36 | Lower = 2, 37 | /// The lowest level of compression that still performs compression. 38 | Lowest = 1, 39 | /// No compression. Use this is you are quite silly. 40 | None = 0 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tests/XmlTestDocuments.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Example/web.config: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /HttpCompress/GZipFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | using System.Text; 5 | using System.Diagnostics; 6 | 7 | using System.IO.Compression; 8 | 9 | namespace blowery.Web.HttpCompress { 10 | /// 11 | /// This is a little filter to support HTTP compression using GZip 12 | /// 13 | public class GZipFilter : CompressingFilter { 14 | 15 | /// 16 | /// compression stream member 17 | /// has to be a member as we can only have one instance of the 18 | /// actual filter class 19 | /// 20 | private GZipStream m_stream = null; 21 | 22 | /// 23 | /// Primary constructor. Need to pass in a stream to wrap up with gzip. 24 | /// 25 | /// The stream to wrap in gzip. Must have CanWrite. 26 | public GZipFilter(Stream baseStream) : base(baseStream, CompressionLevels.Normal) { 27 | m_stream = new GZipStream(baseStream, CompressionMode.Compress); 28 | } 29 | 30 | /// 31 | /// Write content to the stream and have it compressed using gzip. 32 | /// 33 | /// The bytes to write 34 | /// The offset into the buffer to start reading bytes 35 | /// The number of bytes to write 36 | public override void Write(byte[] buffer, int offset, int count) { 37 | if(!HasWrittenHeaders) WriteHeaders(); 38 | m_stream.Write(buffer, offset, count); 39 | } 40 | 41 | /// 42 | /// The Http name of this encoding. Here, gzip. 43 | /// 44 | public override string ContentEncoding { 45 | get { return "gzip"; } 46 | } 47 | 48 | /// 49 | /// Closes this Filter and calls the base class implementation. 50 | /// 51 | public override void Close() { 52 | m_stream.Close(); // this will close the gzip stream along with the underlying stream 53 | // no need for call to base.Close() here. 54 | } 55 | 56 | /// 57 | /// Flushes the stream out to underlying storage 58 | /// 59 | public override void Flush() { 60 | m_stream.Flush(); 61 | } 62 | 63 | 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /HttpCompress/DeflateFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | using System.IO.Compression; 5 | 6 | namespace blowery.Web.HttpCompress { 7 | /// 8 | /// Summary description for DeflateFilter. 9 | /// 10 | public class DeflateFilter : CompressingFilter { 11 | 12 | /// 13 | /// compression stream member 14 | /// has to be a member as we can only have one instance of the 15 | /// actual filter class 16 | /// 17 | private DeflateStream m_stream = null; 18 | 19 | /// 20 | /// Basic constructor that uses the Normal compression level 21 | /// 22 | /// The stream to wrap up with the deflate algorithm 23 | public DeflateFilter(Stream baseStream) : this(baseStream, CompressionLevels.Normal) { } 24 | 25 | /// 26 | /// Full constructor that allows you to set the wrapped stream and the level of compression 27 | /// 28 | /// The stream to wrap up with the deflate algorithm 29 | /// The level of compression to use 30 | public DeflateFilter(Stream baseStream, CompressionLevels compressionLevel) : base(baseStream, compressionLevel) { 31 | m_stream = new DeflateStream(baseStream, CompressionMode.Compress); 32 | } 33 | 34 | /// 35 | /// Write out bytes to the underlying stream after compressing them using deflate 36 | /// 37 | /// The array of bytes to write 38 | /// The offset into the supplied buffer to start 39 | /// The number of bytes to write 40 | public override void Write(byte[] buffer, int offset, int count) { 41 | if(!HasWrittenHeaders) WriteHeaders(); 42 | m_stream.Write(buffer, offset, count); 43 | } 44 | 45 | /// 46 | /// Return the Http name for this encoding. Here, deflate. 47 | /// 48 | public override string ContentEncoding { 49 | get { return "deflate"; } 50 | } 51 | 52 | /// 53 | /// Closes this Filter and calls the base class implementation. 54 | /// 55 | public override void Close() { 56 | m_stream.Close(); 57 | } 58 | 59 | /// 60 | /// Flushes that the filter out to underlying storage 61 | /// 62 | public override void Flush() { 63 | m_stream.Flush(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Fetch/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | // 9 | [assembly: AssemblyTitle("")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("")] 14 | [assembly: AssemblyCopyright("")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | 18 | // 19 | // Version information for an assembly consists of the following four values: 20 | // 21 | // Major Version 22 | // Minor Version 23 | // Build Number 24 | // Revision 25 | // 26 | // You can specify all the values or you can default the Revision and Build Numbers 27 | // by using the '*' as shown below: 28 | 29 | [assembly: AssemblyVersion("6.0")] 30 | 31 | // 32 | // In order to sign your assembly you must specify a key to use. Refer to the 33 | // Microsoft .NET Framework documentation for more information on assembly signing. 34 | // 35 | // Use the attributes below to control which key is used for signing. 36 | // 37 | // Notes: 38 | // (*) If no key is specified, the assembly is not signed. 39 | // (*) KeyName refers to a key that has been installed in the Crypto Service 40 | // Provider (CSP) on your machine. KeyFile refers to a file which contains 41 | // a key. 42 | // (*) If the KeyFile and the KeyName values are both specified, the 43 | // following processing occurs: 44 | // (1) If the KeyName can be found in the CSP, that key is used. 45 | // (2) If the KeyName does not exist and the KeyFile does exist, the key 46 | // in the KeyFile is installed into the CSP and used. 47 | // (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. 48 | // When specifying the KeyFile, the location of the KeyFile should be 49 | // relative to the project output directory which is 50 | // %Project Directory%\obj\. For example, if your KeyFile is 51 | // located in the project directory, you would specify the AssemblyKeyFile 52 | // attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] 53 | // (*) Delay Signing is an advanced option - see the Microsoft .NET Framework 54 | // documentation for more information on this. 55 | // 56 | [assembly: AssemblyDelaySign(false)] 57 | [assembly: AssemblyKeyFile("")] 58 | [assembly: AssemblyKeyName("")] 59 | -------------------------------------------------------------------------------- /HttpCompress/CompressingFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Web; 4 | 5 | namespace blowery.Web.HttpCompress { 6 | /// 7 | /// Base for any HttpFilter that performing compression 8 | /// 9 | /// 10 | /// When implementing this class, you need to implement a 11 | /// along with a . The latter corresponds to a 12 | /// content coding (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5) 13 | /// that your implementation will support. 14 | /// 15 | public abstract class CompressingFilter : HttpOutputFilter { 16 | 17 | private bool hasWrittenHeaders = false; 18 | 19 | /// 20 | /// Protected constructor that sets up the underlying stream we're compressing into 21 | /// 22 | /// The stream we're wrapping up 23 | /// The level of compression to use when compressing the content 24 | protected CompressingFilter(Stream baseStream, CompressionLevels compressionLevel) : base(baseStream) { 25 | _compressionLevel = compressionLevel; 26 | } 27 | 28 | /// 29 | /// The name of the content-encoding that's being implemented 30 | /// 31 | /// 32 | /// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5 for more 33 | /// details on content codings. 34 | /// 35 | public abstract string ContentEncoding { get; } 36 | 37 | private CompressionLevels _compressionLevel; 38 | 39 | /// 40 | /// Allow inheriting classes to get access the the level of compression that should be used 41 | /// 42 | protected CompressionLevels CompressionLevel { 43 | get { return _compressionLevel; } 44 | } 45 | 46 | /// 47 | /// Keeps track of whether or not we're written the compression headers 48 | /// 49 | protected bool HasWrittenHeaders { 50 | get { return hasWrittenHeaders; } 51 | } 52 | 53 | /// 54 | /// Writes out the compression-related headers. Subclasses should call this once before writing to the output stream. 55 | /// 56 | protected void WriteHeaders() { 57 | // this is dangerous. if Response.End is called before the filter is used, directly or indirectly, 58 | // the content will not pass through the filter. However, this header will still be appended. 59 | // Look for handling cases in PreRequestSendHeaders and Pre 60 | HttpContext.Current.Response.AppendHeader("Content-Encoding", this.ContentEncoding); 61 | HttpContext.Current.Response.AppendHeader("X-Compressed-By", "HttpCompress"); 62 | hasWrittenHeaders = true; 63 | } 64 | 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /HttpCompress.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 10.00 2 | # Visual C# Express 2008 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{621AAC3C-38EC-4F70-80D3-68DE4829AA33}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fetch", "Fetch\Fetch.csproj", "{3EFC307A-7AA7-4566-8517-BD80263C2830}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{758C0F47-99BF-43B2-8C91-C94CAEC72DAC}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpCompress", "HttpCompress\HttpCompress.csproj", "{24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}" 10 | EndProject 11 | Global 12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 13 | Debug|Any CPU = Debug|Any CPU 14 | Production|Any CPU = Production|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Production|Any CPU.ActiveCfg = Production|Any CPU 21 | {621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Production|Any CPU.Build.0 = Production|Any CPU 22 | {621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {3EFC307A-7AA7-4566-8517-BD80263C2830}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {3EFC307A-7AA7-4566-8517-BD80263C2830}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {3EFC307A-7AA7-4566-8517-BD80263C2830}.Production|Any CPU.ActiveCfg = Production|Any CPU 27 | {3EFC307A-7AA7-4566-8517-BD80263C2830}.Production|Any CPU.Build.0 = Production|Any CPU 28 | {3EFC307A-7AA7-4566-8517-BD80263C2830}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {3EFC307A-7AA7-4566-8517-BD80263C2830}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Production|Any CPU.ActiveCfg = Production|Any CPU 33 | {758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Production|Any CPU.Build.0 = Production|Any CPU 34 | {758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Production|Any CPU.ActiveCfg = Production|Any CPU 39 | {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Production|Any CPU.Build.0 = Production|Any CPU 40 | {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /HttpCompress/HttpOutputFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace blowery.Web.HttpCompress { 5 | /// 6 | /// The base of anything you want to latch onto the Filter property of a 7 | /// object. 8 | /// 9 | /// 10 | ///

These are generally used with but you could really use them in 11 | /// other HttpModules. This is a general, write-only stream that writes to some underlying stream. When implementing 12 | /// a real class, you have to override void Write(byte[], int offset, int count). Your work will be performed there. 13 | ///
14 | public abstract class HttpOutputFilter : Stream { 15 | 16 | private Stream _sink; 17 | 18 | /// 19 | /// Subclasses need to call this on contruction to setup the underlying stream 20 | /// 21 | /// The stream we're wrapping up in a filter 22 | protected HttpOutputFilter(Stream baseStream) { 23 | _sink = baseStream; 24 | } 25 | 26 | /// 27 | /// Allow subclasses access to the underlying stream 28 | /// 29 | protected Stream BaseStream { 30 | get{ return _sink; } 31 | } 32 | 33 | /// 34 | /// False. These are write-only streams 35 | /// 36 | public override bool CanRead { 37 | get { return false; } 38 | } 39 | 40 | /// 41 | /// False. These are write-only streams 42 | /// 43 | public override bool CanSeek { 44 | get { return false; } 45 | } 46 | 47 | /// 48 | /// True. You can write to the stream. May change if you call Close or Dispose 49 | /// 50 | public override bool CanWrite { 51 | get { return _sink.CanWrite; } 52 | } 53 | 54 | /// 55 | /// Not supported. Throws an exception saying so. 56 | /// 57 | /// Thrown. Always. 58 | public override long Length { 59 | get { throw new NotSupportedException(); } 60 | } 61 | 62 | /// 63 | /// Not supported. Throws an exception saying so. 64 | /// 65 | /// Thrown. Always. 66 | public override long Position { 67 | get { throw new NotSupportedException(); } 68 | set { throw new NotSupportedException(); } 69 | } 70 | 71 | /// 72 | /// Not supported. Throws an exception saying so. 73 | /// 74 | /// Thrown. Always. 75 | public override long Seek(long offset, System.IO.SeekOrigin direction) { 76 | throw new NotSupportedException(); 77 | } 78 | 79 | /// 80 | /// Not supported. Throws an exception saying so. 81 | /// 82 | /// Thrown. Always. 83 | public override void SetLength(long length) { 84 | throw new NotSupportedException(); 85 | } 86 | 87 | /// 88 | /// Closes this Filter and the underlying stream. 89 | /// 90 | /// 91 | /// If you override, call up to this method in your implementation. 92 | /// 93 | public override void Close() { 94 | _sink.Close(); 95 | } 96 | 97 | /// 98 | /// Fluses this Filter and the underlying stream. 99 | /// 100 | /// 101 | /// If you override, call up to this method in your implementation. 102 | /// 103 | public override void Flush() { 104 | _sink.Flush(); 105 | } 106 | 107 | /// 108 | /// Not supported. 109 | /// 110 | /// The buffer to write into. 111 | /// The offset on the buffer to write into 112 | /// The number of bytes to write. Must be less than buffer.Length 113 | /// An int telling you how many bytes were written 114 | public override int Read(byte[] buffer, int offset, int count) { 115 | throw new NotSupportedException(); 116 | } 117 | 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | == Version History == 2 | 6.0, V2 ----- 3 | Changes: 4 | - Compiled against v2 of the .NET framework, using the framework supplied GZip 5 | and Deflate streams. This rev requires .NET2 and does not use #ziplib. 6 | 7 | 6.0 --------- 8 | Changes: 9 | - I now delay the insertion of the compression header until the first 10 | write is attempted on the compressing stream. This allows exceptions 11 | to be written properly, though they will be written uncompressed. 12 | - The same fix should allow Server.Transfer and friends to work without 13 | modification, though the contents of pages written using Server.Transfer 14 | will not be compressed. 15 | - Thanks to Milan Negovan for helping me track down this issue and thanks 16 | to the shower for helping me come up with a general solution. 17 | 18 | 5.0.0.0 ----- 19 | Changes: 20 | - All these came from outside sources. No real work from me. 21 | - Added support for the VaryByHeader, thanks to Simon Fell and Ian Anderson 22 | - Reworked the filter picker (all thanks to Ian Anderson) 23 | - No filter is added if the compression level is set to None 24 | - Fixed a bug where the INSTALLED_TAG was not being set into the 25 | Context properly if compression was determined unnecessary. 26 | 27 | 4.0.0.0 ----- 28 | Changes: 29 | - upped to version 4. From now on, every release will be a major 30 | version release. 31 | - now have support for path-based and ContentType-based exlusions 32 | - now using a slightly custom edition of #zlib 0.5 to fix a couple bugs 33 | - better method for sinking asp.net pipeline events 34 | - modified all namespaces to blowery.Web.HttpCompress 35 | - added custom response header (X-Compressed-By) 36 | - the assembly is now blowery.Web.HttpCompress 37 | - the config section handler is now blowery.Web.HttpCompress.SectionHandler 38 | - the config section is now httpCompress, in keeping with the new naming 39 | - you will have to update your configuration files for this release! 40 | - fixed a bug where i was improperly detecting the supported compression 41 | types sent by the browser. I would only read in the first one. 42 | 43 | 44 | 45 | 1.1 --------- 46 | 47 | MAJOR CHANGES THAT WILL AFFECT YOU 48 | - the assembly is now named HttpCompressionModule.dll 49 | - the config section handler is now HttpCompressionModuleSectionHandler 50 | - YOU WILL NEED TO UPDATE YOUR CONFIG FILES 51 | FOR THIS VERSION TO WORK (see samples for direction) 52 | 53 | Other stuff 54 | - moved to SharpZipLib (formerly NZipLib) 0.31 which 55 | contains a bunch of bug fixes. This means I just 56 | inherited a bunch of bug fixes. yay! 57 | - updated the code to use the new ICSharpCode namespace 58 | - reworked the way the configuration works 59 | - no more generic http modules. i'm only writing this 60 | one, so this made the code simpler 61 | - removed the Unspecified flag on the Enums. Not needed. 62 | now, it defaults to Deflate + Normal 63 | - decided to not support config parenting, as it doesn't 64 | really make sense for this 65 | - pulled out some trace stuff from the DEBUG version that 66 | didn't need to be there 67 | - actually shipping compiled versions, both DEBUG and RELEASE 68 | - added examples. 69 | 70 | 71 | 1.0 --------- 72 | - initial introduction 73 | 74 | === Introduction == 75 | Hey there, 76 | 77 | Thanks for downloading my compressing filter for ASP.NET! As 78 | you can see, the full source is provided so you can 79 | understand how it works and modify it if you want. 80 | 81 | If you don't have visual studio, no fear. The whole project 82 | lives in one directory, so csc *.cs should work, you just need 83 | to add a reference to the supplied SharpZipLib.dll. 84 | 85 | For instructions on how to slip the HttpCompressionModule in, 86 | see the provided example. It shows what entries have 87 | to be added to the web.config to set things up. 88 | 89 | So, to get things going, here's what you have to do: 90 | 1) compile the project into a library (or just use the 91 | version in /lib) 92 | 2) move the .dll that comes from compilation to the /bin directory of 93 | your asp.net web app 94 | 3) add the entries to the web.config of your asp.net app 95 | 96 | That's it. That should get you going. 97 | 98 | 99 | 100 | --b -------------------------------------------------------------------------------- /HttpCompress/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // A Better AssemblyInfo.cs based on better practices. 2 | // 3 | // This little guy sets up all the assembly level attributes 4 | // on the assembly that you're compiling 5 | // 6 | // This version heavily influenced (read copied) from Jeffery Richter's 7 | // Applied Microsoft.NET Framework Programming (pg. 93) 8 | 9 | using System.Reflection; 10 | 11 | #region Company Info 12 | 13 | // Set the CompanyName, LegalCopyright, and LegalTrademark fields 14 | // You shouldn't have to mess with these 15 | [assembly: AssemblyCompany("blowery.org")] 16 | [assembly: AssemblyCopyright("Copyright (c) 2003-Present Ben Lowery")] 17 | 18 | #endregion 19 | 20 | #region Product Info 21 | 22 | // Set the ProductName and ProductVersion fields 23 | // AssemblyProduct should be a descriptive name for the product that includes this assembly 24 | // If this is a generic library that's going to be used by a whole bunch of stuff, 25 | // just make the name the same as the Assembly's name. This isn't really used by 26 | // anything except Windows Explorer's properties tab, so it's not a big deal if you mess it up 27 | // See pg. 60 of Richter for more info. 28 | [assembly: AssemblyProduct("HttpCompress")] 29 | #endregion 30 | 31 | #region Version and Identity Info 32 | 33 | // AssemblyInformationalVersion should be the version of the product that is including this 34 | // assembly. Again, if this assembly isn't being included in a "product", just make this 35 | // the same as the FileVersion and be done with it. See pg 60 of Richter for more info. 36 | [assembly: AssemblyInformationalVersion("7.0")] 37 | 38 | // Set the FileVersion, AssemblyVersion, FileDescription, and Comments fields 39 | // AssemblyFileVersion is stored in the Win32 version resource. It's ignored by the CLR, 40 | // we typically use it to store the build and revision bits every time a build 41 | // is performed. Unfortunately, the c# compiler doesn't do this automatically, 42 | // so we'll have to work out another way to do it. 43 | [assembly: AssemblyFileVersion("7.0")] 44 | 45 | // AssemblyVersion is the version used by the CLR as part of the strong name for 46 | // an assembly. You don't really want to mess with this unless you're 47 | // making changes that add / remove / change functionality within the library. 48 | // For bugfixes, don't mess with this. 49 | // 50 | // Do this: 51 | // [assembly: AssemblyVersion("1.0.0.0")] 52 | // 53 | // Don't do this: 54 | // [assembly: AssemblyVersion("1.0.*.*")] 55 | // as it breaks all the other assemblies that reference this one every time 56 | // you build the project. 57 | [assembly: AssemblyVersion("7.0")] 58 | 59 | // Title is just for inspection utilities and isn't really used for much 60 | // Generally just set this to the name of the file containing the 61 | // manifest, sans extension. I.E. for BensLibrary.dll, name it BensLibrary 62 | [assembly: AssemblyTitle("HttpCompress")] 63 | 64 | // Description is just for inspection utilities and isn't really that important. 65 | // It's generally a good idea to write a decent description so that you 66 | // don't end up looking like a tool when your stuff shows up in an inspector. 67 | [assembly: AssemblyDescription("An HttpModule that compresses the output stream!")] 68 | 69 | #endregion 70 | 71 | #region Culture Info 72 | 73 | // Set the assembly's culture affinity. Generally don't want to set this 74 | // unless you're building an resource only assembly. Assemblies that contain 75 | // code should always be culture neutral 76 | [assembly: AssemblyCulture("")] 77 | 78 | #endregion 79 | 80 | #region Assembly Signing Info 81 | 82 | #if !StronglyNamedAssembly 83 | 84 | // Weakly named assemblies are never signed 85 | [assembly: AssemblyDelaySign(false)] 86 | 87 | #else 88 | 89 | // Strongly named assemblies are usually delay signed while building and 90 | // completely signed using sn.exe's -R or -Rc switch 91 | 92 | #if !SignedUsingACryptoServiceProvider 93 | 94 | // Give the name of the file that contains the public/private key pair. 95 | // If delay signing, only the public key is used 96 | [assembly: AssemblyKeyFile(THE_KEY_FILE_RELATIVE_TO_THIS_FILE)] 97 | 98 | // Note: If AssemblyKeyFile and AssemblyKeyName are both specified, 99 | // here's what happens... 100 | // 1) If the container exists, the key file is ignored 101 | // 2) If the container doesn't exist, the keys from the key 102 | // file are copied in the container and the assembly is signed 103 | 104 | #else 105 | 106 | // Give the name of the cryptographic service provider (CSP) container 107 | // that contains the public/private key pair 108 | // If delay signing, only the public key is used 109 | [assembly: AssemblyKeyName("blowery.org")] 110 | 111 | #endif 112 | 113 | #endif 114 | 115 | #endregion 116 | -------------------------------------------------------------------------------- /Tests/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // A Better AssemblyInfo.cs based on better practices. 2 | // 3 | // This little guy sets up all the assembly level attributes 4 | // on the assembly that you're compiling 5 | // 6 | // This version heavily influenced (read copied) from Jeffery Richter's 7 | // Applied Microsoft.NET Framework Programming (pg. 93) 8 | 9 | using System.Reflection; 10 | 11 | #region Company Info 12 | 13 | // Set the CompanyName, LegalCopyright, and LegalTrademark fields 14 | // You shouldn't have to mess with these 15 | [assembly: AssemblyCompany("blowery.org")] 16 | [assembly: AssemblyCopyright("Copyright (c) 2003 Ben Lowery")] 17 | 18 | #endregion 19 | 20 | #region Product Info 21 | 22 | // Set the ProductName and ProductVersion fields 23 | // AssemblyProduct should be a descriptive name for the product that includes this assembly 24 | // If this is a generic library that's going to be used by a whole bunch of stuff, 25 | // just make the name the same as the Assembly's name. This isn't really used by 26 | // anything except Windows Explorer's properties tab, so it's not a big deal if you mess it up 27 | // See pg. 60 of Richter for more info. 28 | [assembly: AssemblyProduct("HttpCompressionModule Tests")] 29 | #endregion 30 | 31 | #region Version and Identity Info 32 | 33 | // AssemblyInformationalVersion should be the version of the product that is including this 34 | // assembly. Again, if this assembly isn't being included in a "product", just make this 35 | // the same as the FileVersion and be done with it. See pg 60 of Richter for more info. 36 | [assembly: AssemblyInformationalVersion("7.0")] 37 | 38 | // Set the FileVersion, AssemblyVersion, FileDescription, and Comments fields 39 | // AssemblyFileVersion is stored in the Win32 version resource. It's ignored by the CLR, 40 | // we typically use it to store the build and revision bits every time a build 41 | // is performed. Unfortunately, the c# compiler doesn't do this automatically, 42 | // so we'll have to work out another way to do it. 43 | [assembly: AssemblyFileVersion("7.0")] 44 | 45 | // AssemblyVersion is the version used by the CLR as part of the strong name for 46 | // an assembly. You don't really want to mess with this unless you're 47 | // making changes that add / remove / change functionality within the library. 48 | // For bugfixes, don't mess with this. 49 | // 50 | // Do this: 51 | // [assembly: AssemblyVersion("1.0.0.0")] 52 | // 53 | // Don't do this: 54 | // [assembly: AssemblyVersion("1.0.*.*")] 55 | // as it breaks all the other assemblies that reference this one every time 56 | // you build the project. 57 | [assembly: AssemblyVersion("7.0")] 58 | 59 | // Title is just for inspection utilities and isn't really used for much 60 | // Generally just set this to the name of the file containing the 61 | // manifest, sans extension. I.E. for BensLibrary.dll, name it BensLibrary 62 | [assembly: AssemblyTitle("Tests")] 63 | 64 | // Description is just for inspection utilities and isn't really that important. 65 | // It's generally a good idea to write a decent description so that you 66 | // don't end up looking like a tool when your stuff shows up in an inspector. 67 | [assembly: AssemblyDescription("Tests for the HttpCompress")] 68 | 69 | #endregion 70 | 71 | #region Culture Info 72 | 73 | // Set the assembly's culture affinity. Generally don't want to set this 74 | // unless you're building an resource only assembly. Assemblies that contain 75 | // code should always be culture neutral 76 | [assembly: AssemblyCulture("")] 77 | 78 | #endregion 79 | 80 | #region Assembly Signing Info 81 | 82 | #if !StronglyNamedAssembly 83 | 84 | // Weakly named assemblies are never signed 85 | [assembly: AssemblyDelaySign(false)] 86 | 87 | #else 88 | 89 | // Strongly named assemblies are usually delay signed while building and 90 | // completely signed using sn.exe's -R or -Rc switch 91 | 92 | #if !SignedUsingACryptoServiceProvider 93 | 94 | // Give the name of the file that contains the public/private key pair. 95 | // If delay signing, only the public key is used 96 | [assembly: AssemblyKeyFile(THE_KEY_FILE_RELATIVE_TO_THIS_FILE)] 97 | 98 | // Note: If AssemblyKeyFile and AssemblyKeyName are both specified, 99 | // here's what happens... 100 | // 1) If the container exists, the key file is ignored 101 | // 2) If the container doesn't exist, the keys from the key 102 | // file are copied in the container and the assembly is signed 103 | 104 | #else 105 | 106 | // Give the name of the cryptographic service provider (CSP) container 107 | // that contains the public/private key pair 108 | // If delay signing, only the public key is used 109 | [assembly: AssemblyKeyName("blowery.org")] 110 | 111 | #endif 112 | 113 | #endif 114 | 115 | #endregion 116 | 117 | #region AssemblyWideConstants 118 | class Constants { 119 | public const string Namespace = "blowery.Web.HttpCompress.Tests"; 120 | } 121 | #endregion 122 | -------------------------------------------------------------------------------- /Tests/XmlTestDocuments.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | text/microsoft-resx 32 | 33 | 34 | 1.3.0.0 35 | 36 | 37 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 38 | 39 | 40 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 41 | 42 | 43 | <httpCompressionModule/> 44 | 45 | 46 | <httpCompressionModule preferredAlgorithm="deflate" compressionLevel="high"/> 47 | 48 | 49 | <httpCompressionModule preferredAlgorithm="deflate"/> 50 | 51 | 52 | <httpCompressionModule preferredAlgorithm="gzip"/> 53 | 54 | 55 | <httpCompressionModule compressionLevel="high"/> 56 | 57 | 58 | <httpCompressionModule compressionLevel="normal"/> 59 | 60 | 61 | <httpCompressionModule compressionLevel="low"/> 62 | 63 | 64 | <httpCompressionModule compressionLevel="xxx"/> 65 | 66 | 67 | <httpCompressionModule preferredAlgorithm="xxx"/> 68 | 69 | 70 | <httpCompressionModule> 71 | <excludedMimeTypes> 72 | <add type="ben/foo"/> 73 | </excludedMimeTypes> 74 | </httpCompressionModule> 75 | 76 | 77 | <httpCompressionModule> 78 | <excludedMimeTypes> 79 | <delete type="image/jpeg"/> 80 | </excludedMimeTypes> 81 | </httpCompressionModule> 82 | 83 | 84 | <httpCompressionModule> 85 | <excludedMimeTypes> 86 | <add type="ben/foo"/> 87 | <add type="image/jpeg"/> 88 | </excludedMimeTypes> 89 | </httpCompressionModule> 90 | 91 | 92 | <httpCompressionModule> 93 | <excludedMimeTypes> 94 | <add type="user/silly"/> 95 | <delete type="user/silly"/> 96 | </excludedMimeTypes> 97 | </httpCompressionModule> 98 | 99 | 100 | <httpCompressionModule> 101 | <excludedPaths> 102 | <add path="foo.aspx"/> 103 | <add path="foo/bar.aspx"/> 104 | </excludedPaths> 105 | </httpCompressionModule> 106 | 107 | 108 | <httpCompressionModule> 109 | <excludedPaths> 110 | <delete path="foo.aspx"/> 111 | </excludedPaths> 112 | </httpCompressionModule> 113 | 114 | -------------------------------------------------------------------------------- /HttpCompress/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Specialized; 4 | using System.Xml; 5 | 6 | namespace blowery.Web.HttpCompress { 7 | /// 8 | /// This class encapsulates the settings for an HttpCompressionModule 9 | /// 10 | public sealed class Settings { 11 | 12 | private Algorithms _preferredAlgorithm; 13 | private CompressionLevels _compressionLevel; 14 | private StringCollection _excludedTypes; 15 | private StringCollection _excludedPaths; 16 | 17 | /// 18 | /// Create an HttpCompressionModuleSettings from an XmlNode 19 | /// 20 | /// The XmlNode to configure from 21 | public Settings(XmlNode node) : this() { 22 | AddSettings(node); 23 | } 24 | 25 | 26 | private Settings() { 27 | _preferredAlgorithm = Algorithms.Default; 28 | _compressionLevel = CompressionLevels.Default; 29 | _excludedTypes = new StringCollection(); 30 | _excludedPaths = new StringCollection(); 31 | _excludedPaths.Add(".axd"); 32 | } 33 | 34 | /// 35 | /// Suck in some more changes from an XmlNode. Handy for config file parenting. 36 | /// 37 | /// The node to read from 38 | public void AddSettings(XmlNode node) { 39 | if(node == null) 40 | return; 41 | 42 | XmlAttribute preferredAlgorithm = node.Attributes["preferredAlgorithm"]; 43 | if(preferredAlgorithm != null) { 44 | try { 45 | _preferredAlgorithm = (Algorithms)Enum.Parse(typeof(Algorithms), preferredAlgorithm.Value, true); 46 | }catch(ArgumentException) { } 47 | } 48 | 49 | XmlAttribute compressionLevel = node.Attributes["compressionLevel"]; 50 | if(compressionLevel != null) { 51 | try { 52 | _compressionLevel = (CompressionLevels)Enum.Parse(typeof(CompressionLevels), compressionLevel.Value, true); 53 | }catch(ArgumentException) { } 54 | } 55 | 56 | ParseExcludedTypes(node.SelectSingleNode("excludedMimeTypes")); 57 | ParseExcludedPaths(node.SelectSingleNode("excludedPaths")); 58 | 59 | } 60 | 61 | 62 | /// 63 | /// Get the current settings from the xml config file 64 | /// 65 | public static Settings GetSettings() { 66 | Settings settings = (Settings)System.Configuration.ConfigurationSettings.GetConfig("blowery.web/httpCompress"); 67 | if(settings == null) 68 | return Settings.Default; 69 | else 70 | return settings; 71 | } 72 | 73 | /// 74 | /// The default settings. Deflate + normal. 75 | /// 76 | public static Settings Default { 77 | get { return new Settings(); } 78 | } 79 | 80 | /// 81 | /// The preferred algorithm to use for compression 82 | /// 83 | public Algorithms PreferredAlgorithm { 84 | get { return _preferredAlgorithm; } 85 | } 86 | 87 | 88 | /// 89 | /// The preferred compression level 90 | /// 91 | public CompressionLevels CompressionLevel { 92 | get { return _compressionLevel; } 93 | } 94 | 95 | 96 | /// 97 | /// Checks a given mime type to determine if it has been excluded from compression 98 | /// 99 | /// The MimeType to check. Can include wildcards like image/* or */xml. 100 | /// true if the mime type passed in is excluded from compression, false otherwise 101 | public bool IsExcludedMimeType(string mimetype) { 102 | if(mimetype == null) return true; 103 | return _excludedTypes.Contains(mimetype.ToLower()); 104 | } 105 | 106 | /// 107 | /// Looks for a given path in the list of paths excluded from compression 108 | /// 109 | /// the relative url to check 110 | /// true if excluded, false if not 111 | public bool IsExcludedPath(string relUrl) { 112 | return _excludedPaths.Contains(relUrl.ToLower()); 113 | } 114 | 115 | private void ParseExcludedTypes(XmlNode node) { 116 | if(node == null) return; 117 | 118 | for(int i = 0; i < node.ChildNodes.Count; ++i) { 119 | switch(node.ChildNodes[i].LocalName) { 120 | case "add": 121 | if(node.ChildNodes[i].Attributes["type"] != null) 122 | _excludedTypes.Add(node.ChildNodes[i].Attributes["type"].Value.ToLower()); 123 | break; 124 | case "delete": 125 | if(node.ChildNodes[i].Attributes["type"] != null) 126 | _excludedTypes.Remove(node.ChildNodes[i].Attributes["type"].Value.ToLower()); 127 | break; 128 | } 129 | } 130 | } 131 | 132 | private void ParseExcludedPaths(XmlNode node) { 133 | if(node == null) return; 134 | 135 | for(int i = 0; i < node.ChildNodes.Count; ++i) { 136 | switch(node.ChildNodes[i].LocalName) { 137 | case "add": 138 | if(node.ChildNodes[i].Attributes["path"] != null) 139 | _excludedPaths.Add(node.ChildNodes[i].Attributes["path"].Value.ToLower()); 140 | break; 141 | case "delete": 142 | if(node.ChildNodes[i].Attributes["path"] != null) 143 | _excludedPaths.Remove(node.ChildNodes[i].Attributes["path"].Value.ToLower()); 144 | break; 145 | } 146 | } 147 | } 148 | 149 | } 150 | 151 | } 152 | 153 | -------------------------------------------------------------------------------- /Fetch/Fetch.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Local 4 | 8.0.50727 5 | 2.0 6 | {3EFC307A-7AA7-4566-8517-BD80263C2830} 7 | Debug 8 | AnyCPU 9 | App.ico 10 | 11 | 12 | Fetch 13 | 14 | 15 | JScript 16 | Grid 17 | IE50 18 | false 19 | WinExe 20 | Fetch 21 | OnBuildSuccess 22 | Fetch.EntryPoint 23 | 24 | 25 | 26 | 27 | 2.0 28 | v3.5 29 | 30 | 31 | bin\Debug\ 32 | false 33 | 285212672 34 | false 35 | 36 | 37 | DEBUG;TRACE 38 | 39 | 40 | true 41 | 4096 42 | false 43 | 44 | 45 | false 46 | false 47 | false 48 | false 49 | 4 50 | full 51 | prompt 52 | 53 | 54 | bin\Release\ 55 | false 56 | 285212672 57 | false 58 | 59 | 60 | TRACE 61 | 62 | 63 | false 64 | 4096 65 | false 66 | 67 | 68 | true 69 | false 70 | false 71 | false 72 | 4 73 | none 74 | prompt 75 | 76 | 77 | bin\Release\ 78 | false 79 | 285212672 80 | false 81 | 82 | 83 | TRACE 84 | 85 | 86 | false 87 | 4096 88 | false 89 | 90 | 91 | true 92 | false 93 | false 94 | false 95 | 4 96 | none 97 | prompt 98 | 99 | 100 | 101 | System 102 | 103 | 104 | System.Data 105 | 106 | 107 | System.Drawing 108 | 109 | 110 | System.Windows.Forms 111 | 112 | 113 | System.XML 114 | 115 | 116 | 117 | 118 | 119 | Code 120 | 121 | 122 | Code 123 | 124 | 125 | Form 126 | 127 | 128 | MainForm.cs 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /Example/Example.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Local 4 | 8.0.50727 5 | 2.0 6 | {621AAC3C-38EC-4F70-80D3-68DE4829AA33} 7 | Debug 8 | AnyCPU 9 | 10 | 11 | 12 | 13 | Example 14 | 15 | 16 | JScript 17 | Grid 18 | IE50 19 | false 20 | Library 21 | Example 22 | OnBuildSuccess 23 | 24 | 25 | 26 | 27 | 28 | 29 | 2.0 30 | v3.5 31 | 32 | 33 | bin\ 34 | false 35 | 285212672 36 | false 37 | 38 | 39 | 40 | 41 | 42 | 43 | true 44 | 4096 45 | false 46 | 47 | 48 | false 49 | false 50 | false 51 | false 52 | 1 53 | full 54 | prompt 55 | 56 | 57 | bin\ 58 | false 59 | 285212672 60 | false 61 | 62 | 63 | 64 | 65 | 66 | 67 | false 68 | 4096 69 | false 70 | 71 | 72 | false 73 | false 74 | false 75 | false 76 | 1 77 | none 78 | prompt 79 | 80 | 81 | bin\ 82 | false 83 | 285212672 84 | false 85 | 86 | 87 | 88 | 89 | 90 | 91 | false 92 | 4096 93 | false 94 | 95 | 96 | false 97 | false 98 | false 99 | false 100 | 1 101 | none 102 | prompt 103 | 104 | 105 | 106 | ICSharpCode.SharpZipLib 107 | ..\Vendor\ICSharpCode.SharpZipLib\ICSharpCode.SharpZipLib.dll 108 | 109 | 110 | System 111 | 112 | 113 | System.Data 114 | 115 | 116 | System.Drawing 117 | 118 | 119 | System.Web 120 | 121 | 122 | System.Windows.Forms 123 | 124 | 125 | System.XML 126 | 127 | 128 | HttpCompress 129 | {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5} 130 | {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 131 | 132 | 133 | 134 | 135 | 136 | Form 137 | 138 | 139 | ASPXCodeBehind 140 | 141 | 142 | Code 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /Tests/SettingsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Resources; 4 | using NUnit.Framework; 5 | 6 | using blowery.Web.HttpCompress; 7 | 8 | namespace blowery.Web.HttpCompress.Tests { 9 | /// 10 | /// Tests for the class 11 | /// 12 | [TestFixture] 13 | public class SettingsTests { 14 | 15 | ResourceManager rman; 16 | 17 | [SetUp] public void Setup() { 18 | rman = Utility.GetResourceManager("XmlTestDocuments"); 19 | } 20 | 21 | [Test] public void DefaultTest() { 22 | Assertion.AssertEquals(CompressionLevels.Default, Settings.Default.CompressionLevel); 23 | Assertion.AssertEquals(Algorithms.Default, Settings.Default.PreferredAlgorithm); 24 | } 25 | 26 | [Test] public void EmptyNodeTest() { 27 | Settings setting = new Settings(MakeNode(rman.GetString("EmptyNode"))); 28 | Assertion.AssertEquals(CompressionLevels.Default, setting.CompressionLevel); 29 | Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm); 30 | } 31 | 32 | [Test] public void DeflateTest() { 33 | Settings setting = new Settings(MakeNode(rman.GetString("Deflate"))); 34 | Assertion.AssertEquals(Algorithms.Deflate, setting.PreferredAlgorithm); 35 | Assertion.AssertEquals(CompressionLevels.Default, setting.CompressionLevel); 36 | } 37 | 38 | [Test] public void GZipTest() { 39 | Settings setting = new Settings(MakeNode(rman.GetString("GZip"))); 40 | Assertion.AssertEquals(Algorithms.GZip, setting.PreferredAlgorithm); 41 | Assertion.AssertEquals(CompressionLevels.Default, setting.CompressionLevel); 42 | } 43 | 44 | [Test] public void HighLevelTest() { 45 | Settings setting = new Settings(MakeNode(rman.GetString("LevelHigh"))); 46 | Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm); 47 | Assertion.AssertEquals(CompressionLevels.High, setting.CompressionLevel); 48 | } 49 | 50 | [Test] public void NormalLevelTest() { 51 | Settings setting = new Settings(MakeNode(rman.GetString("LevelNormal"))); 52 | Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm); 53 | Assertion.AssertEquals(CompressionLevels.Normal, setting.CompressionLevel); 54 | } 55 | 56 | [Test] public void LowLevelTest() { 57 | Settings setting = new Settings(MakeNode(rman.GetString("LevelLow"))); 58 | Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm); 59 | Assertion.AssertEquals(CompressionLevels.Low, setting.CompressionLevel); 60 | } 61 | 62 | [Test] public void BadLevelTest() { 63 | Settings setting = new Settings(MakeNode(rman.GetString("BadLevel"))); 64 | Assertion.AssertEquals(CompressionLevels.Default, setting.CompressionLevel); 65 | } 66 | 67 | [Test] public void BadAlgorithmTest() { 68 | Settings setting = new Settings(MakeNode(rman.GetString("BadAlgorithm"))); 69 | Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm); 70 | } 71 | 72 | [Test] 73 | public void AddSettingsTests() { 74 | Settings setting = new Settings(MakeNode(rman.GetString("DeflateAndHigh"))); 75 | Assertion.AssertEquals(CompressionLevels.High, setting.CompressionLevel); 76 | Assertion.AssertEquals(Algorithms.Deflate, setting.PreferredAlgorithm); 77 | 78 | // test adding a null node 79 | setting.AddSettings(null); 80 | Assertion.AssertEquals(CompressionLevels.High, setting.CompressionLevel); 81 | Assertion.AssertEquals(Algorithms.Deflate, setting.PreferredAlgorithm); 82 | 83 | // test overriding algorithm 84 | setting.AddSettings(MakeNode(rman.GetString("GZip"))); 85 | Assertion.AssertEquals(CompressionLevels.High, setting.CompressionLevel); 86 | Assertion.AssertEquals(Algorithms.GZip, setting.PreferredAlgorithm); 87 | 88 | // test overriding compression level 89 | setting.AddSettings(MakeNode(rman.GetString("LevelLow"))); 90 | Assertion.AssertEquals(CompressionLevels.Low, setting.CompressionLevel); 91 | Assertion.AssertEquals(Algorithms.GZip, setting.PreferredAlgorithm); 92 | 93 | } 94 | 95 | [Test] public void UserAddedExcludedType() { 96 | Settings setting = new Settings(MakeNode(rman.GetString("ExcludeBenFoo"))); 97 | Assertion.Assert(setting.IsExcludedMimeType("ben/foo")); 98 | } 99 | 100 | [Test] public void UserAddedMultipleExcludedTypes() { 101 | Settings setting = new Settings(MakeNode(rman.GetString("ExcludeMultipleTypes"))); 102 | Assertion.Assert(setting.IsExcludedMimeType("ben/foo")); 103 | Assertion.Assert(setting.IsExcludedMimeType("image/jpeg")); 104 | } 105 | 106 | [Test] public void UserAddedAndRemovedType() { 107 | Settings setting = new Settings(MakeNode(rman.GetString("AddAndRemoveSameType"))); 108 | Assertion.Assert(!setting.IsExcludedMimeType("user/silly")); 109 | } 110 | 111 | [Test] public void UserRemovedExcludedType() { 112 | Settings setting = new Settings(MakeNode(rman.GetString("ExcludeMultipleTypes"))); 113 | Assertion.Assert(setting.IsExcludedMimeType("ben/foo")); 114 | Assertion.Assert(setting.IsExcludedMimeType("image/jpeg")); 115 | 116 | setting.AddSettings(MakeNode(rman.GetString("RemoveImageJpegExclusion"))); 117 | Assertion.Assert(setting.IsExcludedMimeType("ben/foo")); 118 | Assertion.Assert(!setting.IsExcludedMimeType("image/jpeg")); 119 | } 120 | 121 | [Test] public void ChildAddsMimeExclude(){ 122 | Settings parent = Settings.Default; 123 | parent.AddSettings(MakeNode(rman.GetString("ExcludeBenFoo"))); 124 | Assertion.Assert(parent.IsExcludedMimeType("ben/foo")); 125 | } 126 | 127 | [Test] public void ExcludeMultiplePaths() { 128 | Settings setting = new Settings(MakeNode(rman.GetString("ExcludeMultiplePaths"))); 129 | Assertion.Assert(setting.IsExcludedPath("foo.aspx")); 130 | Assertion.Assert(setting.IsExcludedPath("foo/bar.aspx")); 131 | } 132 | 133 | [Test] public void RemoveExistingPathExclude() { 134 | Settings setting = new Settings(MakeNode(rman.GetString("ExcludeMultiplePaths"))); 135 | setting.AddSettings(MakeNode(rman.GetString("RemoveFooAspxExclude"))); 136 | Assertion.Assert(!setting.IsExcludedPath("foo.aspx")); 137 | Assertion.Assert(setting.IsExcludedPath("foo/bar.aspx")); 138 | } 139 | 140 | private System.Xml.XmlNode MakeNode(string xml) { 141 | System.Xml.XmlDocument dom = new System.Xml.XmlDocument(); 142 | dom.LoadXml(xml); 143 | return dom.DocumentElement; 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /Contrib/jporter/HttpCompressionModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Web; 4 | 5 | using System.Collections; 6 | using System.Collections.Specialized; 7 | 8 | namespace blowery.Web.HttpModules { 9 | /// 10 | /// An HttpModule that hooks onto the Response.Filter property of the 11 | /// current request and tries to compress the output, based on what 12 | /// the browser supports 13 | /// 14 | /// 15 | ///

This HttpModule uses classes that inherit from . 16 | /// We already support gzip and deflate (aka zlib), if you'd like to add 17 | /// support for compress (which uses LZW, which is licensed), add in another 18 | /// class that inherits from HttpFilter to do the work.

19 | /// 20 | ///

This module checks the Accept-Encoding HTTP header to determine if the 21 | /// client actually supports any notion of compression. Currently, we support 22 | /// the deflate (zlib) and gzip compression schemes. I chose not to implement 23 | /// compress, because it's uses lzw, which generally requires a license from 24 | /// Unisys. For more information about the common compression types supported, 25 | /// see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 for details.

26 | ///
27 | /// 28 | /// 29 | public sealed class HttpCompressionModule : IHttpModule { 30 | 31 | /// 32 | /// Init the handler and fulfill 33 | /// 34 | /// 35 | /// This implementation hooks the BeginRequest event on the . 36 | /// This should be fine. 37 | /// 38 | /// The this handler is working for. 39 | void IHttpModule.Init(HttpApplication context) { 40 | context.BeginRequest += new EventHandler(this.CompressContent); 41 | } 42 | 43 | /// 44 | /// Implementation of 45 | /// 46 | /// 47 | /// Currently empty. Nothing to really do, as I have no member variables. 48 | /// 49 | void IHttpModule.Dispose() { } 50 | 51 | /// 52 | /// EventHandler that gets ahold of the current request context and attempts to compress the output. 53 | /// 54 | /// The that is firing this event. 55 | /// Arguments to the event 56 | void CompressContent(object sender, EventArgs e) { 57 | 58 | HttpApplication app = (HttpApplication)sender; 59 | 60 | // get the accepted types of compression 61 | // i'm getting them all because the string[] that GetValues was returning 62 | // contained on member with , delimited items. rather silly, so i just go for the whole thing now 63 | // also, the get call is case-insensitive, i just use the pretty casing cause it looks nice 64 | string[] acceptedTypes = app.Request.Headers.GetValues("Accept-Encoding"); 65 | 66 | // this will happen if the header wasn't found. just bail out because the client doesn't want compression 67 | if(acceptedTypes == null) { 68 | return; 69 | } 70 | 71 | // try and find a viable filter for this request 72 | HttpCompressingFilter filter = GetFilterForScheme(acceptedTypes, app.Response.Filter); 73 | 74 | // the filter will be null if no filter was found. if no filter, just bail. 75 | if(filter == null) { 76 | app.Context.Trace.Write("HttpCompressionModule", "Cannot find filter to support any of the client's desired compression schemes"); 77 | return; 78 | } 79 | 80 | // if we get here, we found a viable filter. 81 | // set the filter and change the Content-Encoding header to match so the client can decode the response 82 | app.Response.Filter = filter; 83 | app.Response.AppendHeader("Content-Encoding", filter.NameOfContentEncoding); 84 | 85 | } 86 | 87 | 88 | /// 89 | /// Get ahold of a for the given encoding scheme. 90 | /// If no encoding scheme can be found, it returns null. 91 | /// 92 | HttpCompressingFilter GetFilterForScheme(string[] schemes, Stream currentFilterStream) { 93 | 94 | bool foundDeflate = false; 95 | bool foundGZip = false; 96 | bool foundStar = false; 97 | 98 | float deflateQuality = 0; 99 | float gZipQuality = 0; 100 | float starQuality = 0; 101 | 102 | bool isAcceptableDeflate; 103 | bool isAcceptableGZip; 104 | bool isAcceptableStar; 105 | 106 | for (int i = 0; i 0); 135 | isAcceptableDeflate = (foundDeflate && (deflateQuality > 0)) || (!foundDeflate && isAcceptableStar); 136 | isAcceptableGZip = (foundGZip && (gZipQuality > 0)) || (!foundGZip && isAcceptableStar); 137 | 138 | if (isAcceptableDeflate && !foundDeflate) 139 | deflateQuality = starQuality; 140 | 141 | if (isAcceptableGZip && !foundGZip) 142 | gZipQuality = starQuality; 143 | 144 | HttpCompressionModuleSettings settings = HttpCompressionModuleSettings.GetSettings(); 145 | 146 | if (isAcceptableDeflate && (!isAcceptableGZip || (deflateQuality > gZipQuality))) 147 | return new DeflateFilter(currentFilterStream, settings.CompressionLevel); 148 | if (isAcceptableGZip && (!isAcceptableDeflate || (deflateQuality < gZipQuality))) 149 | return new GZipFilter(currentFilterStream); 150 | 151 | // if they support the preferred algorithm, use it 152 | if(isAcceptableDeflate && settings.PreferredAlgorithm == CompressionTypes.Deflate) 153 | return new DeflateFilter(currentFilterStream, settings.CompressionLevel); 154 | if(isAcceptableGZip && settings.PreferredAlgorithm == CompressionTypes.GZip) 155 | return new GZipFilter(currentFilterStream); 156 | 157 | // return null. we couldn't find a filter. 158 | return null; 159 | } 160 | 161 | float GetQuality(string acceptEncodingValue) { 162 | int qParam = acceptEncodingValue.IndexOf("q="); 163 | 164 | if (qParam >= 0) { 165 | return float.Parse(acceptEncodingValue.Substring(qParam+2, acceptEncodingValue.Length - (qParam+2))); 166 | } else 167 | return 1; 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Local 4 | 9.0.30729 5 | 2.0 6 | {758C0F47-99BF-43B2-8C91-C94CAEC72DAC} 7 | Debug 8 | AnyCPU 9 | 10 | 11 | 12 | 13 | blowery.Web.HttpCompress.Tests 14 | 15 | 16 | JScript 17 | Grid 18 | IE50 19 | false 20 | Library 21 | blowery.Web.HttpCompress.Tests 22 | OnBuildSuccess 23 | 24 | 25 | 26 | 27 | 28 | 29 | 2.0 30 | v3.5 31 | publish\ 32 | true 33 | Disk 34 | false 35 | Foreground 36 | 7 37 | Days 38 | false 39 | false 40 | true 41 | 0 42 | 1.0.0.%2a 43 | false 44 | false 45 | true 46 | 47 | 48 | bin\Debug\ 49 | false 50 | 285212672 51 | false 52 | 53 | 54 | DEBUG;TRACE 55 | 56 | 57 | true 58 | 4096 59 | false 60 | 61 | 62 | false 63 | false 64 | false 65 | false 66 | 4 67 | full 68 | prompt 69 | 70 | 71 | bin\Release\ 72 | false 73 | 285212672 74 | false 75 | 76 | 77 | TRACE 78 | 79 | 80 | false 81 | 4096 82 | false 83 | 84 | 85 | true 86 | false 87 | false 88 | false 89 | 4 90 | none 91 | prompt 92 | 93 | 94 | bin\Release\ 95 | false 96 | 285212672 97 | false 98 | 99 | 100 | TRACE 101 | 102 | 103 | false 104 | 4096 105 | false 106 | 107 | 108 | true 109 | false 110 | false 111 | false 112 | 4 113 | none 114 | prompt 115 | 116 | 117 | 118 | False 119 | ..\..\..\..\..\Program Files\NUnit 2.4.8\bin\nunit.framework.dll 120 | 121 | 122 | System 123 | 124 | 125 | System.Data 126 | 127 | 128 | System.XML 129 | 130 | 131 | HttpCompress 132 | {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5} 133 | {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 134 | 135 | 136 | 137 | 138 | XmlTestDocuments.xsd 139 | 140 | 141 | Code 142 | 143 | 144 | Code 145 | 146 | 147 | Code 148 | 149 | 150 | 151 | 152 | 153 | 154 | False 155 | .NET Framework Client Profile 156 | false 157 | 158 | 159 | False 160 | .NET Framework 2.0 %28x86%29 161 | false 162 | 163 | 164 | False 165 | .NET Framework 3.0 %28x86%29 166 | false 167 | 168 | 169 | False 170 | .NET Framework 3.5 171 | false 172 | 173 | 174 | False 175 | .NET Framework 3.5 SP1 176 | true 177 | 178 | 179 | False 180 | Windows Installer 3.1 181 | true 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /HttpCompress/HttpCompress.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Local 4 | 9.0.30729 5 | 2.0 6 | {24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5} 7 | Debug 8 | AnyCPU 9 | 10 | 11 | 12 | 13 | blowery.Web.HttpCompress 14 | 15 | 16 | JScript 17 | Grid 18 | IE50 19 | false 20 | Library 21 | blowery.Web.HttpCompress 22 | OnBuildSuccess 23 | 24 | 25 | 26 | 27 | 28 | 29 | 2.0 30 | v2.0 31 | 32 | 33 | publish\ 34 | true 35 | Disk 36 | false 37 | Foreground 38 | 7 39 | Days 40 | false 41 | false 42 | true 43 | 0 44 | 1.0.0.%2a 45 | false 46 | false 47 | true 48 | 49 | 50 | bin\Debug\ 51 | false 52 | 285212672 53 | false 54 | 55 | 56 | DEBUG;TRACE 57 | blowery.Web.HttpCompress.xml 58 | true 59 | 4096 60 | false 61 | 62 | 63 | false 64 | false 65 | false 66 | false 67 | 4 68 | full 69 | prompt 70 | 71 | 72 | bin\Release\ 73 | false 74 | 285212672 75 | false 76 | 77 | 78 | TRACE 79 | blowery.Web.HttpCompress.xml 80 | false 81 | 4096 82 | false 83 | 84 | 85 | true 86 | false 87 | false 88 | false 89 | 4 90 | none 91 | prompt 92 | 93 | 94 | bin\Production\ 95 | false 96 | 285212672 97 | false 98 | 99 | 100 | TRACE,StronglyNamedAssembly,SignedUsingACryptoServiceProvider 101 | blowery.Web.HttpCompress.xml 102 | false 103 | 4096 104 | false 105 | 106 | 107 | true 108 | false 109 | false 110 | false 111 | 4 112 | none 113 | prompt 114 | 115 | 116 | 117 | System 118 | 119 | 120 | System.Data 121 | 122 | 123 | System.Web 124 | 125 | 126 | System.XML 127 | 128 | 129 | 130 | 131 | Code 132 | 133 | 134 | Code 135 | 136 | 137 | Code 138 | 139 | 140 | Code 141 | 142 | 143 | Code 144 | 145 | 146 | Code 147 | 148 | 149 | Code 150 | 151 | 152 | Code 153 | 154 | 155 | Code 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | False 164 | .NET Framework Client Profile 165 | false 166 | 167 | 168 | False 169 | .NET Framework 2.0 %28x86%29 170 | false 171 | 172 | 173 | False 174 | .NET Framework 3.0 %28x86%29 175 | false 176 | 177 | 178 | False 179 | .NET Framework 3.5 180 | false 181 | 182 | 183 | False 184 | .NET Framework 3.5 SP1 185 | true 186 | 187 | 188 | False 189 | Windows Installer 3.1 190 | true 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /HttpCompress/HttpModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Web; 4 | 5 | using System.Collections; 6 | using System.Collections.Specialized; 7 | 8 | namespace blowery.Web.HttpCompress { 9 | /// 10 | /// An HttpModule that hooks onto the Response.Filter property of the 11 | /// current request and tries to compress the output, based on what 12 | /// the browser supports 13 | /// 14 | /// 15 | ///

This HttpModule uses classes that inherit from . 16 | /// We already support gzip and deflate (aka zlib), if you'd like to add 17 | /// support for compress (which uses LZW, which is licensed), add in another 18 | /// class that inherits from HttpFilter to do the work.

19 | /// 20 | ///

This module checks the Accept-Encoding HTTP header to determine if the 21 | /// client actually supports any notion of compression. Currently, we support 22 | /// the deflate (zlib) and gzip compression schemes. I chose to not implement 23 | /// compress because it uses lzw which requires a license from 24 | /// Unisys. For more information about the common compression types supported, 25 | /// see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 for details.

26 | ///
27 | /// 28 | /// 29 | public sealed class HttpModule : IHttpModule { 30 | 31 | const string INSTALLED_KEY = "httpcompress.attemptedinstall"; 32 | static readonly object INSTALLED_TAG = new object(); 33 | 34 | /// 35 | /// Init the handler and fulfill 36 | /// 37 | /// 38 | /// This implementation hooks the ReleaseRequestState and PreSendRequestHeaders events to 39 | /// figure out as late as possible if we should install the filter. Previous versions did 40 | /// not do this as well. 41 | /// 42 | /// The this handler is working for. 43 | void IHttpModule.Init(HttpApplication context) { 44 | context.ReleaseRequestState += new EventHandler(this.CompressContent); 45 | context.PreSendRequestHeaders += new EventHandler(this.CompressContent); 46 | } 47 | 48 | /// 49 | /// Implementation of 50 | /// 51 | /// 52 | /// Currently empty. Nothing to really do, as I have no member variables. 53 | /// 54 | void IHttpModule.Dispose() { } 55 | 56 | /// 57 | /// EventHandler that gets ahold of the current request context and attempts to compress the output. 58 | /// 59 | /// The that is firing this event. 60 | /// Arguments to the event 61 | void CompressContent(object sender, EventArgs e) { 62 | 63 | HttpApplication app = (HttpApplication)sender; 64 | 65 | // only do this if we havn't already attempted an install. This prevents PreSendRequestHeaders from 66 | // trying to add this item way to late. We only want the first run through to do anything. 67 | // also, we use the context to store whether or not we've attempted an add, as it's thread-safe and 68 | // scoped to the request. An instance of this module can service multiple requests at the same time, 69 | // so we cannot use a member variable. 70 | if(!app.Context.Items.Contains(INSTALLED_KEY)) { 71 | 72 | // log the install attempt in the HttpContext 73 | // must do this first as several IF statements 74 | // below skip full processing of this method 75 | app.Context.Items.Add(INSTALLED_KEY, INSTALLED_TAG); 76 | 77 | // get the config settings 78 | Settings settings = Settings.GetSettings(); 79 | 80 | if(settings.CompressionLevel == CompressionLevels.None){ 81 | // skip if the CompressionLevel is set to 'None' 82 | return; 83 | } 84 | 85 | string realPath = app.Request.Path.Remove(0, app.Request.ApplicationPath.Length); 86 | if(realPath.StartsWith("/")) { 87 | realPath = realPath.Substring(1); 88 | } 89 | if(settings.IsExcludedPath(realPath)){ 90 | // skip if the file path excludes compression 91 | return; 92 | } 93 | 94 | if(settings.IsExcludedMimeType(app.Response.ContentType)){ 95 | // skip if the MimeType excludes compression 96 | return; 97 | } 98 | 99 | // fix to handle caching appropriately 100 | // see http://www.pocketsoap.com/weblog/2003/07/1330.html 101 | // Note, this header is added only when the request 102 | // has the possibility of being compressed... 103 | // i.e. it is not added when the request is excluded from 104 | // compression by CompressionLevel, Path, or MimeType 105 | app.Response.Cache.VaryByHeaders["Accept-Encoding"] = true; 106 | 107 | // grab an array of algorithm;q=x, algorith;q=x style values 108 | string acceptedTypes = app.Request.Headers["Accept-Encoding"]; 109 | // if we couldn't find the header, bail out 110 | if(acceptedTypes == null){ 111 | return; 112 | } 113 | 114 | // the actual types could be , delimited. split 'em out. 115 | string[] types = acceptedTypes.Split(','); 116 | 117 | CompressingFilter filter = GetFilterForScheme(types, app.Response.Filter, settings); 118 | 119 | if(filter == null){ 120 | // if we didn't find a filter, bail out 121 | return; 122 | } 123 | 124 | // if we get here, we found a viable filter. 125 | // set the filter and change the Content-Encoding header to match so the client can decode the response 126 | app.Response.Filter = filter; 127 | 128 | } 129 | } 130 | 131 | /// 132 | /// Get ahold of a for the given encoding scheme. 133 | /// If no encoding scheme can be found, it returns null. 134 | /// 135 | /// 136 | /// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3 for details 137 | /// on how clients are supposed to construct the Accept-Encoding header. This 138 | /// implementation follows those rules, though we allow the server to override 139 | /// the preference given to different supported algorithms. I'm doing this as 140 | /// I would rather give the server control over the algorithm decision than 141 | /// the client. If the clients send up * as an accepted encoding with highest 142 | /// quality, we use the preferred algorithm as specified in the config file. 143 | /// 144 | public static CompressingFilter GetFilterForScheme(string[] schemes, Stream output, Settings prefs) { 145 | bool foundDeflate = false; 146 | bool foundGZip = false; 147 | bool foundStar = false; 148 | 149 | float deflateQuality = 0f; 150 | float gZipQuality = 0f; 151 | float starQuality = 0f; 152 | 153 | bool isAcceptableDeflate; 154 | bool isAcceptableGZip; 155 | bool isAcceptableStar; 156 | 157 | for (int i = 0; i 0); 186 | isAcceptableDeflate = (foundDeflate && (deflateQuality > 0)) || (!foundDeflate && isAcceptableStar); 187 | isAcceptableGZip = (foundGZip && (gZipQuality > 0)) || (!foundGZip && isAcceptableStar); 188 | 189 | if (isAcceptableDeflate && !foundDeflate) 190 | deflateQuality = starQuality; 191 | 192 | if (isAcceptableGZip && !foundGZip) 193 | gZipQuality = starQuality; 194 | 195 | 196 | // do they support any of our compression methods? 197 | if(!(isAcceptableDeflate || isAcceptableGZip || isAcceptableStar)) { 198 | return null; 199 | } 200 | 201 | 202 | // if deflate is better according to client 203 | if (isAcceptableDeflate && (!isAcceptableGZip || (deflateQuality > gZipQuality))) 204 | return new DeflateFilter(output, prefs.CompressionLevel); 205 | 206 | // if gzip is better according to client 207 | if (isAcceptableGZip && (!isAcceptableDeflate || (deflateQuality < gZipQuality))) 208 | return new GZipFilter(output); 209 | 210 | // if we're here, the client either didn't have a preference or they don't support compression 211 | if(isAcceptableDeflate && (prefs.PreferredAlgorithm == Algorithms.Deflate || prefs.PreferredAlgorithm == Algorithms.Default)) 212 | return new DeflateFilter(output, prefs.CompressionLevel); 213 | if(isAcceptableGZip && prefs.PreferredAlgorithm == Algorithms.GZip) 214 | return new GZipFilter(output); 215 | 216 | if(isAcceptableDeflate || isAcceptableStar) 217 | return new DeflateFilter(output, prefs.CompressionLevel); 218 | if(isAcceptableGZip) 219 | return new GZipFilter(output); 220 | 221 | // return null. we couldn't find a filter. 222 | return null; 223 | } 224 | 225 | static float GetQuality(string acceptEncodingValue) { 226 | int qParam = acceptEncodingValue.IndexOf("q="); 227 | 228 | if (qParam >= 0) { 229 | float val = 0.0f; 230 | try { 231 | val = float.Parse(acceptEncodingValue.Substring(qParam+2, acceptEncodingValue.Length - (qParam+2))); 232 | } catch(FormatException) { 233 | 234 | } 235 | return val; 236 | } else 237 | return 1; 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /Fetch/MainForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Collections; 4 | using System.ComponentModel; 5 | using System.IO; 6 | using System.Net; 7 | using System.Windows.Forms; 8 | 9 | namespace Fetch 10 | { 11 | /// 12 | /// Summary description for MainForm. 13 | /// 14 | public class MainForm : System.Windows.Forms.Form 15 | { 16 | private System.Windows.Forms.StatusBar statusBar; 17 | private System.Windows.Forms.Label urlLabel; 18 | private System.Windows.Forms.GroupBox groupBox1; 19 | private System.Windows.Forms.Label label1; 20 | private System.Windows.Forms.RadioButton gzipOption; 21 | private System.Windows.Forms.GroupBox groupBox2; 22 | private System.Windows.Forms.TextBox urlToHit; 23 | private System.Windows.Forms.Button evaluateButton; 24 | private System.Windows.Forms.RadioButton deflateOption; 25 | private System.Windows.Forms.ListBox resultsList; 26 | private System.Windows.Forms.Label userAgentLabel; 27 | private System.Windows.Forms.TextBox userAgentToUse; 28 | /// 29 | /// Required designer variable. 30 | /// 31 | private System.ComponentModel.Container components = null; 32 | 33 | public MainForm() 34 | { 35 | // 36 | // Required for Windows Form Designer support 37 | // 38 | InitializeComponent(); 39 | 40 | // 41 | // TODO: Add any constructor code after InitializeComponent call 42 | // 43 | } 44 | 45 | /// 46 | /// Clean up any resources being used. 47 | /// 48 | protected override void Dispose( bool disposing ) 49 | { 50 | if( disposing ) 51 | { 52 | if(components != null) 53 | { 54 | components.Dispose(); 55 | } 56 | } 57 | base.Dispose( disposing ); 58 | } 59 | 60 | #region Windows Form Designer generated code 61 | /// 62 | /// Required method for Designer support - do not modify 63 | /// the contents of this method with the code editor. 64 | /// 65 | private void InitializeComponent() 66 | { 67 | this.statusBar = new System.Windows.Forms.StatusBar(); 68 | this.urlToHit = new System.Windows.Forms.TextBox(); 69 | this.urlLabel = new System.Windows.Forms.Label(); 70 | this.groupBox1 = new System.Windows.Forms.GroupBox(); 71 | this.userAgentToUse = new System.Windows.Forms.TextBox(); 72 | this.userAgentLabel = new System.Windows.Forms.Label(); 73 | this.gzipOption = new System.Windows.Forms.RadioButton(); 74 | this.deflateOption = new System.Windows.Forms.RadioButton(); 75 | this.label1 = new System.Windows.Forms.Label(); 76 | this.evaluateButton = new System.Windows.Forms.Button(); 77 | this.groupBox2 = new System.Windows.Forms.GroupBox(); 78 | this.resultsList = new System.Windows.Forms.ListBox(); 79 | this.groupBox1.SuspendLayout(); 80 | this.groupBox2.SuspendLayout(); 81 | this.SuspendLayout(); 82 | // 83 | // statusBar 84 | // 85 | this.statusBar.Location = new System.Drawing.Point(0, 247); 86 | this.statusBar.Name = "statusBar"; 87 | this.statusBar.Size = new System.Drawing.Size(492, 22); 88 | this.statusBar.TabIndex = 0; 89 | this.statusBar.Text = "OK"; 90 | // 91 | // urlToHit 92 | // 93 | this.urlToHit.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 94 | | System.Windows.Forms.AnchorStyles.Right))); 95 | this.urlToHit.Location = new System.Drawing.Point(8, 32); 96 | this.urlToHit.Name = "urlToHit"; 97 | this.urlToHit.Size = new System.Drawing.Size(376, 21); 98 | this.urlToHit.TabIndex = 1; 99 | this.urlToHit.Text = "http://"; 100 | // 101 | // urlLabel 102 | // 103 | this.urlLabel.Location = new System.Drawing.Point(8, 16); 104 | this.urlLabel.Name = "urlLabel"; 105 | this.urlLabel.Size = new System.Drawing.Size(120, 16); 106 | this.urlLabel.TabIndex = 2; 107 | this.urlLabel.Text = "URL to Check:"; 108 | // 109 | // groupBox1 110 | // 111 | this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 112 | | System.Windows.Forms.AnchorStyles.Right))); 113 | this.groupBox1.Controls.Add(this.userAgentToUse); 114 | this.groupBox1.Controls.Add(this.userAgentLabel); 115 | this.groupBox1.Controls.Add(this.gzipOption); 116 | this.groupBox1.Controls.Add(this.deflateOption); 117 | this.groupBox1.Controls.Add(this.label1); 118 | this.groupBox1.Controls.Add(this.urlLabel); 119 | this.groupBox1.Controls.Add(this.urlToHit); 120 | this.groupBox1.Controls.Add(this.evaluateButton); 121 | this.groupBox1.Location = new System.Drawing.Point(8, 8); 122 | this.groupBox1.Name = "groupBox1"; 123 | this.groupBox1.Size = new System.Drawing.Size(476, 136); 124 | this.groupBox1.TabIndex = 3; 125 | this.groupBox1.TabStop = false; 126 | this.groupBox1.Text = "Connection Options"; 127 | // 128 | // userAgentToUse 129 | // 130 | this.userAgentToUse.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 131 | | System.Windows.Forms.AnchorStyles.Right))); 132 | this.userAgentToUse.Location = new System.Drawing.Point(224, 64); 133 | this.userAgentToUse.Name = "userAgentToUse"; 134 | this.userAgentToUse.Size = new System.Drawing.Size(160, 21); 135 | this.userAgentToUse.TabIndex = 6; 136 | this.userAgentToUse.Text = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT)"; 137 | // 138 | // userAgentLabel 139 | // 140 | this.userAgentLabel.Location = new System.Drawing.Point(152, 64); 141 | this.userAgentLabel.Name = "userAgentLabel"; 142 | this.userAgentLabel.Size = new System.Drawing.Size(64, 16); 143 | this.userAgentLabel.TabIndex = 7; 144 | this.userAgentLabel.Text = "User Agent"; 145 | // 146 | // gzipOption 147 | // 148 | this.gzipOption.Checked = true; 149 | this.gzipOption.Location = new System.Drawing.Point(24, 104); 150 | this.gzipOption.Name = "gzipOption"; 151 | this.gzipOption.TabIndex = 5; 152 | this.gzipOption.TabStop = true; 153 | this.gzipOption.Text = "GZip"; 154 | // 155 | // deflateOption 156 | // 157 | this.deflateOption.Location = new System.Drawing.Point(24, 80); 158 | this.deflateOption.Name = "deflateOption"; 159 | this.deflateOption.TabIndex = 4; 160 | this.deflateOption.Tag = "deflate"; 161 | this.deflateOption.Text = "Deflate"; 162 | // 163 | // label1 164 | // 165 | this.label1.Location = new System.Drawing.Point(8, 64); 166 | this.label1.Name = "label1"; 167 | this.label1.Size = new System.Drawing.Size(144, 23); 168 | this.label1.TabIndex = 3; 169 | this.label1.Text = "Compression Algorithm:"; 170 | // 171 | // evaluateButton 172 | // 173 | this.evaluateButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 174 | this.evaluateButton.Location = new System.Drawing.Point(392, 32); 175 | this.evaluateButton.Name = "evaluateButton"; 176 | this.evaluateButton.TabIndex = 4; 177 | this.evaluateButton.Text = "Fetch!"; 178 | this.evaluateButton.Click += new System.EventHandler(this.evaluateButton_Click); 179 | // 180 | // groupBox2 181 | // 182 | this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 183 | | System.Windows.Forms.AnchorStyles.Left) 184 | | System.Windows.Forms.AnchorStyles.Right))); 185 | this.groupBox2.Controls.Add(this.resultsList); 186 | this.groupBox2.Location = new System.Drawing.Point(8, 160); 187 | this.groupBox2.Name = "groupBox2"; 188 | this.groupBox2.Size = new System.Drawing.Size(476, 84); 189 | this.groupBox2.TabIndex = 4; 190 | this.groupBox2.TabStop = false; 191 | this.groupBox2.Text = "Results"; 192 | // 193 | // resultsList 194 | // 195 | this.resultsList.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 196 | | System.Windows.Forms.AnchorStyles.Left) 197 | | System.Windows.Forms.AnchorStyles.Right))); 198 | this.resultsList.Location = new System.Drawing.Point(8, 16); 199 | this.resultsList.Name = "resultsList"; 200 | this.resultsList.Size = new System.Drawing.Size(456, 56); 201 | this.resultsList.TabIndex = 0; 202 | // 203 | // MainForm 204 | // 205 | this.AcceptButton = this.evaluateButton; 206 | this.AutoScaleBaseSize = new System.Drawing.Size(5, 14); 207 | this.ClientSize = new System.Drawing.Size(492, 269); 208 | this.Controls.Add(this.groupBox2); 209 | this.Controls.Add(this.groupBox1); 210 | this.Controls.Add(this.statusBar); 211 | this.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0))); 212 | this.MinimumSize = new System.Drawing.Size(500, 300); 213 | this.Name = "MainForm"; 214 | this.Text = "Compression Checker"; 215 | this.groupBox1.ResumeLayout(false); 216 | this.groupBox2.ResumeLayout(false); 217 | this.ResumeLayout(false); 218 | 219 | } 220 | #endregion 221 | 222 | private void evaluateButton_Click(object sender, System.EventArgs e) { 223 | Uri url; 224 | try { 225 | url = new Uri(urlToHit.Text); 226 | } catch(UriFormatException) { 227 | statusBar.Text = "Invalid URL!"; 228 | return; 229 | } 230 | statusBar.Text = "Fetching uncompressed..."; 231 | 232 | RunInfo[] results = new RunInfo[2]; 233 | 234 | results[0] = FetchUncompressedUrl(url); 235 | statusBar.Text = "Fetching compressed..."; 236 | 237 | if(deflateOption.Checked) 238 | results[1] = FetchCompressedUrl(url, "deflate"); 239 | else 240 | results[1] = FetchCompressedUrl(url, "gzip"); 241 | 242 | resultsList.DataSource = results; 243 | 244 | statusBar.Text = "Done!"; 245 | 246 | } 247 | 248 | RunInfo FetchUncompressedUrl(Uri url) { 249 | RunInfo ri = new RunInfo(); 250 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); 251 | request.UserAgent = userAgentToUse.Text; 252 | DateTime start = DateTime.Now; 253 | HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 254 | ri.TimeToFirstByte = DateTime.Now - start; 255 | ri.BytesReceived = DumpStream(response.GetResponseStream(), Stream.Null); 256 | ri.TimeToLastByte = DateTime.Now - start; 257 | return ri; 258 | } 259 | 260 | RunInfo FetchCompressedUrl(Uri url, string algo) { 261 | RunInfo ri = new RunInfo(algo); 262 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); 263 | request.Headers["Accept-Encoding"] = algo; 264 | request.UserAgent = userAgentToUse.Text; 265 | DateTime start = DateTime.Now; 266 | HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 267 | ri.TimeToFirstByte = DateTime.Now - start; 268 | ri.BytesReceived = DumpStream(response.GetResponseStream(), Stream.Null); 269 | ri.TimeToLastByte = DateTime.Now - start; 270 | return ri; 271 | } 272 | 273 | static long DumpStream(Stream s, Stream output) { 274 | 275 | byte[] buffer = new byte[1024]; 276 | long count = 0; 277 | int read = 0; 278 | while((read = s.Read(buffer, 0, 1024)) > 0) { 279 | count += read; 280 | output.Write(buffer, 0, read); 281 | } 282 | return count; 283 | } 284 | 285 | class RunInfo { 286 | public long BytesReceived = -1; 287 | public TimeSpan TimeToFirstByte = TimeSpan.Zero; 288 | public TimeSpan TimeToLastByte = TimeSpan.Zero; 289 | public TimeSpan TransitTime { get { return TimeToLastByte - TimeToFirstByte; } } 290 | public string Algorithm = "none"; 291 | 292 | public RunInfo() { 293 | } 294 | 295 | public RunInfo(string algo) { 296 | Algorithm = algo; 297 | } 298 | 299 | public override string ToString() { 300 | return string.Format("{0}; {1} bytes; TTFB={2}ms; TTLB={3}ms; Transit={4}ms", Algorithm, BytesReceived, TimeToFirstByte.TotalMilliseconds, TimeToLastByte.TotalMilliseconds, TransitTime.TotalMilliseconds); 301 | } 302 | 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /Fetch/MainForm.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | text/microsoft-resx 90 | 91 | 92 | 1.3 93 | 94 | 95 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 96 | 97 | 98 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 99 | 100 | 101 | False 102 | 103 | 104 | Private 105 | 106 | 107 | Private 108 | 109 | 110 | Private 111 | 112 | 113 | False 114 | 115 | 116 | Private 117 | 118 | 119 | False 120 | 121 | 122 | Private 123 | 124 | 125 | Private 126 | 127 | 128 | Private 129 | 130 | 131 | 8, 8 132 | 133 | 134 | True 135 | 136 | 137 | False 138 | 139 | 140 | True 141 | 142 | 143 | Private 144 | 145 | 146 | Private 147 | 148 | 149 | False 150 | 151 | 152 | Private 153 | 154 | 155 | False 156 | 157 | 158 | Private 159 | 160 | 161 | Private 162 | 163 | 164 | False 165 | 166 | 167 | Private 168 | 169 | 170 | Private 171 | 172 | 173 | False 174 | 175 | 176 | Private 177 | 178 | 179 | Private 180 | 181 | 182 | False 183 | 184 | 185 | Private 186 | 187 | 188 | Private 189 | 190 | 191 | False 192 | 193 | 194 | Private 195 | 196 | 197 | Private 198 | 199 | 200 | Private 201 | 202 | 203 | 8, 8 204 | 205 | 206 | True 207 | 208 | 209 | False 210 | 211 | 212 | True 213 | 214 | 215 | Private 216 | 217 | 218 | Private 219 | 220 | 221 | False 222 | 223 | 224 | Private 225 | 226 | 227 | False 228 | 229 | 230 | (Default) 231 | 232 | 233 | False 234 | 235 | 236 | False 237 | 238 | 239 | 8, 8 240 | 241 | 242 | True 243 | 244 | 245 | 80 246 | 247 | 248 | MainForm 249 | 250 | 251 | True 252 | 253 | 254 | Private 255 | 256 | --------------------------------------------------------------------------------