Skip to content

Net7 regression: Kestrel HttpRequestHeaders doesn't enumerate "Content-Length" unless there's another known header also present. #54557

@Tomius

Description

@Tomius

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

If you add a key to a Dictionary, then enumerate its elements just afterwards, you expect the recently added key to be present.

However this is not the case for the Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestHeaders class starting from net7.0, if the key is "Content-Length", and the dictionary does not contain any other known headers:

httpRequestHeaders["Content-Length"] = "423";
        - Count: 1
        - Keys: []
        - Values: []
        - Enumerate: []
        - TryGetValue("Content-Length", out var _) == True

E.g. some of the properties on the directionary act as if the "Content-Length" was added, others act as if it wasn't.

What's interesting to is that if there is at least one other known header in the dictionary, regardless if it was added before or after the "Content-Length", then the behaviour of the dictionary becomes normal again.

E.g. for the above example, adding "Content-Type" to the headers makes the "Content-Length" appear in the enumerated list as well:

httpRequestHeaders["Content-Type"] = "text";
        - Count: 2
        - Keys: [Content-Type, Content-Length]
        - Values: [text, 423]
        - Enumerate: [[Content-Type, text], [Content-Length, 423]]
        - TryGetValue("Content-Length", out var _) == True 

Expected Behavior

Since the HttpRequestHeaders implements the IDictionary interface, it should behave like a Dictionary in respect to adding and enumarating elements (e.g. how it behaved until net6.0), and the number of enumerated elements should always be equal to the .Count property.

Even if "Content-Length" is treated in a special way, the presence of other known headers should not impact whether "Content-Length" is enumerated or not.

Steps To Reproduce

Create a console app:

The .csproj:

<Project Sdk="Microsoft.NET.Sdk.Web">
    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <OutputType>Exe</OutputType>
        <ImplicitUsings>enable</ImplicitUsings>
    </PropertyGroup>
</Project>

The Program.cs:

using System.Text;
using Microsoft.Net.Http.Headers;

public static class Program
{
    public static void Main()
    {
        IHeaderDictionary dict =
            (IHeaderDictionary)
            typeof(Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer)
            .Assembly
            .GetType("Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestHeaders", throwOnError: true)!
            .GetConstructor([typeof(bool), typeof(Func<string, Encoding>)])!
            .Invoke([true, null]);

        AddAndPrint(dict, "a", "1");
        AddAndPrint(dict, HeaderNames.ContentLength, "423");
        AddAndPrint(dict, "b", "2");
        AddAndPrint(dict, HeaderNames.ContentType, "text");
    }

    private static void AddAndPrint(IHeaderDictionary dict, string key, string value)
    {
        Console.WriteLine($"httpRequestHeaders[\"{key}\"] = \"{value}\";");
        dict[key] = value;
        Console.WriteLine("\t- Count: " + dict.Count);
        Console.WriteLine("\t- Keys: [" + string.Join(", ", dict.Keys) + "]");
        Console.WriteLine("\t- Values: [" + string.Join(", ", dict.Values) + "]");
        Console.WriteLine("\t- Enumerate: [" + string.Join(", ", dict) + "]");
        Console.WriteLine($"\t- TryGetValue(\"{HeaderNames.ContentLength}\", out var _) == " + dict.TryGetValue(HeaderNames.ContentLength, out var x) + "\n");
    }
}

Exceptions (if any)

No response

.NET Version

7.0.0 or newer

Anything else?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-networkingIncludes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractionsfeature-kestrel

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions