Skip to content
This repository has been archived by the owner on Jul 27, 2021. It is now read-only.

Commit

Permalink
initial import
Browse files Browse the repository at this point in the history
blowery committed Oct 27, 2007

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
1 parent 8637a24 commit bfa5786
Showing 38 changed files with 6,516 additions and 0 deletions.
170 changes: 170 additions & 0 deletions Contrib/jporter/HttpCompressionModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
using System;
using System.IO;
using System.Web;

using System.Collections;
using System.Collections.Specialized;

namespace blowery.Web.HttpModules {
/// <summary>
/// An HttpModule that hooks onto the Response.Filter property of the
/// current request and tries to compress the output, based on what
/// the browser supports
/// </summary>
/// <remarks>
/// <p>This HttpModule uses classes that inherit from <see cref="HttpCompressingFilter"/>.
/// We already support gzip and deflate (aka zlib), if you'd like to add
/// support for compress (which uses LZW, which is licensed), add in another
/// class that inherits from HttpFilter to do the work.</p>
///
/// <p>This module checks the Accept-Encoding HTTP header to determine if the
/// client actually supports any notion of compression. Currently, we support
/// the deflate (zlib) and gzip compression schemes. I chose not to implement
/// compress, because it's uses lzw, which generally requires a license from
/// Unisys. For more information about the common compression types supported,
/// see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 for details.</p>
/// </remarks>
/// <seealso cref="HttpCompressingFilter"/>
/// <seealso cref="Stream"/>
public sealed class HttpCompressionModule : IHttpModule {

/// <summary>
/// Init the handler and fulfill <see cref="IHttpModule"/>
/// </summary>
/// <remarks>
/// This implementation hooks the BeginRequest event on the <see cref="HttpApplication"/>.
/// This should be fine.
/// </remarks>
/// <param name="context">The <see cref="HttpApplication"/> this handler is working for.</param>
void IHttpModule.Init(HttpApplication context) {
context.BeginRequest += new EventHandler(this.CompressContent);
}

/// <summary>
/// Implementation of <see cref="IHttpModule"/>
/// </summary>
/// <remarks>
/// Currently empty. Nothing to really do, as I have no member variables.
/// </remarks>
void IHttpModule.Dispose() { }

/// <summary>
/// EventHandler that gets ahold of the current request context and attempts to compress the output.
/// </summary>
/// <param name="sender">The <see cref="HttpApplication"/> that is firing this event.</param>
/// <param name="e">Arguments to the event</param>
void CompressContent(object sender, EventArgs e) {

HttpApplication app = (HttpApplication)sender;

// get the accepted types of compression
// i'm getting them all because the string[] that GetValues was returning
// contained on member with , delimited items. rather silly, so i just go for the whole thing now
// also, the get call is case-insensitive, i just use the pretty casing cause it looks nice
string[] acceptedTypes = app.Request.Headers.GetValues("Accept-Encoding");

// this will happen if the header wasn't found. just bail out because the client doesn't want compression
if(acceptedTypes == null) {
return;
}

// try and find a viable filter for this request
HttpCompressingFilter filter = GetFilterForScheme(acceptedTypes, app.Response.Filter);

// the filter will be null if no filter was found. if no filter, just bail.
if(filter == null) {
app.Context.Trace.Write("HttpCompressionModule", "Cannot find filter to support any of the client's desired compression schemes");
return;
}

// if we get here, we found a viable filter.
// set the filter and change the Content-Encoding header to match so the client can decode the response
app.Response.Filter = filter;
app.Response.AppendHeader("Content-Encoding", filter.NameOfContentEncoding);

}


/// <summary>
/// Get ahold of a <see cref="HttpCompressingFilter"/> for the given encoding scheme.
/// If no encoding scheme can be found, it returns null.
/// </summary>
HttpCompressingFilter GetFilterForScheme(string[] schemes, Stream currentFilterStream) {

bool foundDeflate = false;
bool foundGZip = false;
bool foundStar = false;

float deflateQuality = 0;
float gZipQuality = 0;
float starQuality = 0;

bool isAcceptableDeflate;
bool isAcceptableGZip;
bool isAcceptableStar;

for (int i = 0; i<schemes.Length;i++) {
string acceptEncodingValue = schemes[i].Trim().ToLower();

if (acceptEncodingValue.StartsWith("deflate")) {
foundDeflate = true;

float newDeflateQuality = GetQuality(acceptEncodingValue);
if (deflateQuality < newDeflateQuality)
deflateQuality = newDeflateQuality;
}

if (acceptEncodingValue.StartsWith("gzip") || acceptEncodingValue.StartsWith("x-gzip")) {
foundGZip = true;

float newGZipQuality = GetQuality(acceptEncodingValue);
if (gZipQuality < newGZipQuality)
gZipQuality = newGZipQuality;
}

if (acceptEncodingValue.StartsWith("*")) {
foundStar = true;

float newStarQuality = GetQuality(acceptEncodingValue);
if (starQuality < newStarQuality)
starQuality = newStarQuality;
}
}

isAcceptableStar = foundStar && (starQuality > 0);
isAcceptableDeflate = (foundDeflate && (deflateQuality > 0)) || (!foundDeflate && isAcceptableStar);
isAcceptableGZip = (foundGZip && (gZipQuality > 0)) || (!foundGZip && isAcceptableStar);

if (isAcceptableDeflate && !foundDeflate)
deflateQuality = starQuality;

if (isAcceptableGZip && !foundGZip)
gZipQuality = starQuality;

HttpCompressionModuleSettings settings = HttpCompressionModuleSettings.GetSettings();

if (isAcceptableDeflate && (!isAcceptableGZip || (deflateQuality > gZipQuality)))
return new DeflateFilter(currentFilterStream, settings.CompressionLevel);
if (isAcceptableGZip && (!isAcceptableDeflate || (deflateQuality < gZipQuality)))
return new GZipFilter(currentFilterStream);

// if they support the preferred algorithm, use it
if(isAcceptableDeflate && settings.PreferredAlgorithm == CompressionTypes.Deflate)
return new DeflateFilter(currentFilterStream, settings.CompressionLevel);
if(isAcceptableGZip && settings.PreferredAlgorithm == CompressionTypes.GZip)
return new GZipFilter(currentFilterStream);

// return null. we couldn't find a filter.
return null;
}

float GetQuality(string acceptEncodingValue) {
int qParam = acceptEncodingValue.IndexOf("q=");

if (qParam >= 0) {
return float.Parse(acceptEncodingValue.Substring(qParam+2, acceptEncodingValue.Length - (qParam+2)));
} else
return 1;
}
}
}
12 changes: 12 additions & 0 deletions Example/Default.aspx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<%@ Page Inherits="Example.DefaultController"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>a test of compression</title>
</head>
<body>
Hi there pokey!<br >
<asp:label id="MyLabel" runat="server"/>
</body>
</html>
32 changes: 32 additions & 0 deletions Example/DefaultController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Example {
/// <summary>
/// This class acts as a controller for the default.aspx page.
/// It handles Page events and maps events from
/// the classes it contains to event handlers.
/// </summary>
public class DefaultController : Page {

/// <summary>
/// A label on the form
/// </summary>
protected Label MyLabel;

/// <summary>
/// Override of OnLoad that adds some processing.
/// </summary>
/// <param name="e"></param>
protected override void OnLoad(EventArgs e) {
try {
MyLabel.Text = "Right Now: " + DateTime.Now.ToString();
}finally {
base.OnLoad(e); // be sure to call base to fire the event
}
}


}
}
153 changes: 153 additions & 0 deletions Example/Example.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectType>Local</ProjectType>
<ProductVersion>8.0.50727</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{621AAC3C-38EC-4F70-80D3-68DE4829AA33}</ProjectGuid>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ApplicationIcon>
</ApplicationIcon>
<AssemblyKeyContainerName>
</AssemblyKeyContainerName>
<AssemblyName>Example</AssemblyName>
<AssemblyOriginatorKeyFile>
</AssemblyOriginatorKeyFile>
<DefaultClientScript>JScript</DefaultClientScript>
<DefaultHTMLPageLayout>Grid</DefaultHTMLPageLayout>
<DefaultTargetSchema>IE50</DefaultTargetSchema>
<DelaySign>false</DelaySign>
<OutputType>Library</OutputType>
<RootNamespace>Example</RootNamespace>
<RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
<StartupObject>
</StartupObject>
<FileUpgradeFlags>
</FileUpgradeFlags>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<OutputPath>bin\</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>
</DefineConstants>
<DocumentationFile>
</DocumentationFile>
<DebugSymbols>true</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>
<NoWarn>
</NoWarn>
<Optimize>false</Optimize>
<RegisterForComInterop>false</RegisterForComInterop>
<RemoveIntegerChecks>false</RemoveIntegerChecks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningLevel>1</WarningLevel>
<DebugType>full</DebugType>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<OutputPath>bin\</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>
</DefineConstants>
<DocumentationFile>
</DocumentationFile>
<DebugSymbols>false</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>
<NoWarn>
</NoWarn>
<Optimize>false</Optimize>
<RegisterForComInterop>false</RegisterForComInterop>
<RemoveIntegerChecks>false</RemoveIntegerChecks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningLevel>1</WarningLevel>
<DebugType>none</DebugType>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Production|AnyCPU' ">
<OutputPath>bin\</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>
</DefineConstants>
<DocumentationFile>
</DocumentationFile>
<DebugSymbols>false</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>
<NoWarn>
</NoWarn>
<Optimize>false</Optimize>
<RegisterForComInterop>false</RegisterForComInterop>
<RemoveIntegerChecks>false</RemoveIntegerChecks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningLevel>1</WarningLevel>
<DebugType>none</DebugType>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<Reference Include="ICSharpCode.SharpZipLib">
<Name>ICSharpCode.SharpZipLib</Name>
<HintPath>..\Vendor\ICSharpCode.SharpZipLib\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="System">
<Name>System</Name>
</Reference>
<Reference Include="System.Data">
<Name>System.Data</Name>
</Reference>
<Reference Include="System.Drawing">
<Name>System.Drawing</Name>
</Reference>
<Reference Include="System.Web">
<Name>System.Web</Name>
</Reference>
<Reference Include="System.Windows.Forms">
<Name>System.Windows.Forms</Name>
</Reference>
<Reference Include="System.Xml">
<Name>System.XML</Name>
</Reference>
<ProjectReference Include="..\HttpCompress\HttpCompress.csproj">
<Name>HttpCompress</Name>
<Project>{24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}</Project>
<Package>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</Package>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="Default.aspx" />
<Content Include="NoCompress.aspx">
<SubType>Form</SubType>
</Content>
<Compile Include="DefaultController.cs">
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="ExceptionThrowingHandler.cs">
<SubType>Code</SubType>
</Compile>
<None Include="ExistingImage.ashx" />
<None Include="Image.ashx" />
<None Include="web.config" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
</Project>
22 changes: 22 additions & 0 deletions Example/ExceptionThrowingHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Web;

namespace Example
{
/// <summary>
/// An HttpHandler that just throws exceptions.
/// </summary>
public class ExceptionThrowingHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context) {
//context.Response.Write("foo");
throw new Exception("My custom exception.");
}

public bool IsReusable {
get {
return true;
}
}
}
}
22 changes: 22 additions & 0 deletions Example/ExistingImage.ashx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<%@ WebHandler Language="C#" Class="ExistingImage" %>

using System.Web;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

public class ExistingImage : IHttpHandler
{

public void ProcessRequest (HttpContext context)
{
context.Response.ContentType = "image/gif";
context.Response.WriteFile("blowery.gif");
}


public bool IsReusable
{
get { return true; }
}
}
36 changes: 36 additions & 0 deletions Example/Image.ashx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<%@ WebHandler Language="C#" Class="ImageMaker" %>

using System.Web;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

public class ImageMaker : IHttpHandler
{
public void ProcessRequest (HttpContext context)
{
using(Bitmap myFoo = new Bitmap(200,200,PixelFormat.Format24bppRgb)) {
using(Graphics g = Graphics.FromImage(myFoo)) {

g.FillRectangle(Brushes.Gray,0,0,200,200);
g.FillPie(Brushes.Yellow,100,100,100,100,0,90);

}
context.Response.ContentType = "image/png";

// have to do this crap because saving
// png directly to HttpResponse.OutputStream
// is broken in the 1.0 bits (at least up to sp2)
// should just be
// myFoo.Save(context.Response.OutputStream, ImageFormat.Png);
MemoryStream ms = new MemoryStream();
myFoo.Save(ms, ImageFormat.Png);
context.Response.OutputStream.Write(ms.ToArray(), 0, (int)ms.Length);
}
}

public bool IsReusable
{
get { return true; }
}
}
9 changes: 9 additions & 0 deletions Example/NoCompress.aspx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<%@ Page %>
<html>
<head>
<title>A non-compressed page!</title>
</head>
<body>
<p>This page was not compressed! Hopefully.</p>
</body>
</html>
Binary file added Example/blowery.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 52 additions & 0 deletions Example/web.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?xml version="1.0"?>
<!--
This is a sample web.config. After compiling the project, put the .dll into the
bin directory of your web application (or add a reference to the project in vs.net).
After you have the .dll in your asp.net app's bin directory, add the following
chunks to your web.config file.
-->
<configuration>

<!-- This chunk sets up the configuration section handler for blowery.web/httpModules/ -->
<configSections>
<sectionGroup name="blowery.web">
<section name="httpCompress" type="blowery.Web.HttpCompress.SectionHandler, blowery.Web.HttpCompress"/>
</sectionGroup>
</configSections>

<!-- config section for my http module -->
<blowery.web>
<!--
Here's an example on how to change the algorithm or compression level
<compressionModule preferredAlgorithm="deflate|gzip" compressionLevel="high|normal|low"/>
so, to use deflate by default, and high compression, you would use the following line
-->
<httpCompress preferredAlgorithm="gzip" compressionLevel="high">
<excludedMimeTypes>
<add type="image/jpeg"/>
<add type="image/gif"/>
</excludedMimeTypes>
<excludedPaths>
<add path="NoCompress.aspx"/>
</excludedPaths>
</httpCompress>
</blowery.web>

<!--
Here, we insert the HttpModule into the HttpRuntime for this web application.
-->
<system.web>
<compilation debug="true"/>
<authentication mode="None"/>
<httpModules>
<add name="CompressionModule" type="blowery.Web.HttpCompress.HttpModule, blowery.web.HttpCompress"/>
</httpModules>
<httpHandlers>
<add verb="*" path="throw.aspx" type="Example.ExceptionThrowingHandler, Example"/>
</httpHandlers>
</system.web>

</configuration>
Binary file added Fetch/App.ico
Binary file not shown.
58 changes: 58 additions & 0 deletions Fetch/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Reflection;
using System.Runtime.CompilerServices;

//
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
//
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

//
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:

[assembly: AssemblyVersion("6.0")]

//
// In order to sign your assembly you must specify a key to use. Refer to the
// Microsoft .NET Framework documentation for more information on assembly signing.
//
// Use the attributes below to control which key is used for signing.
//
// Notes:
// (*) If no key is specified, the assembly is not signed.
// (*) KeyName refers to a key that has been installed in the Crypto Service
// Provider (CSP) on your machine. KeyFile refers to a file which contains
// a key.
// (*) If the KeyFile and the KeyName values are both specified, the
// following processing occurs:
// (1) If the KeyName can be found in the CSP, that key is used.
// (2) If the KeyName does not exist and the KeyFile does exist, the key
// in the KeyFile is installed into the CSP and used.
// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility.
// When specifying the KeyFile, the location of the KeyFile should be
// relative to the project output directory which is
// %Project Directory%\obj\<configuration>. For example, if your KeyFile is
// located in the project directory, you would specify the AssemblyKeyFile
// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")]
// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework
// documentation for more information on this.
//
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("")]
[assembly: AssemblyKeyName("")]
20 changes: 20 additions & 0 deletions Fetch/EntryPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.IO;
using System.Net;

using System.Windows.Forms;

namespace Fetch {
class EntryPoint {

/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args) {
Application.Run(new MainForm());
}


}
}
136 changes: 136 additions & 0 deletions Fetch/Fetch.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectType>Local</ProjectType>
<ProductVersion>8.0.50727</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{3EFC307A-7AA7-4566-8517-BD80263C2830}</ProjectGuid>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ApplicationIcon>App.ico</ApplicationIcon>
<AssemblyKeyContainerName>
</AssemblyKeyContainerName>
<AssemblyName>Fetch</AssemblyName>
<AssemblyOriginatorKeyFile>
</AssemblyOriginatorKeyFile>
<DefaultClientScript>JScript</DefaultClientScript>
<DefaultHTMLPageLayout>Grid</DefaultHTMLPageLayout>
<DefaultTargetSchema>IE50</DefaultTargetSchema>
<DelaySign>false</DelaySign>
<OutputType>WinExe</OutputType>
<RootNamespace>Fetch</RootNamespace>
<RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
<StartupObject>Fetch.EntryPoint</StartupObject>
<FileUpgradeFlags>
</FileUpgradeFlags>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<OutputPath>bin\Debug\</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DocumentationFile>
</DocumentationFile>
<DebugSymbols>true</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>
<NoWarn>
</NoWarn>
<Optimize>false</Optimize>
<RegisterForComInterop>false</RegisterForComInterop>
<RemoveIntegerChecks>false</RemoveIntegerChecks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel>
<DebugType>full</DebugType>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<OutputPath>bin\Release\</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>TRACE</DefineConstants>
<DocumentationFile>
</DocumentationFile>
<DebugSymbols>false</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>
<NoWarn>
</NoWarn>
<Optimize>true</Optimize>
<RegisterForComInterop>false</RegisterForComInterop>
<RemoveIntegerChecks>false</RemoveIntegerChecks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel>
<DebugType>none</DebugType>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Production|AnyCPU' ">
<OutputPath>bin\Release\</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>TRACE</DefineConstants>
<DocumentationFile>
</DocumentationFile>
<DebugSymbols>false</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>
<NoWarn>
</NoWarn>
<Optimize>true</Optimize>
<RegisterForComInterop>false</RegisterForComInterop>
<RemoveIntegerChecks>false</RemoveIntegerChecks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel>
<DebugType>none</DebugType>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<Reference Include="System">
<Name>System</Name>
</Reference>
<Reference Include="System.Data">
<Name>System.Data</Name>
</Reference>
<Reference Include="System.Drawing">
<Name>System.Drawing</Name>
</Reference>
<Reference Include="System.Windows.Forms">
<Name>System.Windows.Forms</Name>
</Reference>
<Reference Include="System.Xml">
<Name>System.XML</Name>
</Reference>
</ItemGroup>
<ItemGroup>
<Content Include="App.ico" />
<Compile Include="AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="EntryPoint.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="MainForm.cs">
<SubType>Form</SubType>
</Compile>
<EmbeddedResource Include="MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
</Project>
305 changes: 305 additions & 0 deletions Fetch/MainForm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Windows.Forms;

namespace Fetch
{
/// <summary>
/// Summary description for MainForm.
/// </summary>
public class MainForm : System.Windows.Forms.Form
{
private System.Windows.Forms.StatusBar statusBar;
private System.Windows.Forms.Label urlLabel;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.RadioButton gzipOption;
private System.Windows.Forms.GroupBox groupBox2;
private System.Windows.Forms.TextBox urlToHit;
private System.Windows.Forms.Button evaluateButton;
private System.Windows.Forms.RadioButton deflateOption;
private System.Windows.Forms.ListBox resultsList;
private System.Windows.Forms.Label userAgentLabel;
private System.Windows.Forms.TextBox userAgentToUse;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

public MainForm()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();

//
// TODO: Add any constructor code after InitializeComponent call
//
}

/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}

#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.statusBar = new System.Windows.Forms.StatusBar();
this.urlToHit = new System.Windows.Forms.TextBox();
this.urlLabel = new System.Windows.Forms.Label();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.userAgentToUse = new System.Windows.Forms.TextBox();
this.userAgentLabel = new System.Windows.Forms.Label();
this.gzipOption = new System.Windows.Forms.RadioButton();
this.deflateOption = new System.Windows.Forms.RadioButton();
this.label1 = new System.Windows.Forms.Label();
this.evaluateButton = new System.Windows.Forms.Button();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.resultsList = new System.Windows.Forms.ListBox();
this.groupBox1.SuspendLayout();
this.groupBox2.SuspendLayout();
this.SuspendLayout();
//
// statusBar
//
this.statusBar.Location = new System.Drawing.Point(0, 247);
this.statusBar.Name = "statusBar";
this.statusBar.Size = new System.Drawing.Size(492, 22);
this.statusBar.TabIndex = 0;
this.statusBar.Text = "OK";
//
// urlToHit
//
this.urlToHit.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.urlToHit.Location = new System.Drawing.Point(8, 32);
this.urlToHit.Name = "urlToHit";
this.urlToHit.Size = new System.Drawing.Size(376, 21);
this.urlToHit.TabIndex = 1;
this.urlToHit.Text = "http://";
//
// urlLabel
//
this.urlLabel.Location = new System.Drawing.Point(8, 16);
this.urlLabel.Name = "urlLabel";
this.urlLabel.Size = new System.Drawing.Size(120, 16);
this.urlLabel.TabIndex = 2;
this.urlLabel.Text = "URL to Check:";
//
// groupBox1
//
this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.groupBox1.Controls.Add(this.userAgentToUse);
this.groupBox1.Controls.Add(this.userAgentLabel);
this.groupBox1.Controls.Add(this.gzipOption);
this.groupBox1.Controls.Add(this.deflateOption);
this.groupBox1.Controls.Add(this.label1);
this.groupBox1.Controls.Add(this.urlLabel);
this.groupBox1.Controls.Add(this.urlToHit);
this.groupBox1.Controls.Add(this.evaluateButton);
this.groupBox1.Location = new System.Drawing.Point(8, 8);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(476, 136);
this.groupBox1.TabIndex = 3;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "Connection Options";
//
// userAgentToUse
//
this.userAgentToUse.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.userAgentToUse.Location = new System.Drawing.Point(224, 64);
this.userAgentToUse.Name = "userAgentToUse";
this.userAgentToUse.Size = new System.Drawing.Size(160, 21);
this.userAgentToUse.TabIndex = 6;
this.userAgentToUse.Text = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT)";
//
// userAgentLabel
//
this.userAgentLabel.Location = new System.Drawing.Point(152, 64);
this.userAgentLabel.Name = "userAgentLabel";
this.userAgentLabel.Size = new System.Drawing.Size(64, 16);
this.userAgentLabel.TabIndex = 7;
this.userAgentLabel.Text = "User Agent";
//
// gzipOption
//
this.gzipOption.Checked = true;
this.gzipOption.Location = new System.Drawing.Point(24, 104);
this.gzipOption.Name = "gzipOption";
this.gzipOption.TabIndex = 5;
this.gzipOption.TabStop = true;
this.gzipOption.Text = "GZip";
//
// deflateOption
//
this.deflateOption.Location = new System.Drawing.Point(24, 80);
this.deflateOption.Name = "deflateOption";
this.deflateOption.TabIndex = 4;
this.deflateOption.Tag = "deflate";
this.deflateOption.Text = "Deflate";
//
// label1
//
this.label1.Location = new System.Drawing.Point(8, 64);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(144, 23);
this.label1.TabIndex = 3;
this.label1.Text = "Compression Algorithm:";
//
// evaluateButton
//
this.evaluateButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.evaluateButton.Location = new System.Drawing.Point(392, 32);
this.evaluateButton.Name = "evaluateButton";
this.evaluateButton.TabIndex = 4;
this.evaluateButton.Text = "Fetch!";
this.evaluateButton.Click += new System.EventHandler(this.evaluateButton_Click);
//
// groupBox2
//
this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.groupBox2.Controls.Add(this.resultsList);
this.groupBox2.Location = new System.Drawing.Point(8, 160);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(476, 84);
this.groupBox2.TabIndex = 4;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "Results";
//
// resultsList
//
this.resultsList.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.resultsList.Location = new System.Drawing.Point(8, 16);
this.resultsList.Name = "resultsList";
this.resultsList.Size = new System.Drawing.Size(456, 56);
this.resultsList.TabIndex = 0;
//
// MainForm
//
this.AcceptButton = this.evaluateButton;
this.AutoScaleBaseSize = new System.Drawing.Size(5, 14);
this.ClientSize = new System.Drawing.Size(492, 269);
this.Controls.Add(this.groupBox2);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.statusBar);
this.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.MinimumSize = new System.Drawing.Size(500, 300);
this.Name = "MainForm";
this.Text = "Compression Checker";
this.groupBox1.ResumeLayout(false);
this.groupBox2.ResumeLayout(false);
this.ResumeLayout(false);

}
#endregion

private void evaluateButton_Click(object sender, System.EventArgs e) {
Uri url;
try {
url = new Uri(urlToHit.Text);
} catch(UriFormatException) {
statusBar.Text = "Invalid URL!";
return;
}
statusBar.Text = "Fetching uncompressed...";

RunInfo[] results = new RunInfo[2];

results[0] = FetchUncompressedUrl(url);
statusBar.Text = "Fetching compressed...";

if(deflateOption.Checked)
results[1] = FetchCompressedUrl(url, "deflate");
else
results[1] = FetchCompressedUrl(url, "gzip");

resultsList.DataSource = results;

statusBar.Text = "Done!";

}

RunInfo FetchUncompressedUrl(Uri url) {
RunInfo ri = new RunInfo();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.UserAgent = userAgentToUse.Text;
DateTime start = DateTime.Now;
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
ri.TimeToFirstByte = DateTime.Now - start;
ri.BytesReceived = DumpStream(response.GetResponseStream(), Stream.Null);
ri.TimeToLastByte = DateTime.Now - start;
return ri;
}

RunInfo FetchCompressedUrl(Uri url, string algo) {
RunInfo ri = new RunInfo(algo);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Headers["Accept-Encoding"] = algo;
request.UserAgent = userAgentToUse.Text;
DateTime start = DateTime.Now;
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
ri.TimeToFirstByte = DateTime.Now - start;
ri.BytesReceived = DumpStream(response.GetResponseStream(), Stream.Null);
ri.TimeToLastByte = DateTime.Now - start;
return ri;
}

static long DumpStream(Stream s, Stream output) {

byte[] buffer = new byte[1024];
long count = 0;
int read = 0;
while((read = s.Read(buffer, 0, 1024)) > 0) {
count += read;
output.Write(buffer, 0, read);
}
return count;
}

class RunInfo {
public long BytesReceived = -1;
public TimeSpan TimeToFirstByte = TimeSpan.Zero;
public TimeSpan TimeToLastByte = TimeSpan.Zero;
public TimeSpan TransitTime { get { return TimeToLastByte - TimeToFirstByte; } }
public string Algorithm = "none";

public RunInfo() {
}

public RunInfo(string algo) {
Algorithm = algo;
}

public override string ToString() {
return string.Format("{0}; {1} bytes; TTFB={2}ms; TTLB={3}ms; Transit={4}ms", Algorithm, BytesReceived, TimeToFirstByte.TotalMilliseconds, TimeToLastByte.TotalMilliseconds, TransitTime.TotalMilliseconds);
}

}
}
}
256 changes: 256 additions & 0 deletions Fetch/MainForm.resx
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used forserialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="statusBar.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="statusBar.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="statusBar.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="urlToHit.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="urlToHit.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="urlToHit.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="urlLabel.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="urlLabel.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="urlLabel.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="groupBox1.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="groupBox1.GridSize" type="System.Drawing.Size, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>8, 8</value>
</data>
<data name="groupBox1.SnapToGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</data>
<data name="groupBox1.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="groupBox1.DrawGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</data>
<data name="groupBox1.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="userAgentToUse.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="userAgentToUse.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="userAgentToUse.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="userAgentLabel.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="userAgentLabel.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="userAgentLabel.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="gzipOption.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="gzipOption.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="gzipOption.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="deflateOption.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="deflateOption.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="deflateOption.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="label1.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="label1.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="label1.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="evaluateButton.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="evaluateButton.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="evaluateButton.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="groupBox2.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="groupBox2.GridSize" type="System.Drawing.Size, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>8, 8</value>
</data>
<data name="groupBox2.SnapToGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</data>
<data name="groupBox2.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="groupBox2.DrawGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</data>
<data name="groupBox2.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="resultsList.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="resultsList.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="resultsList.Modifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
<data name="$this.Locked" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="$this.Language" type="System.Globalization.CultureInfo, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>(Default)</value>
</data>
<data name="$this.TrayLargeIcon" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="$this.Localizable" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</data>
<data name="$this.GridSize" type="System.Drawing.Size, System.Drawing, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>8, 8</value>
</data>
<data name="$this.DrawGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</data>
<data name="$this.TrayHeight" type="System.Int32, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>80</value>
</data>
<data name="$this.Name">
<value>MainForm</value>
</data>
<data name="$this.SnapToGrid" type="System.Boolean, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</data>
<data name="$this.DefaultModifiers" type="System.CodeDom.MemberAttributes, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>Private</value>
</data>
</root>
46 changes: 46 additions & 0 deletions HttpCompress.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Microsoft Visual Studio Solution File, Format Version 9.00
# Visual Studio 2005
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "example\Example.csproj", "{621AAC3C-38EC-4F70-80D3-68DE4829AA33}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fetch", "Fetch\Fetch.csproj", "{3EFC307A-7AA7-4566-8517-BD80263C2830}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{758C0F47-99BF-43B2-8C91-C94CAEC72DAC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HttpCompress", "HttpCompress\HttpCompress.csproj", "{24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Production|Any CPU = Production|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Debug|Any CPU.Build.0 = Debug|Any CPU
{621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Production|Any CPU.ActiveCfg = Production|Any CPU
{621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Production|Any CPU.Build.0 = Production|Any CPU
{621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Release|Any CPU.ActiveCfg = Release|Any CPU
{621AAC3C-38EC-4F70-80D3-68DE4829AA33}.Release|Any CPU.Build.0 = Release|Any CPU
{3EFC307A-7AA7-4566-8517-BD80263C2830}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3EFC307A-7AA7-4566-8517-BD80263C2830}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3EFC307A-7AA7-4566-8517-BD80263C2830}.Production|Any CPU.ActiveCfg = Production|Any CPU
{3EFC307A-7AA7-4566-8517-BD80263C2830}.Production|Any CPU.Build.0 = Production|Any CPU
{3EFC307A-7AA7-4566-8517-BD80263C2830}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3EFC307A-7AA7-4566-8517-BD80263C2830}.Release|Any CPU.Build.0 = Release|Any CPU
{758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Production|Any CPU.ActiveCfg = Production|Any CPU
{758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Production|Any CPU.Build.0 = Production|Any CPU
{758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{758C0F47-99BF-43B2-8C91-C94CAEC72DAC}.Release|Any CPU.Build.0 = Release|Any CPU
{24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Production|Any CPU.ActiveCfg = Production|Any CPU
{24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Production|Any CPU.Build.0 = Production|Any CPU
{24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
115 changes: 115 additions & 0 deletions HttpCompress/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// A Better AssemblyInfo.cs based on better practices.
//
// This little guy sets up all the assembly level attributes
// on the assembly that you're compiling
//
// This version heavily influenced (read copied) from Jeffery Richter's
// Applied Microsoft.NET Framework Programming (pg. 93)

using System.Reflection;

#region Company Info

// Set the CompanyName, LegalCopyright, and LegalTrademark fields
// You shouldn't have to mess with these
[assembly: AssemblyCompany("blowery.org")]
[assembly: AssemblyCopyright("Copyright (c) 2003-Present Ben Lowery")]

#endregion

#region Product Info

// Set the ProductName and ProductVersion fields
// AssemblyProduct should be a descriptive name for the product that includes this assembly
// If this is a generic library that's going to be used by a whole bunch of stuff,
// just make the name the same as the Assembly's name. This isn't really used by
// anything except Windows Explorer's properties tab, so it's not a big deal if you mess it up
// See pg. 60 of Richter for more info.
[assembly: AssemblyProduct("HttpCompress")]
#endregion

#region Version and Identity Info

// AssemblyInformationalVersion should be the version of the product that is including this
// assembly. Again, if this assembly isn't being included in a "product", just make this
// the same as the FileVersion and be done with it. See pg 60 of Richter for more info.
[assembly: AssemblyInformationalVersion("6.0")]

// Set the FileVersion, AssemblyVersion, FileDescription, and Comments fields
// AssemblyFileVersion is stored in the Win32 version resource. It's ignored by the CLR,
// we typically use it to store the build and revision bits every time a build
// is performed. Unfortunately, the c# compiler doesn't do this automatically,
// so we'll have to work out another way to do it.
[assembly: AssemblyFileVersion("6.0")]

// AssemblyVersion is the version used by the CLR as part of the strong name for
// an assembly. You don't really want to mess with this unless you're
// making changes that add / remove / change functionality within the library.
// For bugfixes, don't mess with this.
//
// Do this:
// [assembly: AssemblyVersion("1.0.0.0")]
//
// Don't do this:
// [assembly: AssemblyVersion("1.0.*.*")]
// as it breaks all the other assemblies that reference this one every time
// you build the project.
[assembly: AssemblyVersion("6.0")]

// Title is just for inspection utilities and isn't really used for much
// Generally just set this to the name of the file containing the
// manifest, sans extension. I.E. for BensLibrary.dll, name it BensLibrary
[assembly: AssemblyTitle("HttpCompress")]

// Description is just for inspection utilities and isn't really that important.
// It's generally a good idea to write a decent description so that you
// don't end up looking like a tool when your stuff shows up in an inspector.
[assembly: AssemblyDescription("An HttpModule that compresses the output stream!")]

#endregion

#region Culture Info

// Set the assembly's culture affinity. Generally don't want to set this
// unless you're building an resource only assembly. Assemblies that contain
// code should always be culture neutral
[assembly: AssemblyCulture("")]

#endregion

#region Assembly Signing Info

#if !StronglyNamedAssembly

// Weakly named assemblies are never signed
[assembly: AssemblyDelaySign(false)]

#else

// Strongly named assemblies are usually delay signed while building and
// completely signed using sn.exe's -R or -Rc switch

#if !SignedUsingACryptoServiceProvider

// Give the name of the file that contains the public/private key pair.
// If delay signing, only the public key is used
[assembly: AssemblyKeyFile(THE_KEY_FILE_RELATIVE_TO_THIS_FILE)]

// Note: If AssemblyKeyFile and AssemblyKeyName are both specified,
// here's what happens...
// 1) If the container exists, the key file is ignored
// 2) If the container doesn't exist, the keys from the key
// file are copied in the container and the assembly is signed

#else

// Give the name of the cryptographic service provider (CSP) container
// that contains the public/private key pair
// If delay signing, only the public key is used
[assembly: AssemblyKeyName("blowery.org")]

#endif

#endif

#endregion
67 changes: 67 additions & 0 deletions HttpCompress/CompressingFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.IO;
using System.Web;

namespace blowery.Web.HttpCompress {
/// <summary>
/// Base for any HttpFilter that performing compression
/// </summary>
/// <remarks>
/// When implementing this class, you need to implement a <see cref="HttpOutputFilter"/>
/// along with a <see cref="CompressingFilter.ContentEncoding"/>. The latter corresponds to a
/// content coding (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5)
/// that your implementation will support.
/// </remarks>
public abstract class CompressingFilter : HttpOutputFilter {

private bool hasWrittenHeaders = false;

/// <summary>
/// Protected constructor that sets up the underlying stream we're compressing into
/// </summary>
/// <param name="baseStream">The stream we're wrapping up</param>
/// <param name="compressionLevel">The level of compression to use when compressing the content</param>
protected CompressingFilter(Stream baseStream, CompressionLevels compressionLevel) : base(baseStream) {
_compressionLevel = compressionLevel;
}

/// <summary>
/// The name of the content-encoding that's being implemented
/// </summary>
/// <remarks>
/// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5 for more
/// details on content codings.
/// </remarks>
public abstract string ContentEncoding { get; }

private CompressionLevels _compressionLevel;

/// <summary>
/// Allow inheriting classes to get access the the level of compression that should be used
/// </summary>
protected CompressionLevels CompressionLevel {
get { return _compressionLevel; }
}

/// <summary>
/// Keeps track of whether or not we're written the compression headers
/// </summary>
protected bool HasWrittenHeaders {
get { return hasWrittenHeaders; }
}

/// <summary>
/// Writes out the compression-related headers. Subclasses should call this once before writing to the output stream.
/// </summary>
protected void WriteHeaders() {
// this is dangerous. if Response.End is called before the filter is used, directly or indirectly,
// the content will not pass through the filter. However, this header will still be appended.
// Look for handling cases in PreRequestSendHeaders and Pre
HttpContext.Current.Response.AppendHeader("Content-Encoding", this.ContentEncoding);
HttpContext.Current.Response.AppendHeader("X-Compressed-By", "HttpCompress");
hasWrittenHeaders = true;
}


}
}
66 changes: 66 additions & 0 deletions HttpCompress/DeflateFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.IO;

using System.IO.Compression;

namespace blowery.Web.HttpCompress {
/// <summary>
/// Summary description for DeflateFilter.
/// </summary>
public class DeflateFilter : CompressingFilter {

/// <summary>
/// compression stream member
/// has to be a member as we can only have one instance of the
/// actual filter class
/// </summary>
private DeflateStream m_stream = null;

/// <summary>
/// Basic constructor that uses the Normal compression level
/// </summary>
/// <param name="baseStream">The stream to wrap up with the deflate algorithm</param>
public DeflateFilter(Stream baseStream) : this(baseStream, CompressionLevels.Normal) { }

/// <summary>
/// Full constructor that allows you to set the wrapped stream and the level of compression
/// </summary>
/// <param name="baseStream">The stream to wrap up with the deflate algorithm</param>
/// <param name="compressionLevel">The level of compression to use</param>
public DeflateFilter(Stream baseStream, CompressionLevels compressionLevel) : base(baseStream, compressionLevel) {
m_stream = new DeflateStream(baseStream, CompressionMode.Compress);
}

/// <summary>
/// Write out bytes to the underlying stream after compressing them using deflate
/// </summary>
/// <param name="buffer">The array of bytes to write</param>
/// <param name="offset">The offset into the supplied buffer to start</param>
/// <param name="count">The number of bytes to write</param>
public override void Write(byte[] buffer, int offset, int count) {
if(!HasWrittenHeaders) WriteHeaders();
m_stream.Write(buffer, offset, count);
}

/// <summary>
/// Return the Http name for this encoding. Here, deflate.
/// </summary>
public override string ContentEncoding {
get { return "deflate"; }
}

/// <summary>
/// Closes this Filter and calls the base class implementation.
/// </summary>
public override void Close() {
m_stream.Close();
}

/// <summary>
/// Flushes that the filter out to underlying storage
/// </summary>
public override void Flush() {
m_stream.Flush();
}
}
}
42 changes: 42 additions & 0 deletions HttpCompress/Enums.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace blowery.Web.HttpCompress
{
/// <summary>
/// The available compression algorithms to use with the HttpCompressionModule
/// </summary>
public enum Algorithms {
/// <summary>Use the Deflate algorithm</summary>
Deflate,
/// <summary>Use the GZip algorithm</summary>
GZip,
/// <summary>Use the default algorithm (picked by client)</summary>
Default=-1
}

/// <summary>
/// The level of compression to use with deflate
/// </summary>
public enum CompressionLevels {
/// <summary>Use the default compression level</summary>
Default = -1,
/// <summary>The highest level of compression. Also the slowest.</summary>
Highest = 9,
/// <summary>A higher level of compression.</summary>
Higher = 8,
/// <summary>A high level of compression.</summary>
High = 7,
/// <summary>More compression.</summary>
More = 6,
/// <summary>Normal compression.</summary>
Normal = 5,
/// <summary>Less than normal compression.</summary>
Less = 4,
/// <summary>A low level of compression.</summary>
Low = 3,
/// <summary>A lower level of compression.</summary>
Lower = 2,
/// <summary>The lowest level of compression that still performs compression.</summary>
Lowest = 1,
/// <summary>No compression. Use this is you are quite silly.</summary>
None = 0
}
}
66 changes: 66 additions & 0 deletions HttpCompress/GZipFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.IO;

using System.Text;
using System.Diagnostics;

using System.IO.Compression;

namespace blowery.Web.HttpCompress {
/// <summary>
/// This is a little filter to support HTTP compression using GZip
/// </summary>
public class GZipFilter : CompressingFilter {

/// <summary>
/// compression stream member
/// has to be a member as we can only have one instance of the
/// actual filter class
/// </summary>
private GZipStream m_stream = null;

/// <summary>
/// Primary constructor. Need to pass in a stream to wrap up with gzip.
/// </summary>
/// <param name="baseStream">The stream to wrap in gzip. Must have CanWrite.</param>
public GZipFilter(Stream baseStream) : base(baseStream, CompressionLevels.Normal) {
m_stream = new GZipStream(baseStream, CompressionMode.Compress);
}

/// <summary>
/// Write content to the stream and have it compressed using gzip.
/// </summary>
/// <param name="buffer">The bytes to write</param>
/// <param name="offset">The offset into the buffer to start reading bytes</param>
/// <param name="count">The number of bytes to write</param>
public override void Write(byte[] buffer, int offset, int count) {
if(!HasWrittenHeaders) WriteHeaders();
m_stream.Write(buffer, offset, count);
}

/// <summary>
/// The Http name of this encoding. Here, gzip.
/// </summary>
public override string ContentEncoding {
get { return "gzip"; }
}

/// <summary>
/// Closes this Filter and calls the base class implementation.
/// </summary>
public override void Close() {
m_stream.Close(); // this will close the gzip stream along with the underlying stream
// no need for call to base.Close() here.
}

/// <summary>
/// Flushes the stream out to underlying storage
/// </summary>
public override void Flush() {
m_stream.Flush();
}



}
}
146 changes: 146 additions & 0 deletions HttpCompress/HttpCompress.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectType>Local</ProjectType>
<ProductVersion>8.0.50727</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}</ProjectGuid>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ApplicationIcon>
</ApplicationIcon>
<AssemblyKeyContainerName>
</AssemblyKeyContainerName>
<AssemblyName>blowery.Web.HttpCompress</AssemblyName>
<AssemblyOriginatorKeyFile>
</AssemblyOriginatorKeyFile>
<DefaultClientScript>JScript</DefaultClientScript>
<DefaultHTMLPageLayout>Grid</DefaultHTMLPageLayout>
<DefaultTargetSchema>IE50</DefaultTargetSchema>
<DelaySign>false</DelaySign>
<OutputType>Library</OutputType>
<RootNamespace>blowery.Web.HttpCompress</RootNamespace>
<RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
<StartupObject>
</StartupObject>
<FileUpgradeFlags>
</FileUpgradeFlags>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<OutputPath>bin\Debug\</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DocumentationFile>blowery.Web.HttpCompress.xml</DocumentationFile>
<DebugSymbols>true</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>
<NoWarn>
</NoWarn>
<Optimize>false</Optimize>
<RegisterForComInterop>false</RegisterForComInterop>
<RemoveIntegerChecks>false</RemoveIntegerChecks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel>
<DebugType>full</DebugType>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<OutputPath>bin\Release\</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>TRACE</DefineConstants>
<DocumentationFile>blowery.Web.HttpCompress.xml</DocumentationFile>
<DebugSymbols>false</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>
<NoWarn>
</NoWarn>
<Optimize>true</Optimize>
<RegisterForComInterop>false</RegisterForComInterop>
<RemoveIntegerChecks>false</RemoveIntegerChecks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel>
<DebugType>none</DebugType>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Production|AnyCPU' ">
<OutputPath>bin\Production\</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>TRACE,StronglyNamedAssembly,SignedUsingACryptoServiceProvider</DefineConstants>
<DocumentationFile>blowery.Web.HttpCompress.xml</DocumentationFile>
<DebugSymbols>false</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>
<NoWarn>
</NoWarn>
<Optimize>true</Optimize>
<RegisterForComInterop>false</RegisterForComInterop>
<RemoveIntegerChecks>false</RemoveIntegerChecks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel>
<DebugType>none</DebugType>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<Reference Include="System">
<Name>System</Name>
</Reference>
<Reference Include="System.Data">
<Name>System.Data</Name>
</Reference>
<Reference Include="System.Web">
<Name>System.Web</Name>
</Reference>
<Reference Include="System.Xml">
<Name>System.XML</Name>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="CompressingFilter.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="DeflateFilter.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Enums.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="GZipFilter.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="HttpModule.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="HttpOutputFilter.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="SectionHandler.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Settings.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
</Project>
237 changes: 237 additions & 0 deletions HttpCompress/HttpModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
using System;
using System.IO;
using System.Web;

using System.Collections;
using System.Collections.Specialized;

namespace blowery.Web.HttpCompress {
/// <summary>
/// An HttpModule that hooks onto the Response.Filter property of the
/// current request and tries to compress the output, based on what
/// the browser supports
/// </summary>
/// <remarks>
/// <p>This HttpModule uses classes that inherit from <see cref="CompressingFilter"/>.
/// We already support gzip and deflate (aka zlib), if you'd like to add
/// support for compress (which uses LZW, which is licensed), add in another
/// class that inherits from HttpFilter to do the work.</p>
///
/// <p>This module checks the Accept-Encoding HTTP header to determine if the
/// client actually supports any notion of compression. Currently, we support
/// the deflate (zlib) and gzip compression schemes. I chose to not implement
/// compress because it uses lzw which requires a license from
/// Unisys. For more information about the common compression types supported,
/// see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 for details.</p>
/// </remarks>
/// <seealso cref="CompressingFilter"/>
/// <seealso cref="Stream"/>
public sealed class HttpModule : IHttpModule {

const string INSTALLED_KEY = "httpcompress.attemptedinstall";
static readonly object INSTALLED_TAG = new object();

/// <summary>
/// Init the handler and fulfill <see cref="IHttpModule"/>
/// </summary>
/// <remarks>
/// This implementation hooks the ReleaseRequestState and PreSendRequestHeaders events to
/// figure out as late as possible if we should install the filter. Previous versions did
/// not do this as well.
/// </remarks>
/// <param name="context">The <see cref="HttpApplication"/> this handler is working for.</param>
void IHttpModule.Init(HttpApplication context) {
context.ReleaseRequestState += new EventHandler(this.CompressContent);
context.PreSendRequestHeaders += new EventHandler(this.CompressContent);
}

/// <summary>
/// Implementation of <see cref="IHttpModule"/>
/// </summary>
/// <remarks>
/// Currently empty. Nothing to really do, as I have no member variables.
/// </remarks>
void IHttpModule.Dispose() { }

/// <summary>
/// EventHandler that gets ahold of the current request context and attempts to compress the output.
/// </summary>
/// <param name="sender">The <see cref="HttpApplication"/> that is firing this event.</param>
/// <param name="e">Arguments to the event</param>
void CompressContent(object sender, EventArgs e) {

HttpApplication app = (HttpApplication)sender;

// only do this if we havn't already attempted an install. This prevents PreSendRequestHeaders from
// trying to add this item way to late. We only want the first run through to do anything.
// also, we use the context to store whether or not we've attempted an add, as it's thread-safe and
// scoped to the request. An instance of this module can service multiple requests at the same time,
// so we cannot use a member variable.
if(!app.Context.Items.Contains(INSTALLED_KEY)) {

// log the install attempt in the HttpContext
// must do this first as several IF statements
// below skip full processing of this method
app.Context.Items.Add(INSTALLED_KEY, INSTALLED_TAG);

// get the config settings
Settings settings = Settings.GetSettings();

if(settings.CompressionLevel == CompressionLevels.None){
// skip if the CompressionLevel is set to 'None'
return;
}

string realPath = app.Request.Path.Remove(0, app.Request.ApplicationPath.Length+1);
if(settings.IsExcludedPath(realPath)){
// skip if the file path excludes compression
return;
}

if(settings.IsExcludedMimeType(app.Response.ContentType)){
// skip if the MimeType excludes compression
return;
}

// fix to handle caching appropriately
// see http://www.pocketsoap.com/weblog/2003/07/1330.html
// Note, this header is added only when the request
// has the possibility of being compressed...
// i.e. it is not added when the request is excluded from
// compression by CompressionLevel, Path, or MimeType
app.Response.Cache.VaryByHeaders["Accept-Encoding"] = true;

// grab an array of algorithm;q=x, algorith;q=x style values
string acceptedTypes = app.Request.Headers["Accept-Encoding"];
// if we couldn't find the header, bail out
if(acceptedTypes == null){
return;
}

// the actual types could be , delimited. split 'em out.
string[] types = acceptedTypes.Split(',');

CompressingFilter filter = GetFilterForScheme(types, app.Response.Filter, settings);

if(filter == null){
// if we didn't find a filter, bail out
return;
}

// if we get here, we found a viable filter.
// set the filter and change the Content-Encoding header to match so the client can decode the response
app.Response.Filter = filter;

}
}

/// <summary>
/// Get ahold of a <see cref="CompressingFilter"/> for the given encoding scheme.
/// If no encoding scheme can be found, it returns null.
/// </summary>
/// <remarks>
/// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3 for details
/// on how clients are supposed to construct the Accept-Encoding header. This
/// implementation follows those rules, though we allow the server to override
/// the preference given to different supported algorithms. I'm doing this as
/// I would rather give the server control over the algorithm decision than
/// the client. If the clients send up * as an accepted encoding with highest
/// quality, we use the preferred algorithm as specified in the config file.
/// </remarks>
public static CompressingFilter GetFilterForScheme(string[] schemes, Stream output, Settings prefs) {
bool foundDeflate = false;
bool foundGZip = false;
bool foundStar = false;

float deflateQuality = 0f;
float gZipQuality = 0f;
float starQuality = 0f;

bool isAcceptableDeflate;
bool isAcceptableGZip;
bool isAcceptableStar;

for (int i = 0; i<schemes.Length;i++) {
string acceptEncodingValue = schemes[i].Trim().ToLower();

if (acceptEncodingValue.StartsWith("deflate")) {
foundDeflate = true;

float newDeflateQuality = GetQuality(acceptEncodingValue);
if (deflateQuality < newDeflateQuality)
deflateQuality = newDeflateQuality;
}

else if (acceptEncodingValue.StartsWith("gzip") || acceptEncodingValue.StartsWith("x-gzip")) {
foundGZip = true;

float newGZipQuality = GetQuality(acceptEncodingValue);
if (gZipQuality < newGZipQuality)
gZipQuality = newGZipQuality;
}

else if (acceptEncodingValue.StartsWith("*")) {
foundStar = true;

float newStarQuality = GetQuality(acceptEncodingValue);
if (starQuality < newStarQuality)
starQuality = newStarQuality;
}
}

isAcceptableStar = foundStar && (starQuality > 0);
isAcceptableDeflate = (foundDeflate && (deflateQuality > 0)) || (!foundDeflate && isAcceptableStar);
isAcceptableGZip = (foundGZip && (gZipQuality > 0)) || (!foundGZip && isAcceptableStar);

if (isAcceptableDeflate && !foundDeflate)
deflateQuality = starQuality;

if (isAcceptableGZip && !foundGZip)
gZipQuality = starQuality;


// do they support any of our compression methods?
if(!(isAcceptableDeflate || isAcceptableGZip || isAcceptableStar)) {
return null;
}


// if deflate is better according to client
if (isAcceptableDeflate && (!isAcceptableGZip || (deflateQuality > gZipQuality)))
return new DeflateFilter(output, prefs.CompressionLevel);

// if gzip is better according to client
if (isAcceptableGZip && (!isAcceptableDeflate || (deflateQuality < gZipQuality)))
return new GZipFilter(output);

// if we're here, the client either didn't have a preference or they don't support compression
if(isAcceptableDeflate && (prefs.PreferredAlgorithm == Algorithms.Deflate || prefs.PreferredAlgorithm == Algorithms.Default))
return new DeflateFilter(output, prefs.CompressionLevel);
if(isAcceptableGZip && prefs.PreferredAlgorithm == Algorithms.GZip)
return new GZipFilter(output);

if(isAcceptableDeflate || isAcceptableStar)
return new DeflateFilter(output, prefs.CompressionLevel);
if(isAcceptableGZip)
return new GZipFilter(output);

// return null. we couldn't find a filter.
return null;
}

static float GetQuality(string acceptEncodingValue) {
int qParam = acceptEncodingValue.IndexOf("q=");

if (qParam >= 0) {
float val = 0.0f;
try {
val = float.Parse(acceptEncodingValue.Substring(qParam+2, acceptEncodingValue.Length - (qParam+2)));
} catch(FormatException) {

}
return val;
} else
return 1;
}
}
}
119 changes: 119 additions & 0 deletions HttpCompress/HttpOutputFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using System;
using System.IO;

namespace blowery.Web.HttpCompress {
/// <summary>
/// The base of anything you want to latch onto the Filter property of a <see cref="System.Web.HttpResponse"/>
/// object.
/// </summary>
/// <remarks>
/// <p></p>These are generally used with <see cref="HttpModule"/> but you could really use them in
/// other HttpModules. This is a general, write-only stream that writes to some underlying stream. When implementing
/// a real class, you have to override void Write(byte[], int offset, int count). Your work will be performed there.
/// </remarks>
public abstract class HttpOutputFilter : Stream {

private Stream _sink;

/// <summary>
/// Subclasses need to call this on contruction to setup the underlying stream
/// </summary>
/// <param name="baseStream">The stream we're wrapping up in a filter</param>
protected HttpOutputFilter(Stream baseStream) {
_sink = baseStream;
}

/// <summary>
/// Allow subclasses access to the underlying stream
/// </summary>
protected Stream BaseStream {
get{ return _sink; }
}

/// <summary>
/// False. These are write-only streams
/// </summary>
public override bool CanRead {
get { return false; }
}

/// <summary>
/// False. These are write-only streams
/// </summary>
public override bool CanSeek {
get { return false; }
}

/// <summary>
/// True. You can write to the stream. May change if you call Close or Dispose
/// </summary>
public override bool CanWrite {
get { return _sink.CanWrite; }
}

/// <summary>
/// Not supported. Throws an exception saying so.
/// </summary>
/// <exception cref="NotSupportedException">Thrown. Always.</exception>
public override long Length {
get { throw new NotSupportedException(); }
}

/// <summary>
/// Not supported. Throws an exception saying so.
/// </summary>
/// <exception cref="NotSupportedException">Thrown. Always.</exception>
public override long Position {
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}

/// <summary>
/// Not supported. Throws an exception saying so.
/// </summary>
/// <exception cref="NotSupportedException">Thrown. Always.</exception>
public override long Seek(long offset, System.IO.SeekOrigin direction) {
throw new NotSupportedException();
}

/// <summary>
/// Not supported. Throws an exception saying so.
/// </summary>
/// <exception cref="NotSupportedException">Thrown. Always.</exception>
public override void SetLength(long length) {
throw new NotSupportedException();
}

/// <summary>
/// Closes this Filter and the underlying stream.
/// </summary>
/// <remarks>
/// If you override, call up to this method in your implementation.
/// </remarks>
public override void Close() {
_sink.Close();
}

/// <summary>
/// Fluses this Filter and the underlying stream.
/// </summary>
/// <remarks>
/// If you override, call up to this method in your implementation.
/// </remarks>
public override void Flush() {
_sink.Flush();
}

/// <summary>
/// Not supported.
/// </summary>
/// <param name="buffer">The buffer to write into.</param>
/// <param name="offset">The offset on the buffer to write into</param>
/// <param name="count">The number of bytes to write. Must be less than buffer.Length</param>
/// <returns>An int telling you how many bytes were written</returns>
public override int Read(byte[] buffer, int offset, int count) {
throw new NotSupportedException();
}

}
}
27 changes: 27 additions & 0 deletions HttpCompress/SectionHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Xml;
using System.Xml.Serialization;
using System.Configuration;

namespace blowery.Web.HttpCompress
{
/// <summary>
/// This class acts as a factory for the configuration settings.
/// </summary>
public sealed class SectionHandler : IConfigurationSectionHandler
{
/// <summary>
/// Create a new config section handler. This is of type <see cref="Settings"/>
/// </summary>
object IConfigurationSectionHandler.Create(object parent, object configContext, XmlNode configSection) {
Settings settings;
if(parent == null)
settings = Settings.Default;
else
settings = (Settings)parent;
settings.AddSettings(configSection);
return settings;
}
}

}
150 changes: 150 additions & 0 deletions HttpCompress/Settings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Xml;

namespace blowery.Web.HttpCompress {
/// <summary>
/// This class encapsulates the settings for an HttpCompressionModule
/// </summary>
public sealed class Settings {

private Algorithms _preferredAlgorithm;
private CompressionLevels _compressionLevel;
private StringCollection _excludedTypes;
private StringCollection _excludedPaths;

/// <summary>
/// Create an HttpCompressionModuleSettings from an XmlNode
/// </summary>
/// <param name="node">The XmlNode to configure from</param>
public Settings(XmlNode node) : this() {
AddSettings(node);
}


private Settings() {
_preferredAlgorithm = Algorithms.Default;
_compressionLevel = CompressionLevels.Default;
_excludedTypes = new StringCollection();
_excludedPaths = new StringCollection();
}

/// <summary>
/// Suck in some more changes from an XmlNode. Handy for config file parenting.
/// </summary>
/// <param name="node">The node to read from</param>
public void AddSettings(XmlNode node) {
if(node == null)
return;

XmlAttribute preferredAlgorithm = node.Attributes["preferredAlgorithm"];
if(preferredAlgorithm != null) {
try {
_preferredAlgorithm = (Algorithms)Enum.Parse(typeof(Algorithms), preferredAlgorithm.Value, true);
}catch(ArgumentException) { }
}

XmlAttribute compressionLevel = node.Attributes["compressionLevel"];
if(compressionLevel != null) {
try {
_compressionLevel = (CompressionLevels)Enum.Parse(typeof(CompressionLevels), compressionLevel.Value, true);
}catch(ArgumentException) { }
}

ParseExcludedTypes(node.SelectSingleNode("excludedMimeTypes"));
ParseExcludedPaths(node.SelectSingleNode("excludedPaths"));

}


/// <summary>
/// Get the current settings from the xml config file
/// </summary>
public static Settings GetSettings() {
Settings settings = (Settings)System.Configuration.ConfigurationSettings.GetConfig("blowery.web/httpCompress");
if(settings == null)
return Settings.Default;
else
return settings;
}

/// <summary>
/// The default settings. Deflate + normal.
/// </summary>
public static Settings Default {
get { return new Settings(); }
}

/// <summary>
/// The preferred algorithm to use for compression
/// </summary>
public Algorithms PreferredAlgorithm {
get { return _preferredAlgorithm; }
}


/// <summary>
/// The preferred compression level
/// </summary>
public CompressionLevels CompressionLevel {
get { return _compressionLevel; }
}


/// <summary>
/// Checks a given mime type to determine if it has been excluded from compression
/// </summary>
/// <param name="mimetype">The MimeType to check. Can include wildcards like image/* or */xml.</param>
/// <returns>true if the mime type passed in is excluded from compression, false otherwise</returns>
public bool IsExcludedMimeType(string mimetype) {
return _excludedTypes.Contains(mimetype.ToLower());
}

/// <summary>
/// Looks for a given path in the list of paths excluded from compression
/// </summary>
/// <param name="relUrl">the relative url to check</param>
/// <returns>true if excluded, false if not</returns>
public bool IsExcludedPath(string relUrl) {
return _excludedPaths.Contains(relUrl.ToLower());
}

private void ParseExcludedTypes(XmlNode node) {
if(node == null) return;

for(int i = 0; i < node.ChildNodes.Count; ++i) {
switch(node.ChildNodes[i].LocalName) {
case "add":
if(node.ChildNodes[i].Attributes["type"] != null)
_excludedTypes.Add(node.ChildNodes[i].Attributes["type"].Value.ToLower());
break;
case "delete":
if(node.ChildNodes[i].Attributes["type"] != null)
_excludedTypes.Remove(node.ChildNodes[i].Attributes["type"].Value.ToLower());
break;
}
}
}

private void ParseExcludedPaths(XmlNode node) {
if(node == null) return;

for(int i = 0; i < node.ChildNodes.Count; ++i) {
switch(node.ChildNodes[i].LocalName) {
case "add":
if(node.ChildNodes[i].Attributes["path"] != null)
_excludedPaths.Add(node.ChildNodes[i].Attributes["path"].Value.ToLower());
break;
case "delete":
if(node.ChildNodes[i].Attributes["path"] != null)
_excludedPaths.Remove(node.ChildNodes[i].Attributes["path"].Value.ToLower());
break;
}
}
}

}

}

121 changes: 121 additions & 0 deletions Tests/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// A Better AssemblyInfo.cs based on better practices.
//
// This little guy sets up all the assembly level attributes
// on the assembly that you're compiling
//
// This version heavily influenced (read copied) from Jeffery Richter's
// Applied Microsoft.NET Framework Programming (pg. 93)

using System.Reflection;

#region Company Info

// Set the CompanyName, LegalCopyright, and LegalTrademark fields
// You shouldn't have to mess with these
[assembly: AssemblyCompany("blowery.org")]
[assembly: AssemblyCopyright("Copyright (c) 2003 Ben Lowery")]

#endregion

#region Product Info

// Set the ProductName and ProductVersion fields
// AssemblyProduct should be a descriptive name for the product that includes this assembly
// If this is a generic library that's going to be used by a whole bunch of stuff,
// just make the name the same as the Assembly's name. This isn't really used by
// anything except Windows Explorer's properties tab, so it's not a big deal if you mess it up
// See pg. 60 of Richter for more info.
[assembly: AssemblyProduct("HttpCompressionModule Tests")]
#endregion

#region Version and Identity Info

// AssemblyInformationalVersion should be the version of the product that is including this
// assembly. Again, if this assembly isn't being included in a "product", just make this
// the same as the FileVersion and be done with it. See pg 60 of Richter for more info.
[assembly: AssemblyInformationalVersion("6.0")]

// Set the FileVersion, AssemblyVersion, FileDescription, and Comments fields
// AssemblyFileVersion is stored in the Win32 version resource. It's ignored by the CLR,
// we typically use it to store the build and revision bits every time a build
// is performed. Unfortunately, the c# compiler doesn't do this automatically,
// so we'll have to work out another way to do it.
[assembly: AssemblyFileVersion("6.0")]

// AssemblyVersion is the version used by the CLR as part of the strong name for
// an assembly. You don't really want to mess with this unless you're
// making changes that add / remove / change functionality within the library.
// For bugfixes, don't mess with this.
//
// Do this:
// [assembly: AssemblyVersion("1.0.0.0")]
//
// Don't do this:
// [assembly: AssemblyVersion("1.0.*.*")]
// as it breaks all the other assemblies that reference this one every time
// you build the project.
[assembly: AssemblyVersion("6.0")]

// Title is just for inspection utilities and isn't really used for much
// Generally just set this to the name of the file containing the
// manifest, sans extension. I.E. for BensLibrary.dll, name it BensLibrary
[assembly: AssemblyTitle("Tests")]

// Description is just for inspection utilities and isn't really that important.
// It's generally a good idea to write a decent description so that you
// don't end up looking like a tool when your stuff shows up in an inspector.
[assembly: AssemblyDescription("Tests for the HttpCompress")]

#endregion

#region Culture Info

// Set the assembly's culture affinity. Generally don't want to set this
// unless you're building an resource only assembly. Assemblies that contain
// code should always be culture neutral
[assembly: AssemblyCulture("")]

#endregion

#region Assembly Signing Info

#if !StronglyNamedAssembly

// Weakly named assemblies are never signed
[assembly: AssemblyDelaySign(false)]

#else

// Strongly named assemblies are usually delay signed while building and
// completely signed using sn.exe's -R or -Rc switch

#if !SignedUsingACryptoServiceProvider

// Give the name of the file that contains the public/private key pair.
// If delay signing, only the public key is used
[assembly: AssemblyKeyFile(THE_KEY_FILE_RELATIVE_TO_THIS_FILE)]

// Note: If AssemblyKeyFile and AssemblyKeyName are both specified,
// here's what happens...
// 1) If the container exists, the key file is ignored
// 2) If the container doesn't exist, the keys from the key
// file are copied in the container and the assembly is signed

#else

// Give the name of the cryptographic service provider (CSP) container
// that contains the public/private key pair
// If delay signing, only the public key is used
[assembly: AssemblyKeyName("blowery.org")]

#endif

#endif

#endregion

#region AssemblyWideConstants
class Constants {
public const string Namespace = "blowery.Web.HttpCompress.Tests";
}
#endregion
146 changes: 146 additions & 0 deletions Tests/SettingsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using System;
using System.Diagnostics;
using System.Resources;
using NUnit.Framework;

using blowery.Web.HttpCompress;

namespace blowery.Web.HttpCompress.Tests {
/// <summary>
/// Tests for the <see cref="Settings"/> class
/// </summary>
[TestFixture]
public class SettingsTests {

ResourceManager rman;

[SetUp] public void Setup() {
rman = Utility.GetResourceManager("XmlTestDocuments");
}

[Test] public void DefaultTest() {
Assertion.AssertEquals(CompressionLevels.Default, Settings.Default.CompressionLevel);
Assertion.AssertEquals(Algorithms.Default, Settings.Default.PreferredAlgorithm);
}

[Test] public void EmptyNodeTest() {
Settings setting = new Settings(MakeNode(rman.GetString("EmptyNode")));
Assertion.AssertEquals(CompressionLevels.Default, setting.CompressionLevel);
Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm);
}

[Test] public void DeflateTest() {
Settings setting = new Settings(MakeNode(rman.GetString("Deflate")));
Assertion.AssertEquals(Algorithms.Deflate, setting.PreferredAlgorithm);
Assertion.AssertEquals(CompressionLevels.Default, setting.CompressionLevel);
}

[Test] public void GZipTest() {
Settings setting = new Settings(MakeNode(rman.GetString("GZip")));
Assertion.AssertEquals(Algorithms.GZip, setting.PreferredAlgorithm);
Assertion.AssertEquals(CompressionLevels.Default, setting.CompressionLevel);
}

[Test] public void HighLevelTest() {
Settings setting = new Settings(MakeNode(rman.GetString("LevelHigh")));
Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm);
Assertion.AssertEquals(CompressionLevels.High, setting.CompressionLevel);
}

[Test] public void NormalLevelTest() {
Settings setting = new Settings(MakeNode(rman.GetString("LevelNormal")));
Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm);
Assertion.AssertEquals(CompressionLevels.Normal, setting.CompressionLevel);
}

[Test] public void LowLevelTest() {
Settings setting = new Settings(MakeNode(rman.GetString("LevelLow")));
Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm);
Assertion.AssertEquals(CompressionLevels.Low, setting.CompressionLevel);
}

[Test] public void BadLevelTest() {
Settings setting = new Settings(MakeNode(rman.GetString("BadLevel")));
Assertion.AssertEquals(CompressionLevels.Default, setting.CompressionLevel);
}

[Test] public void BadAlgorithmTest() {
Settings setting = new Settings(MakeNode(rman.GetString("BadAlgorithm")));
Assertion.AssertEquals(Algorithms.Default, setting.PreferredAlgorithm);
}

[Test]
public void AddSettingsTests() {
Settings setting = new Settings(MakeNode(rman.GetString("DeflateAndHigh")));
Assertion.AssertEquals(CompressionLevels.High, setting.CompressionLevel);
Assertion.AssertEquals(Algorithms.Deflate, setting.PreferredAlgorithm);

// test adding a null node
setting.AddSettings(null);
Assertion.AssertEquals(CompressionLevels.High, setting.CompressionLevel);
Assertion.AssertEquals(Algorithms.Deflate, setting.PreferredAlgorithm);

// test overriding algorithm
setting.AddSettings(MakeNode(rman.GetString("GZip")));
Assertion.AssertEquals(CompressionLevels.High, setting.CompressionLevel);
Assertion.AssertEquals(Algorithms.GZip, setting.PreferredAlgorithm);

// test overriding compression level
setting.AddSettings(MakeNode(rman.GetString("LevelLow")));
Assertion.AssertEquals(CompressionLevels.Low, setting.CompressionLevel);
Assertion.AssertEquals(Algorithms.GZip, setting.PreferredAlgorithm);

}

[Test] public void UserAddedExcludedType() {
Settings setting = new Settings(MakeNode(rman.GetString("ExcludeBenFoo")));
Assertion.Assert(setting.IsExcludedMimeType("ben/foo"));
}

[Test] public void UserAddedMultipleExcludedTypes() {
Settings setting = new Settings(MakeNode(rman.GetString("ExcludeMultipleTypes")));
Assertion.Assert(setting.IsExcludedMimeType("ben/foo"));
Assertion.Assert(setting.IsExcludedMimeType("image/jpeg"));
}

[Test] public void UserAddedAndRemovedType() {
Settings setting = new Settings(MakeNode(rman.GetString("AddAndRemoveSameType")));
Assertion.Assert(!setting.IsExcludedMimeType("user/silly"));
}

[Test] public void UserRemovedExcludedType() {
Settings setting = new Settings(MakeNode(rman.GetString("ExcludeMultipleTypes")));
Assertion.Assert(setting.IsExcludedMimeType("ben/foo"));
Assertion.Assert(setting.IsExcludedMimeType("image/jpeg"));

setting.AddSettings(MakeNode(rman.GetString("RemoveImageJpegExclusion")));
Assertion.Assert(setting.IsExcludedMimeType("ben/foo"));
Assertion.Assert(!setting.IsExcludedMimeType("image/jpeg"));
}

[Test] public void ChildAddsMimeExclude(){
Settings parent = Settings.Default;
parent.AddSettings(MakeNode(rman.GetString("ExcludeBenFoo")));
Assertion.Assert(parent.IsExcludedMimeType("ben/foo"));
}

[Test] public void ExcludeMultiplePaths() {
Settings setting = new Settings(MakeNode(rman.GetString("ExcludeMultiplePaths")));
Assertion.Assert(setting.IsExcludedPath("foo.aspx"));
Assertion.Assert(setting.IsExcludedPath("foo/bar.aspx"));
}

[Test] public void RemoveExistingPathExclude() {
Settings setting = new Settings(MakeNode(rman.GetString("ExcludeMultiplePaths")));
setting.AddSettings(MakeNode(rman.GetString("RemoveFooAspxExclude")));
Assertion.Assert(!setting.IsExcludedPath("foo.aspx"));
Assertion.Assert(setting.IsExcludedPath("foo/bar.aspx"));
}

private System.Xml.XmlNode MakeNode(string xml) {
System.Xml.XmlDocument dom = new System.Xml.XmlDocument();
dom.LoadXml(xml);
return dom.DocumentElement;
}
}
}
143 changes: 143 additions & 0 deletions Tests/Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectType>Local</ProjectType>
<ProductVersion>8.0.50727</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{758C0F47-99BF-43B2-8C91-C94CAEC72DAC}</ProjectGuid>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ApplicationIcon>
</ApplicationIcon>
<AssemblyKeyContainerName>
</AssemblyKeyContainerName>
<AssemblyName>blowery.Web.HttpCompress.Tests</AssemblyName>
<AssemblyOriginatorKeyFile>
</AssemblyOriginatorKeyFile>
<DefaultClientScript>JScript</DefaultClientScript>
<DefaultHTMLPageLayout>Grid</DefaultHTMLPageLayout>
<DefaultTargetSchema>IE50</DefaultTargetSchema>
<DelaySign>false</DelaySign>
<OutputType>Library</OutputType>
<RootNamespace>blowery.Web.HttpCompress.Tests</RootNamespace>
<RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
<StartupObject>
</StartupObject>
<FileUpgradeFlags>
</FileUpgradeFlags>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<OutputPath>bin\Debug\</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DocumentationFile>
</DocumentationFile>
<DebugSymbols>true</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>
<NoWarn>
</NoWarn>
<Optimize>false</Optimize>
<RegisterForComInterop>false</RegisterForComInterop>
<RemoveIntegerChecks>false</RemoveIntegerChecks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel>
<DebugType>full</DebugType>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<OutputPath>bin\Release\</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>TRACE</DefineConstants>
<DocumentationFile>
</DocumentationFile>
<DebugSymbols>false</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>
<NoWarn>
</NoWarn>
<Optimize>true</Optimize>
<RegisterForComInterop>false</RegisterForComInterop>
<RemoveIntegerChecks>false</RemoveIntegerChecks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel>
<DebugType>none</DebugType>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Production|AnyCPU' ">
<OutputPath>bin\Release\</OutputPath>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<BaseAddress>285212672</BaseAddress>
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
<ConfigurationOverrideFile>
</ConfigurationOverrideFile>
<DefineConstants>TRACE</DefineConstants>
<DocumentationFile>
</DocumentationFile>
<DebugSymbols>false</DebugSymbols>
<FileAlignment>4096</FileAlignment>
<NoStdLib>false</NoStdLib>
<NoWarn>
</NoWarn>
<Optimize>true</Optimize>
<RegisterForComInterop>false</RegisterForComInterop>
<RemoveIntegerChecks>false</RemoveIntegerChecks>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningLevel>4</WarningLevel>
<DebugType>none</DebugType>
<ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<ItemGroup>
<Reference Include="nunit.framework">
<Name>nunit.framework</Name>
<HintPath>lib\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System">
<Name>System</Name>
</Reference>
<Reference Include="System.Data">
<Name>System.Data</Name>
</Reference>
<Reference Include="System.Xml">
<Name>System.XML</Name>
</Reference>
<ProjectReference Include="..\HttpCompress\HttpCompress.csproj">
<Name>HttpCompress</Name>
<Project>{24B5F8DA-F11C-4B77-8BF9-5D2E4534DDE5}</Project>
<Package>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</Package>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="XmlTestDocuments.xsx">
<DependentUpon>XmlTestDocuments.xsd</DependentUpon>
</None>
<Compile Include="AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="SettingsTests.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Utility.cs">
<SubType>Code</SubType>
</Compile>
<EmbeddedResource Include="XmlTestDocuments.resx" />
<Content Include="XmlTestDocuments.xsd" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
</Project>
13 changes: 13 additions & 0 deletions Tests/Utility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Resources;

namespace blowery.Web.HttpCompress.Tests {
/// <summary>Some utility functions for the tests</summary>
sealed class Utility {
private Utility() { }

public static System.Resources.ResourceManager GetResourceManager(string nameOfResouce) {
return new ResourceManager(Constants.Namespace + "." + nameOfResouce, System.Reflection.Assembly.GetExecutingAssembly());
}
}
}
Loading

0 comments on commit bfa5786

Please sign in to comment.