August Feng

Allowed HTTP Request Header Types

About

I'm trying to build a reverse proxy but it seems that there are some constraints on the headers that we can add to a HttpRequestMessage.

open System.Net.Http

let client = new HttpClient()

let request = new HttpRequestMessage(HttpMethod.Post, "https://example.com")

let tryAddHeader (message : HttpRequestMessage) (k : string) (v : string) =
    match message.Headers.TryAddWithoutValidation(k,v) with
    | true -> ()
    | false -> eprintfn $"Failed to add header: {k}."

let addHeader (message : HttpRequestMessage) (k : string) (v : string) =
    message.Headers.Add(k,v)

tryAddHeader request "Content-Type" "text/plain" // XXX: Failed to add header: Content-Type.
tryAddHeader request "Content-Length" "7" // XXX: Failed to add header: Content-Length.

addHeader request "Content-Length" "7" // XXX: throws an exception.

let response = client.Send(request)

Allowed Headers

The TryAddWithoutValidation will verify whether the header is allowed before adding it.

public bool TryAddWithoutValidation(string name, string? value) =>
            TryGetHeaderDescriptor(name, out HeaderDescriptor descriptor) &&
            TryAddWithoutValidation(descriptor, value);

The TryGetHeaderDescriptor will categorize the Content-Type and Content-length as a Content and that's not an allowed header type.

// header type.
{
  [Flags]
  internal enum HttpHeaderType : byte
  {
    General = 1,
    Request = 2,
    Response = 4,
    Content = 8,
    Custom = 16, // 0x10
    NonTrailing = 32, // 0x20
    All = NonTrailing | Custom | Content | Response | Request | General, // 0x3F
    None = 0,
  }
}

Message.Content.Headers

I investigated the yarp project to understand how they were proxying the headers and learned that these headers can be added by using methods on the req.Content.Headers object.

open System.IO
open System.Net.Http

let client = new HttpClient()

let ms =
    let bytes = "helloworld" |> System.Text.Encoding.UTF8.GetBytes
    new MemoryStream(bytes)

let buildMessage () =
    let req = new HttpRequestMessage(HttpMethod.Post, "http://localhost:1234")
    req.Content <- new StreamContent(ms)
    req.Content.Headers.TryAddWithoutValidation("Content-Type", "application/augustfeng") |> ignore
    req.Content.Headers.TryAddWithoutValidation("Content-Length", "10") |> ignore
    req

let message = buildMessage()

let response = client.Send(message)

0

The req.Content.Headers object is an instance of a HttpContentHeaders class whereas the req.Headers is an HttpRequestHeaders object.