7

In spite of the RFC stating that the order of uniquely-named headers shouldn't matter, the website I'm sending this request to does implement a check on the order of headers.

This works:

GET https://www.thewebsite.com HTTP/1.1
Host: www.thewebsite.com
Connection: keep-alive
Accept: */*
User-Agent: Mozilla/5.0 etc

This doesn't work:

GET https://www.thewebsite.com HTTP/1.1
Accept: */*
User-Agent: Mozilla/5.0 etc
Host: www.thewebsite.com
Connection: keep-alive

The default HttpWebRequest seems to put the Host and Connection headers at the end, before the blank line, rather than just after the url.

Is there any way (using a fork of HttpWebRequest or some other library in Nuget even) to specify the order of headers in a HttpWebRequest?

If possible, I'd rather not start going down the route of implementing a proxy to sort them or having to code the whole thing up using a TcpClient.

I'd appreciate any hints at all on this.

Update: With Fiddler running, header order in HttpWebrequest can be re-shuffled in CustomRules.cs. Still no closer to a solution without a proxy though.

user2146441
  • 220
  • 1
  • 19
  • 43
  • Please elaborate what does it mean `This doesn't work`. What http status code does the server return? – Peter Csala Aug 28 '20 at 06:33
  • It returns a 403 Forbidden. – user2146441 Aug 28 '20 at 10:22
  • Did you try to perform these requests from Postman? – Peter Csala Aug 28 '20 at 14:44
  • Quite frankly I don't think so there is any third party library that supports header sorting. I've checked [RestSharp's](https://github.com/restsharp/RestSharp/blob/9d6f853f4f6933c31079f5f114db941d73420c75/src/RestSharp/Http.cs#L185) and [Flurl's](https://github.com/tmenier/Flurl/blob/8d3749ea780f69e4203cd5be1ecd0914878af602/src/Flurl.Http/FlurlRequest.cs#L137) solution for request headers and none of them are ordered. – Peter Csala Aug 28 '20 at 14:50
  • 1
    I tried Postman and checked what was being sent with Wireshark. You can't order the headers with Postman, so it fails each time with a 403. Fiddler is a better tool for this. – user2146441 Aug 28 '20 at 17:10
  • Can you update the question with how the web request was created in C#? – weichch Sep 09 '20 at 10:28
  • 2
    in regards to "I'd rather not start going down the route of implementing a proxy to sort them"... I think you should go down the route of implementing a reverse proxy. I think working with a site that is stricter than the RFC standard merits that layer of abstraction. consider the scenario where order is changed\different in a staging environment, or varies by what resource you're requesting – Brett Caswell Sep 09 '20 at 20:57
  • I think you will effectively require a rule\policy based feature in place for that uncertainty. – Brett Caswell Sep 09 '20 at 21:04
  • excuse me, I meant forward proxy for outbound requests – Brett Caswell Sep 09 '20 at 22:10
  • Is this for .NET Framework? Or .NETCore? – Andy Sep 10 '20 at 15:23
  • .NET Framework. – user2146441 Sep 10 '20 at 20:09
  • It turns out my solution didn't work on .net Framework. I'm starting to think that this approach to reordering headers (also used by @tontonsevilla) could potentiality break in a future update to .net Core. Nowhere in the documentation does it say that we at allowed to do this, it's just exploiting a side effect due to the internal collection used. – Jason Sep 10 '20 at 23:33

2 Answers2

6

Some server implement header ordering as a precaution for any attacks or spam, an article explaining Why ordering HTTP headers is important.

But the standard is, the order in which header fields with differing field names are received is not significant.

HttpWebRequest, there is no easy way to order the headers and the Connection and Host is added internally.

If ordering is really important, use the HttpClient instead, it can easily arrange the Headers based on the example of @Jason.

If you will be using HttpClient, you can create a custom HttpClientHandler and you can arrange your header from there. It can be something like this.

HANDLER

public class CustomHttpClientHandler : HttpClientHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.Clear();

        request.Headers.Add("Host", $"{request.RequestUri.Authority}");
        request.Headers.Add("Connection", "keep-alive");
        request.Headers.Add("Accept", "*/*");
        request.Headers.Add("User-Agent", "Mozilla/5.0 etc");

        return await base.SendAsync(request, cancellationToken);
    }
}

IMPLEMENTATION

HttpClient clientRequest = new HttpClient(new CustomHttpClientHandler());
await clientRequest.GetAsync(url);
tontonsevilla
  • 2,649
  • 1
  • 11
  • 18
  • I think this is a good answer - though not complete. you could improve this answer by providing instructions on how to reuse this solution. That is, since OP, is looking for non proxy or project coded solution, it stands to reason that this behavior would be a package and framework integration, and that it may require\support some configuration efforts. – Brett Caswell Sep 09 '20 at 20:19
  • @tontonsevilla it turns out my solution didn't work on .net Framework. I'm starting to think that this approach to reordering headers could potentiality break in a future update to .net Core. Nowhere in the documentation does it say that we at allowed to do this, it's just exploiting a side effect due to the internal collection used. – Jason Sep 10 '20 at 23:29
4

.Net Core

If you set the headers yourself, you can specify the order. When the common headers are added it will find the existing headers instead of appending them:

using System.Net;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            var request = WebRequest.Create("http://www.google.com");
            request.Headers.Add("Host", "www.google.com");
            // this will be set within GetResponse.
            request.Headers.Add("Connection", "");
            request.Headers.Add("Accept", "*/*");
            request.Headers.Add("User-Agent", "Mozilla/5.0 etc");
            request.GetResponse();
        }
    }
}

enter image description here

Here is an example with HttpClient:

using System.Net.Http;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var client = new HttpClient();
            client.DefaultRequestHeaders.Add("Host", "www.google.com");
            client.DefaultRequestHeaders.Add("Connection", "keep-alive");
            client.DefaultRequestHeaders.Add("Accept", "*/*");
            client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 etc");
            await client.GetAsync("http://www.google.com");
            await client.PostAsync("http://www.google.com", new StringContent(""));
        }
    }
}

GET with ordered headers POST with ordered headers

Edit The above code did not work on .Net Framework only .Net Core

.Net Framework

On .Net Framework the headers are reserved so they cannot be set like this, see Cannot set some HTTP headers when using System.Net.WebRequest.

One work around is to use reflection to modify the behavior of the framework class, but be warned this could break if the libraries are updated so it's not recommended!.

Essentially, HttpWebRequest calls ToString on WebHeaderCollection to serialize. See https://referencesource.microsoft.com/#System/net/System/Net/HttpWebRequest.cs,5079

So a custom class can be made to override ToString. Unfortunately reflection is needed to set the headers as WebRequest copies the collection on assignment to Headers, instead of taking the new reference.

WARNING, THE FOLLOWING CODE CAN BREAK IF FRAMEWORK CHANGES

If you use this, write some unit tests that verify the behavior still stays consistent after updates to .NET Framework

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            // WARNING, CODE CAN BREAK IF FRAMEWORK CHANGES
            // If you use this, write some unit tests that verify the behavior still stays consistent after updates to .NET Framework
            var request = (HttpWebRequest)WebRequest.Create("http://www.google.com");
            var field = typeof(HttpWebRequest).GetField("_HttpRequestHeaders", BindingFlags.Instance | BindingFlags.NonPublic);
            var headers = new CustomWebHeaderCollection(new Dictionary<string, string>
            {
                ["Host"] = "www.google.com",
                ["Connection"] = "keep-alive",
                ["Accept"] = "*/*",
                ["User-Agent"] = "Mozilla/5.0 etc"
            });
            field.SetValue(request, headers);
            request.GetResponse();
        }
    }

    internal class CustomWebHeaderCollection : WebHeaderCollection
    {
        private readonly Dictionary<string, string> _customHeaders;

        public CustomWebHeaderCollection(Dictionary<string, string> customHeaders)
        {
            _customHeaders = customHeaders;
        }

        public override string ToString()
        {
            // Could call base.ToString() split on Newline and sort as needed

            var lines = _customHeaders
                .Select(kvp => $"{kvp.Key}: {kvp.Value}")
                // These two new lines are needed after the HTTP header
                .Concat(new [] { string.Empty, string.Empty });

            var headers = string.Join("\r\n", lines);

            return headers;
        }
    }
}

enter image description here

Jason
  • 1,505
  • 5
  • 9
  • I also looked into having a custom `GetEnumerator` with a sort function, implemented on a custom `WebHeaderCollection`, but it required reflection to set the field as the setter within `HttpWebRequest` copies content into a new `WebHeaderCollection`. – Jason Sep 06 '20 at 10:25
  • The WebRequest code above won't run in .NET Framework. Is this for .NET Core? – user2146441 Sep 10 '20 at 20:10
  • @user2146441 Aaah, yes I tested this on .net core. I just had a quick test and I have a solution that uses reflection. But it's not pretty – Jason Sep 10 '20 at 22:15
  • @user2146441 I have added the ugly code. It can work and it could be cleaned up by passing a dictionary into the custom headers or making a sort function in ToString, but it is definitely error prone if the framework were to change it's implementation. – Jason Sep 10 '20 at 22:25