im in ur web, enriching ur code

 
 

What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?

This post has been brewing for a little while. It stems from an inconsistency I've seen in code posted here and there over the web. Quite specifically this happens often when trying to detect which Accept-Encoding a browser can accept, so a GZIP or DEFLATE filter can be used to compress the content.

The Offending Code

The code in question goes something like this:

string encoding = Request.Headers["Accept-Encoding"];

if (encoding.Contains("gzip"))

{

  Response.AppendHeader("Content-Encoding", "gzip");

  Response.Filter = new GZipStream(Response.Filter, CompressionMode.Compress);

}

else if (encoding.Contains("deflate"))

{

  Response.AppendHeader("Content-Encoding", "deflate");

  Response.Filter = new DeflateStream(Response.Filter, CompressionMode.Compress);

}

What's wrong with it? It seems to be pretty standard and widely in use.

Well, consider the following possible values that can be sent in the Accept-Encoding header.

1: "gzip,deflate"

2: "deflate,gzip"

3: "gzip;q=.5,deflate"

4: "gzip;q=0,deflate"

5: "deflate;q=0.5,gzip;q=0.5,identity"

6: "*"

and here are some observations when running the above code listing, compared to what is expected by section 14 of RFC2616.

  1. "gzip,deflate":
    This one passes the example code and the rfc fine. As the browser, we're expecting gzip first, otherwise give us deflate.
  2. "deflate,gzip":
    Unfortunately, as the browser, we want deflate first, but gzip is okay if the server can't give us our first preference; we're served gzip in this case as the code just looks to see if "gzip" is contained within the Accept-Encoding header before it looks to see if "deflate" is there. This isn't such a big deal as we're still receiving compressed content, but we should have been served deflate.
  3. "gzip;q=.5,deflate":
    This one uses another valid method to tell the server (and your application) that the browser would prefer deflate encoding before gzip encoding. When a "q=" is specified after the encoding, with a value between 0 and 1, the preference is given that weight (or quality). When no "q=" is specified, it defaults to 1. So this rule tells the server the browser wants gzip only half as much as it wants deflate compression. Because our code is looking to see if gzip is contained within the header, it serves us gzip despite our preference. It's still not critical as we can still accept the gzip encoding.
  4. "gzip;q=0,deflate":
    Here is where we get into trouble. Now our header is telling the server "Do NOT give me gzip compressed content, but I'll take content encoded with deflate if you can". Again, because the code simply looks to see if gzip is contained within the header, regardless of its weight and being disallowed, we're still served gzip.
  5. "deflate;q=0.5,gzip;q=0.5,identity":
    This one is more of a trick question; its not as innocent as you might think. For accept encodings, "identity" is a special case. It means that it should be served without content encoding. Considering that the "q" defaults to "1" when not specified, this is the order of our preferred encodings "identity,deflate;q=0.5,gzip;q=0.5". This means that we shouldn't give a content-encoding. But once again, because the code simply looks to see if contains gzip, we get gzipped content.
  6. "*":
    The last one has said "We'll accept any encoding you want to serve.", and while we could (and perhaps should) be served compressed content, because the code can't see gzip or deflate in the header, it serves the normal content. This one isn't so critical, but it could have been easily solved and help in reducing bandwidth and download time.

In all fairness, its probably not likely now days to not accept gzip encoding so it probably effects very few requests - but it is incorrect. In my examples I've used an arbitrary ordering to illustrate the importance of the "q" part in regards to preference. Quite usually however, the most preferred values appear first in the list - but it's not guaranteed

To handle this situation we need to be able to find the preferred encoding (and this can be applied to any of the similar headers i.e. Accept-Encoding, Accept-Charset, Accept-Language & Accept) that is accepted.

Finding a preferred HTTP Header Value in C#

I decided to split the header value up into its relevant segments, sort it and then interrogate it in a generic list. There is possibly more overhead from parsing the value into a list and then looping over the values, but verses having an incorrect implementation as the first code listing does, its worth it. My implementation of a QValueList follows shortly. First, if you're interested, I'd encourage you to have a look at the built-in Microsoft "System.Web.Caching.OutputCacheModule" module via Lutz Roeder's .NET Reflector. It uses a parsing method to determine whether an encoding is allowed or not; and truthfully, its also where I encountered the "identity" encoding and investigated its impact on what we're trying to achieve So an alternative to this approach would be to parse similar to the OutputCache module.

Here's how we'll change the typical code from the start of the post:

/// load encodings from header

QValueList encodings = new QValueList(Request.Headers["Accept-Encoding"]);

 

/// get the types we can handle, can be accepted and

/// in the defined client preference

QValue preferred = encodings.FindPreferred("gzip", "deflate", "identity");

 

/// if none of the preferred values were found, but the

/// client can accept wildcard encodings, we'll default

/// to Gzip.

if(preferred.IsEmpty && encodings.AcceptWildcard && encodings.Find("gzip").IsEmpty)

  preferred = new QValue("gzip");

 

// handle the preferred encoding

switch(preferred.Name)

{

  case "gzip":

      Response.AppendHeader("Content-Encoding", "gzip");

      Response.Filter = new GZipStream(Response.Filter, CompressionMode.Compress);

    break;

  case "deflate":

      Response.AppendHeader("Content-Encoding", "deflate");

      Response.Filter = new DeflateStream(Response.Filter, CompressionMode.Compress);

    break;

  case "identity":

  default:

    break;

}

The first observation one might make is that there is quite a lot more code. However, it is required to get the correct behaviour. Besides, its not a lot if you factor it into your code/library/component correctly.

So why is it called "QValue" and not simply simply "AcceptEncoding"? Well, you can apply this to any of the headers that use the same qualified value convention including:

  • Request.Headers["Accept-Encoding"]
  • Request.Headers["Accept-Charset"]
  • Request.Headers["Accept-Language"]
  • Request.Headers["Accept"]

Source Code Listing & Download

Here's the code listing in line, or you can download the .cs file containing both QValue and QValueList.

using System;

using System.Collections.Generic;

using System.Diagnostics;

using System.Text;

 

/// <summary>

/// Represents a weighted value (or quality value) from an http header e.g. gzip=0.9; deflate; x-gzip=0.5;

/// </summary>

/// <remarks>

/// accept-encoding spec:

///    http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

/// </remarks>

/// <example>

/// Accept:          text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5

/// Accept-Encoding: gzip,deflate

/// Accept-Charset:  ISO-8859-1,utf-8;q=0.7,*;q=0.7

/// Accept-Language: en-us,en;q=0.5

/// </example>

[DebuggerDisplay("QValue[{Name}, {Weight}]")]

public struct QValue : IComparable<QValue>

{

  static char[] delimiters = { ';', '=' };

  const float defaultWeight = 1;

 

  #region Fields

 

  string _name;

  float _weight;

  int _ordinal;

 

  #endregion

 

  #region Constructors

 

  /// <summary>

  /// Creates a new QValue by parsing the given value

  /// for name and weight (qvalue)

  /// </summary>

  /// <param name="value">The value to be parsed e.g. gzip=0.3</param>

  public QValue(string value)

    : this(value, 0)

  { }

 

  /// <summary>

  /// Creates a new QValue by parsing the given value

  /// for name and weight (qvalue) and assigns the given

  /// ordinal

  /// </summary>

  /// <param name="value">The value to be parsed e.g. gzip=0.3</param>

  /// <param name="ordinal">The ordinal/index where the item

  /// was found in the original list.</param>

  public QValue(string value, int ordinal)

  {

    _name = null;

    _weight = 0;

    _ordinal = ordinal;

 

    ParseInternal(ref this, value);

  }

 

  #endregion

 

  #region Properties

 

  /// <summary>

  /// The name of the value part

  /// </summary>

  public string Name

  {

    get { return _name; }

  }

 

  /// <summary>

  /// The weighting (or qvalue, quality value) of the encoding

  /// </summary>

  public float Weight

  {

    get { return _weight; }

  }

 

  /// <summary>

  /// Whether the value can be accepted

  /// i.e. it's weight is greater than zero

  /// </summary>

  public bool CanAccept

  {

    get { return _weight > 0; }

  }

 

  /// <summary>

  /// Whether the value is empty (i.e. has no name)

  /// </summary>

  public bool IsEmpty

  {

    get { return string.IsNullOrEmpty(_name); }

  }

 

  #endregion

 

  #region Methods

 

  /// <summary>

  /// Parses the given string for name and

  /// weigth (qvalue)

  /// </summary>

  /// <param name="value">The string to parse</param>

  public static QValue Parse(string value)

  {

    QValue item = new QValue();

    ParseInternal(ref item, value);

    return item;

  }

 

  /// <summary>

  /// Parses the given string for name and

  /// weigth (qvalue)

  /// </summary>

  /// <param name="value">The string to parse</param>

  /// <param name="ordinal">The order of item in sequence</param>

  /// <returns></returns>

  public static QValue Parse(string value, int ordinal)

  {

    QValue item = Parse(value);

    item._ordinal = ordinal;

    return item;

  }

 

  /// <summary>

  /// Parses the given string for name and

  /// weigth (qvalue)

  /// </summary>

  /// <param name="value">The string to parse</param>

  static void ParseInternal(ref QValue target, string value)

  {

    string[] parts = value.Split(delimiters, 3);

    if (parts.Length > 0)

    {

      target._name = parts[0].Trim();

      target._weight = defaultWeight;

    }

 

    if (parts.Length == 3)

    {

      float.TryParse(parts[2], out target._weight);

    }

  }

 

  #endregion

 

  #region IComparable<QValue> Members

 

  /// <summary>

  /// Compares this instance to another QValue by

  /// comparing first weights, then ordinals.

  /// </summary>

  /// <param name="other">The QValue to compare</param>

  /// <returns></returns>

  public int CompareTo(QValue other)

  {

    int value = _weight.CompareTo(other._weight);

    if (value == 0)

    {

      int ord = -_ordinal;

      value = ord.CompareTo(-other._ordinal);

    }

    return value;

  }

 

  #endregion

 

  #region CompareByWeight

 

  /// <summary>

  /// Compares two QValues in ascending order.

  /// </summary>

  /// <param name="x">The first QValue</param>

  /// <param name="y">The second QValue</param>

  /// <returns></returns>

  public static int CompareByWeightAsc(QValue x, QValue y)

  {

    return x.CompareTo(y);

  }

 

  /// <summary>

  /// Compares two QValues in descending order.

  /// </summary>

  /// <param name="x">The first QValue</param>

  /// <param name="y">The second QValue</param>

  /// <returns></returns>

  public static int CompareByWeightDesc(QValue x, QValue y)

  {

    return -x.CompareTo(y);

  }

 

  #endregion

 

}

 

/// <summary>

/// Provides a collection for working with qvalue http headers

/// </summary>

/// <remarks>

/// accept-encoding spec:

///    http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

/// </remarks>

[DebuggerDisplay("QValue[{Count}, {AcceptWildcard}]")]

public sealed class QValueList : List<QValue>

{

  static char[] delimiters = { ',' };

 

  #region Fields

 

  bool _acceptWildcard;

  bool _autoSort;

 

  #endregion

 

  #region Constructors

 

  /// <summary>

  /// Creates a new instance of an QValueList list from

  /// the given string of comma delimited values

  /// </summary>

  /// <param name="values">The raw string of qvalues to load</param>

  public QValueList(string values)

    : this(null == values ? new string[0] : values.Split(delimiters, StringSplitOptions.RemoveEmptyEntries))

  { }

 

  /// <summary>

  /// Creates a new instance of an QValueList from

  /// the given string array of qvalues

  /// </summary>

  /// <param name="values">The array of qvalue strings

  /// i.e. name(;q=[0-9\.]+)?</param>

  /// <remarks>

  /// Should AcceptWildcard include */* as well?

  /// What about other wildcard forms?

  /// </remarks>

  public QValueList(string[] values)

  {

    int ordinal = -1;

    foreach (string value in values)

    {

      QValue qvalue = QValue.Parse(value.Trim(), ++ordinal);

      if (qvalue.Name.Equals("*")) // wildcard

        _acceptWildcard = qvalue.CanAccept;

      Add(qvalue);

    }

 

    /// this list should be sorted by weight for

    /// methods like FindPreferred to work correctly

    DefaultSort();

    _autoSort = true;

  }

 

  #endregion

 

  #region Properties

 

  /// <summary>

  /// Whether or not the wildcarded encoding is available and allowed

  /// </summary>

  public bool AcceptWildcard

  {

    get { return _acceptWildcard; }

  }

 

  /// <summary>

  /// Whether, after an add operation, the list should be resorted

  /// </summary>

  public bool AutoSort

  {

    get { return _autoSort; }

    set { _autoSort = value; }

  }

 

  /// <summary>

  /// Synonym for FindPreferred

  /// </summary>

  /// <param name="candidates">The preferred order in which to return an encoding</param>

  /// <returns>An QValue based on weight, or null</returns>

  public QValue this[params string[] candidates]

  {

    get { return FindPreferred(candidates); }

  }

 

  #endregion

 

  #region Add

 

  /// <summary>

  /// Adds an item to the list, then applies sorting

  /// if AutoSort is enabled.

  /// </summary>

  /// <param name="item">The item to add</param>

  public new void Add(QValue item)

  {

    base.Add(item);

 

    applyAutoSort();

  }

 

  #endregion

 

  #region AddRange

 

  /// <summary>

  /// Adds a range of items to the list, then applies sorting

  /// if AutoSort is enabled.

  /// </summary>

  /// <param name="collection">The items to add</param>

  public new void AddRange(IEnumerable<QValue> collection)

  {

    bool state = _autoSort;

    _autoSort = false;

 

    base.AddRange(collection);

 

    _autoSort = state;

    applyAutoSort();

  }

 

  #endregion

 

  #region Find

 

  /// <summary>

  /// Finds the first QValue with the given name (case-insensitive)

  /// </summary>

  /// <param name="name">The name of the QValue to search for</param>

  /// <returns></returns>

  public QValue Find(string name)

  {

    Predicate<QValue> criteria = delegate(QValue item) { return item.Name.Equals(name, StringComparison.OrdinalIgnoreCase); };

    return Find(criteria);

  }

 

  #endregion

 

  #region FindHighestWeight

 

  /// <summary>

  /// Returns the first match found from the given candidates

  /// </summary>

  /// <param name="candidates">The list of QValue names to find</param>

  /// <returns>The first QValue match to be found</returns>

  /// <remarks>Loops from the first item in the list to the last and finds

  /// the first candidate - the list must be sorted for weight prior to

  /// calling this method.</remarks>

  public QValue FindHighestWeight(params string[] candidates)

  {

    Predicate<QValue> criteria = delegate(QValue item)

    {

      return isCandidate(item.Name, candidates);

    };

    return Find(criteria);

  }

 

  #endregion

 

  #region FindPreferred

 

  /// <summary>

  /// Returns the first match found from the given candidates that is accepted

  /// </summary>

  /// <param name="candidates">The list of names to find</param>

  /// <returns>The first QValue match to be found</returns>

  /// <remarks>Loops from the first item in the list to the last and finds the

  /// first candidate that can be accepted - the list must be sorted for weight

  /// prior to calling this method.</remarks>

  public QValue FindPreferred(params string[] candidates)

  {

    Predicate<QValue> criteria = delegate(QValue item)

    {

      return isCandidate(item.Name, candidates) && item.CanAccept;

    };

    return Find(criteria);

  }

 

  #endregion

 

  #region DefaultSort

 

  /// <summary>

  /// Sorts the list comparing by weight in

  /// descending order

  /// </summary>

  public void DefaultSort()

  {

    Sort(QValue.CompareByWeightDesc);

  }

 

  #endregion

 

  #region applyAutoSort

 

  /// <summary>

  /// Applies the default sorting method if

  /// the autosort field is currently enabled

  /// </summary>

  void applyAutoSort()

  {

    if (_autoSort)

      DefaultSort();

  }

 

  #endregion

 

  #region isCandidate

 

  /// <summary>

  /// Determines if the given item contained within the applied array

  /// (case-insensitive)

  /// </summary>

  /// <param name="item">The string to search for</param>

  /// <param name="candidates">The array to search in</param>

  /// <returns></returns>

  static bool isCandidate(string item, params string[] candidates)

  {

    foreach (string candidate in candidates)

    {

      if (candidate.Equals(item, StringComparison.OrdinalIgnoreCase))

        return true;

    }

    return false;

  }

 

  #endregion

 

}

 

kick it on DotNetKicks.com

Posted on Sunday, July 06, 2008 4:55 PM
Filed Under [ C#, Tips, .NET, Source Code ]

Comments

Gravatar
# re: What's wrong with Request.["Accept-Encoding"].Contains("gzip")?
Posted by Simone
Posted on 7/6/2008 10:31 PM
Dave, but isn't the accept-language already parsed in some way by a .NET class already?
Gravatar
# re: What's wrong with Request.["Accept-Encoding"].Contains("gzip")?
Posted by Miron
Posted on 7/7/2008 4:22 AM
Theoretically you are right, but
can you give an example of any browser that will use the request "Accept-Encoding" with the value
"gzip;q=0,deflate" or "*" ?
Gravatar
# re: What's wrong with Request.["Accept-Encoding"].Contains("gzip")?
Posted by Dave Transom
Posted on 7/7/2008 9:29 AM
@Simone:
I didn't check for this earlier, but having a look via reflector I found a couple of instances where headers are parsed to return a string array.

A private static method of the internal class "System.Net.HeaderInfoTable.ParseMultiValue(string value)".

And the private static method "System.Web.HttpRequest.ParseMultivalueHeader(string s)".

Both have similar code, but the "qvalue" isn't taken into account.

It's the second method that does the parsing for the "Request.UserLanguages", so no ordering based on preference is undertaken.

The only place I found this working as expected was in "System.Web.Caching.OutputCacheModule".

So no, there doesn't seem to be inbuilt functionality that we can touch for the "Accept-Language" header either.
Gravatar
# re: What's wrong with Request.["Accept-Encoding"].Contains("gzip")?
Posted by Dave Transom
Posted on 7/7/2008 9:49 AM
@Miron:
I wouldn't think it common place for this to be sent. And quite possibly because it may happen so infrequently, we wouldn't notice a miss in this area. And possibly, in the scenario of simply giving preference to the encoding types, the user agent would be able to handle the served content in any case, which is no biggie.

As to an example of a modern browser with out-of-the-box settings, I wouldn't think there are any.

Proxies altering headers of those browsers I'm not so sure about, or non-browser user-agents perhaps used to check/scrape sites with very specific requirements. What if a network admin, reading the HTTP specification, wanted to deny all gzip content (with gzip;q=0) because of some issues with another component working in the network, she'd still be served gzip content. Okay, its not likely, but what if there are? It's all hypothetical really.

This leads me back to the main point - most implementations ignore this preference; should they be?

PS: In my last comment to Simone, I looked into whether the accept languages were handled with this in mind but I can't find evidence that they are. Does this mean its entirely unimportant? The OutputCache module certainly thinks it is, being the only one that parses the weight of the preference.


Gravatar
# re: What's wrong with Request.["Accept-Encoding"].Contains("gzip")?
Posted by Miron
Posted on 7/7/2008 11:06 AM
Thanks for your reply.
I do think this check must not be ignore.
Another thing, is "gzip;deflate" valid? isn't the separator char must be a comma ',' ?
Gravatar
# re: What's wrong with Request.["Accept-Encoding"].Contains("gzip")?
Posted by Dave Transom
Posted on 7/7/2008 11:26 AM
@Miron:
Yes, the semicolon is to separate the quality, and comma for the actual types.

I've updated the post. Good spotting :)
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by DavidP
Posted on 7/10/2008 4:33 AM
This is off-topic by why are we doing HTTP compression at the application level? Wouldn't this be better accomplished using IIS or an external device?
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Damien Guard
Posted on 7/10/2008 5:00 AM
The problem with this code is that if a new encoding type comes out that browsers specify in preference over gzip this code will not even consider falling back to gzip.

Really that case over .preferred should be a foreach loop over the order of priority and break out as soon as it finds something it can deal with.

[)amien
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Dave Transom
Posted on 7/10/2008 8:16 AM
@DavidP:
That's a good point, but not all applications or developments have access to controlling this feature in IIS or by an external device, especially in a shared hosting environment. I imagine it will get easier to achieve with a configuration option in IIS7 so we won't have to touch IIS (in an administrative sense) to get the compression benefits.

But it's not only about compression, the post is really highlighting the point that there is more to interrogating http headers than is commonly practiced.
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Dave Transom
Posted on 7/10/2008 8:38 AM
@Damien:
If a new encoding 'comes out' you'd have to revisit your code in any case - you'd have to add the compression algorithm itself, then the case for setting up the response filter - so I wouldn't see that as a problem per se.

Although, I do see that the wild card scenario, where it falls back to "gzip", should really loop over the preferred options to find the first accepted encoding, as opposed to simply seeing if "gzip" is not explicitly denied.

PS: In the common code out there, if that 'new encoding' was far fetched and inappropriately named "gzip2008" (or perhaps even "compressingzip", i.e. "Compressing Zip"), everyone would be served content with gzip encoding, because the "gzip" string is still found.
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Nick Berardi
Posted on 7/10/2008 12:38 PM
@Miron wrote "Theoretically you are right, but
can you give an example of any browser that will use the request "Accept-Encoding" with the value
"gzip;q=0,deflate" or "*" ?"

Actually "*" is used more than you think, it is used by most of the AJAX requests, as well as intermediate proxy servers like those used to serve Facebook applications.

Thanks for this post I will definitely be using some form of this in my future projects.
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by James L
Posted on 7/11/2008 8:29 AM
To hell with what the client prefers. If it can read it, I'll send it how I bloody well want. Good day sir.
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Dave Transom
Posted on 7/11/2008 9:00 AM
@James L:
That's a fair statement and you are correct in a sense. Although, you would _know_ "If it can read it" or not by your correct interrogation of the headers - basically anything that isn't explicitly denied you could send when accept "*" is specified.

Then, if you're doing things right, the rest becomes pure preference and you are free to send it how you want from what's left over.
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Paul Wratt
Posted on 7/19/2008 1:29 PM
I have a "qwirk" at the moment, I have a browser and localhost that sends a "gzip,deflate" value. however when I connect to a server on the net, it only supplies "identity,deflate"

I have only ever seen 2 values supplied by a browser (or one value), however, would you guess (as I do) that the proxy of "this" network is modifying ACCEPT-ENCODING, essentially saying it will not accept "GZIP" (the proxy is SQUID fyi)

Although technically the delivery is a none issue (it can and does revert to deflate, or identity), I wonder if this senario with the proxy is actually a common thing or not.

What are your thoughts..

Paul
PS there are some "wierd" descussions regarding this (I just read some squid-dev list posts going back to 1998, mentioning a VARY response header from the atrget server???
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Dave Transom
Posted on 7/24/2008 5:55 PM
Hi Paul, sorry for the late reply.

It sounds to me like 'something' along the way is modifying the header - a proxy being a likely candidate - it may be your ISP as well.

I would think this happens often, but since it never has a real effect, like you pointed out, is a non-issue, and we (developers) tend to notice less.

In regards to the VARY response header, it's best (and common in code in the wild) to specify the header, especially if you're trying to achieve compression. It tells downstream proxies and caches under which circumstances it should cache or not.

The response header "Vary: Accept-Encoding" helps when, for instance a gzip file is cached by a proxy, and a new client requests the same file, but can't handle gzip. It (the proxy) should then serve a more appropriate file based on the new request header "Accept-Encoding".

Although, whether any of the intermediate proxies follow all the rules and guidelines laid out in a standard is open for debate :)

As a side note, I've wasted quite a few hours trying to get proxies to obey headers sent from a server with varying degree's of success. One such case recently was trying to get the proxy to close the connection so a 'simple' Response.Redirect from HTTP to HTTPS would work. I never quite got this one fixed and had to jump through a few hoops to get the very simple redirect to work. I think a lot comes down to configuration of the proxy in question - and whether the proxy owner has the time or inclination to help.

Hope that helps :)
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Anthony Bouch
Posted on 8/3/2008 7:32 PM
Thanks a bunch for this Dave. I was using a modified version of B. Lowery's encoding and quality detection code - but this is much cleaner. Awesome. Have just updated my photo gallery app at 58bits.com (with credits and a link to this post in the code).
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Nicolas
Posted on 8/30/2008 9:44 AM
You might have a bigger problem here. Don't you actually mean Transfer-Encoding? Is compressing, as you're using it here, a content encoding? Go read the RFC before responding.
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Piers
Posted on 1/5/2010 1:57 PM
I like this and will probbaly use it in my RESTful MVC example. I do have one issue with the article though... I don't think your code will deal with the Accept header as you suggest. The accept header requires a more complex comparison taking into account q value but also how specific the "Name" element is. Also the Accept header allows each media to have more than one parameter (i.e. there could be more than just q=0.8). For example, RFC 2616 gives the example:

Accept: text/*;q=0.3, text/html;q=0.7, text/html;level=1,
text/html;level=2;q=0.4, */*;q=0.5

But for dealing with the simpler headers, this looks spot on!

BTW There is an example SDK from Microsoft on Codeplex that contains code for parsing the Accept headers into a sorted list.
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Dave Transom
Posted on 1/5/2010 4:00 PM
@Piers: Thanks for the comment. You are correct in a sense. I remember looking at the RFC at the time of writing the post, but didn't believe it applied to the Accept-Encoding header as there wasn't additional information about it - no examples are given under the "Accept-Encoding" section, only for "Accept", which is for media type rather than encoding.

The 'level' token seems to be classified as an "accept-extension", and while they give examples, they don't explain what they are used for. I could only find a reference (on an Apache resource) that it is "used to give the version of text/html media types".

It doesn't seem to be legal syntax to send anything beyond the simple syntax for the Accept-Encoding header - at least as far as I could interpret. So it would seem that the code above is still sound as it is for the purposes of the post's topic of compression.

However, I've suggested that it could be used for the other "Accept" headers and on hindsight (and a little more investigation as I didn't look deeply at the other header specs at the time) is not the case. It can only be applied to the "Accept-Encoding" header.

Thanks again, I appreciate being kept on my toes :)
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Piers
Posted on 1/5/2010 10:21 PM
I'll be using it for Accept-Charset for which it looks just fine! So thanks for posting it.
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Jacob
Posted on 7/9/2010 3:25 PM
Great article. Like you I'm also a purist with this type of thing. Even though a simpler approach would work *most* of the time, I'm the type of guy who wants it work *every* time.

In the end I opted to roll my own code rather that use yours because I felt that using regular expressions would make it simpler. The regular expression below matches a single acceptable encoding and optional quality value. Use it with the Matches method of a Regex (hopefully it won't get mangled in the comments).

@"(?<type>[^\s;,]+)(?:\s*;\s*q\s*=\s*(?<quality>\d?\.\d{1,3}|\d))?\s*(?:,|$)"

Also, after reading the HTTP spec a dozen times, I'm not sure I agree with you on point 2 above. There's nothing I have found that would imply that the order the codings are listed in has any bearing on their "priority". An accept encoding of "deflate,gzip" would be the equivalent of "gzip;q=1,deflate;q=1". The browser is prioritize both equally, and therefore, either response is valid.
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Dave Transom
Posted on 7/12/2010 12:06 PM
@Jacob: Hmm, certainly something to think about regarding precedence. The RFC doesn't give a specific example - I've popped the question over to stack overflow (http://stackoverflow.com/questions/3225136/http-what-is-the-preferred-accept-encoding-for-gzip-deflate) to see what others think.

Indeed both are acceptable, but is the really still a precedence?
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by RichB
Posted on 12/19/2011 11:37 PM
I'd love to use this. What's your license on it?

Also, .Net 4.5 has an API for this, which at the time of writing is a Work-In-Progress in Mono:

https://github.com/mono/mono/tree/master/mcs/class/System.Net.Http/System.Net.Http.Headers
Gravatar
# re: What's wrong with Request.Headers["Accept-Encoding"].Contains("gzip")?
Posted by Dave Transom
Posted on 12/30/2011 11:36 AM
@RichB: Thanks Rich - I don't currently publish a licence under which snippets and work on this blog can be re-used. But have had the question come up previously --> http://www.singular.co.nz/blog/archive/2007/12/20/shortguid-a-shorter-and-url-friendly-guid-in-c-sharp.aspx#499

I've intended it to be freely available, customisable and distributable.

I'll choose a license shortly, however, please feel free to use it as you will until I've specified a license.

HTH

Post Comment

Title *
Name *
Email
Url
Comment *  
Please add 6 and 5 and type the answer here:

Recently on C# Vitamins...

Powered By Subtext

 

About C# Vitamins

Dave has been working in the industry for around 14 years, and has a focus on Javascript, C#, ASP.NET and SQL Server web development; not to mention being a standards driven type of guy.

C# Vitamins is the result of his findings while working in the web industry and a desire to share with the community; and if it was traced back far enough, you might say it might not have existed if he hadn't taken such an interest in id Software's original Quake.

Related Links

Below is a list of related links of Dave's other sites.