Skip to content

Commit

Permalink
Add HttpHeaders.NonValidated (#53555)
Browse files Browse the repository at this point in the history
This adds an HttpHeaders.NonValidated property, which returns a type that provides a non-validating / non-parsing / non-allocating view of headers in the collection.  Querying the resulting collection does not force parsing or validation on the contents of the headers, handing back exactly the raw data that it contains; if a header doesn't contain a raw value but instead contains an already parsed value, a string representation of that header value(s) is returned.  When using the strongly-typed members, querying and enumeration is allocation-free, unless strings need to be created to represent already parsed values.
  • Loading branch information
stephentoub authored Jun 6, 2021
1 parent bd9ba59 commit 86903dd
Show file tree
Hide file tree
Showing 15 changed files with 616 additions and 148 deletions.
48 changes: 48 additions & 0 deletions src/libraries/System.Net.Http/ref/System.Net.Http.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// Changes to this file must follow the https://aka.ms/api-review process.
// ------------------------------------------------------------------------------

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace System.Net.Http
Expand Down Expand Up @@ -520,6 +521,26 @@ public EntityTagHeaderValue(string tag, bool isWeak) { }
public override string ToString() { throw null; }
public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? input, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.Http.Headers.EntityTagHeaderValue? parsedValue) { throw null; }
}
public readonly partial struct HeaderStringValues : System.Collections.Generic.IEnumerable<string>, System.Collections.Generic.IReadOnlyCollection<string>, System.Collections.IEnumerable
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public int Count { get { throw null; } }
public System.Net.Http.Headers.HeaderStringValues.Enumerator GetEnumerator() { throw null; }
System.Collections.Generic.IEnumerator<string> System.Collections.Generic.IEnumerable<string>.GetEnumerator() { throw null; }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
public override string ToString() { throw null; }
public partial struct Enumerator : System.Collections.Generic.IEnumerator<string>, System.Collections.IEnumerator, System.IDisposable
{
private object _dummy;
private int _dummyPrimitive;
public string Current { get { throw null; } }
object System.Collections.IEnumerator.Current { get { throw null; } }
public void Dispose() { }
public bool MoveNext() { throw null; }
void System.Collections.IEnumerator.Reset() { }
}
}
public sealed partial class HttpContentHeaders : System.Net.Http.Headers.HttpHeaders
{
internal HttpContentHeaders() { }
Expand All @@ -538,6 +559,7 @@ internal HttpContentHeaders() { }
public abstract partial class HttpHeaders : System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, System.Collections.Generic.IEnumerable<string>>>, System.Collections.IEnumerable
{
protected HttpHeaders() { }
public System.Net.Http.Headers.HttpHeadersNonValidated NonValidated { get { throw null; } }
public void Add(string name, System.Collections.Generic.IEnumerable<string?> values) { }
public void Add(string name, string? value) { }
public void Clear() { }
Expand All @@ -551,6 +573,32 @@ public void Clear() { }
public bool TryAddWithoutValidation(string name, string? value) { throw null; }
public bool TryGetValues(string name, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Collections.Generic.IEnumerable<string>? values) { throw null; }
}
public readonly partial struct HttpHeadersNonValidated : System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, System.Net.Http.Headers.HeaderStringValues>>, System.Collections.Generic.IReadOnlyCollection<System.Collections.Generic.KeyValuePair<string, System.Net.Http.Headers.HeaderStringValues>>, System.Collections.Generic.IReadOnlyDictionary<string, System.Net.Http.Headers.HeaderStringValues>, System.Collections.IEnumerable
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public int Count { get { throw null; } }
public System.Net.Http.Headers.HeaderStringValues this[string headerName] { get { throw null; } }
System.Collections.Generic.IEnumerable<string> System.Collections.Generic.IReadOnlyDictionary<string, System.Net.Http.Headers.HeaderStringValues>.Keys { get { throw null; } }
System.Collections.Generic.IEnumerable<System.Net.Http.Headers.HeaderStringValues> System.Collections.Generic.IReadOnlyDictionary<string, System.Net.Http.Headers.HeaderStringValues>.Values { get { throw null; } }
public bool Contains(string headerName) { throw null; }
bool System.Collections.Generic.IReadOnlyDictionary<string, System.Net.Http.Headers.HeaderStringValues>.ContainsKey(string key) { throw null; }
public System.Net.Http.Headers.HttpHeadersNonValidated.Enumerator GetEnumerator() { throw null; }
System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, System.Net.Http.Headers.HeaderStringValues>> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, System.Net.Http.Headers.HeaderStringValues>>.GetEnumerator() { throw null; }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
public bool TryGetValues(string headerName, out System.Net.Http.Headers.HeaderStringValues values) { throw null; }
bool System.Collections.Generic.IReadOnlyDictionary<string, System.Net.Http.Headers.HeaderStringValues>.TryGetValue(string key, out System.Net.Http.Headers.HeaderStringValues value) { throw null; }
public partial struct Enumerator : System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, System.Net.Http.Headers.HeaderStringValues>>, System.Collections.IEnumerator, System.IDisposable
{
private object _dummy;
private int _dummyPrimitive;
public System.Collections.Generic.KeyValuePair<string, System.Net.Http.Headers.HeaderStringValues> Current { get { throw null; } }
object System.Collections.IEnumerator.Current { get { throw null; } }
public void Dispose() { }
public bool MoveNext() { throw null; }
void System.Collections.IEnumerator.Reset() { }
}
}
public sealed partial class HttpHeaderValueCollection<T> : System.Collections.Generic.ICollection<T>, System.Collections.Generic.IEnumerable<T>, System.Collections.IEnumerable where T : class
{
internal HttpHeaderValueCollection() { }
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,13 @@
<Compile Include="System\Net\Http\Headers\EntityTagHeaderValue.cs" />
<Compile Include="System\Net\Http\Headers\GenericHeaderParser.cs" />
<Compile Include="System\Net\Http\Headers\HeaderDescriptor.cs" />
<Compile Include="System\Net\Http\Headers\HeaderStringValues.cs" />
<Compile Include="System\Net\Http\Headers\HeaderUtilities.cs" />
<Compile Include="System\Net\Http\Headers\HttpContentHeaders.cs" />
<Compile Include="System\Net\Http\Headers\HttpGeneralHeaders.cs" />
<Compile Include="System\Net\Http\Headers\HttpHeaderParser.cs" />
<Compile Include="System\Net\Http\Headers\HttpHeaders.cs" />
<Compile Include="System\Net\Http\Headers\HttpHeadersNonValidated.cs" />
<Compile Include="System\Net\Http\Headers\HttpHeaderValueCollection.cs" />
<Compile Include="System\Net\Http\Headers\HttpRequestHeaders.cs" />
<Compile Include="System\Net\Http\Headers\HttpResponseHeaders.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.Collections.Generic;

namespace System.Net.Http.Headers
{
/// <summary>Provides a collection of header string values.</summary>
public readonly struct HeaderStringValues : IReadOnlyCollection<string>
{
/// <summary>The associated header. This is used only for producing a string from <see cref="_value"/> when it's an array.</summary>
private readonly HeaderDescriptor _header;
/// <summary>A string or string array (or null if the instance is default).</summary>
private readonly object _value;

/// <summary>Initializes the instance.</summary>
/// <param name="descriptor">The header descriptor associated with the header value.</param>
/// <param name="value">The header value.</param>
internal HeaderStringValues(HeaderDescriptor descriptor, string value)
{
_header = descriptor;
_value = value;
}

/// <summary>Initializes the instance.</summary>
/// <param name="descriptor">The header descriptor associated with the header values.</param>
/// <param name="values">The header values.</param>
internal HeaderStringValues(HeaderDescriptor descriptor, string[] values)
{
_header = descriptor;
_value = values;
}

/// <summary>Gets the number of header values in the collection.</summary>
public int Count => _value switch
{
string => 1,
string[] values => values.Length,
_ => 0
};

/// <summary>Gets a string containing all the headers in the collection.</summary>
/// <returns></returns>
public override string ToString() => _value switch
{
string value => value,
string[] values => string.Join(_header.Parser is HttpHeaderParser parser && parser.SupportsMultipleValues ? parser.Separator : HttpHeaderParser.DefaultSeparator, values),
_ => string.Empty,
};

/// <summary>Gets an enumerator for all of the strings in the collection.</summary>
/// <returns></returns>
public Enumerator GetEnumerator() => new Enumerator(_value);

/// <inheritdoc/>
IEnumerator<string> IEnumerable<string>.GetEnumerator() => GetEnumerator();

/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

/// <summary>Enumerates the elements of a <see cref="HeaderStringValues"/>.</summary>
public struct Enumerator : IEnumerator<string>
{
/// <summary>If this wraps a string[], that array. Otherwise, null.</summary>
private readonly string[]? _values;
/// <summary>The current string header value. If this wraps a single string, that string.</summary>
private string? _current;
/// <summary>Current state of the iteration.</summary>
private int _index;

/// <summary>Initializes the enumerator with a string or string[].</summary>
/// <param name="value">The string or string[] value, or null if this collection is empty.</param>
internal Enumerator(object value)
{
if (value is string s)
{
_values = null;
_current = s;
}
else
{
_values = value as string[];
_current = null;
}

_index = 0;
}

/// <inheritdoc/>
public bool MoveNext()
{
int index = _index;
if (index < 0)
{
return false;
}

string[]? values = _values;
if (values != null)
{
if ((uint)index < (uint)values.Length)
{
_index = index + 1;
_current = values[index];
return true;
}

_index = -1;
return false;
}

_index = -1;
return _current != null;
}

/// <inheritdoc/>
public string Current => _current!;

/// <inheritdoc/>
object IEnumerator.Current => Current;

/// <inheritdoc/>
public void Dispose() { }

/// <inheritdoc/>
void IEnumerator.Reset() => throw new NotSupportedException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ internal static void DumpHeaders(StringBuilder sb, params HttpHeaders?[] headers
{
if (headers[i] is HttpHeaders hh)
{
foreach (KeyValuePair<string, string[]> header in hh.EnumerateWithoutValidation())
foreach (KeyValuePair<string, HeaderStringValues> header in hh.NonValidated)
{
foreach (string headerValue in header.Value)
{
Expand Down
Loading

0 comments on commit 86903dd

Please sign in to comment.