Skip to content

Commit

Permalink
#778 DefautRequestHeaders sync bug
Browse files Browse the repository at this point in the history
  • Loading branch information
Todd committed Dec 8, 2023
1 parent d9924c4 commit 439afe9
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 2 deletions.
28 changes: 26 additions & 2 deletions src/Flurl.Http/FlurlClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using Flurl.Http.Testing;
using Flurl.Util;
using System.Collections.Generic;
using System.Net.Http.Headers;
using System.Reflection;

namespace Flurl.Http
{
Expand Down Expand Up @@ -83,12 +85,34 @@ internal FlurlClient(HttpClient httpClient, string baseUrl, FlurlHttpSettings se
EventHandlers = eventHandlers ?? new List<(FlurlEventType, IFlurlEventHandler)>();

Headers = headers ?? new NameValueList<string>(false); // header names are case-insensitive https://stackoverflow.com/a/5259004/62600
foreach (var header in httpClient.DefaultRequestHeaders.SelectMany(h => h.Value, (kv, v) => (kv.Key, v))) {
if (!Headers.Contains(header.Key))

foreach (var header in GetHeadersFromHttpClient(httpClient)) {
if (!Headers.Contains(header.Name))
Headers.Add(header);
}
}

// reflection is (relatively) expensive, so keep a cache of HttpRequestHeaders properties
// https://learn.microsoft.com/en-us/dotnet/api/system.net.http.headers.httprequestheaders?#properties
private static IDictionary<string, PropertyInfo> _reqHeaderProps =
typeof(HttpRequestHeaders).GetProperties().ToDictionary(p => p.Name.ToLower(), p => p);

private static IEnumerable<(string Name, string Value)> GetHeadersFromHttpClient(HttpClient httpClient) {
foreach (var h in httpClient.DefaultRequestHeaders) {
// MS isn't making this easy. In some cases, a header value will be split into multiple values, but when iterating the collection
// there's no way to know exactly how to piece them back together. The standard says multiple values should be comma-delimited,
// but with User-Agent they need to be space-delimited. ToString() on properties like UserAgent do this correctly though, so when spinning
// through the collection we'll try to match the header name to a property and ToString() it, otherwise we'll comma-delimit the values.
if (_reqHeaderProps.TryGetValue(h.Key.Replace("-", "").ToLower(), out var prop)) {
var val = prop.GetValue(httpClient.DefaultRequestHeaders).ToString();
yield return (h.Key, val);
}
else {
yield return (h.Key, string.Join(",", h.Value));
}
}
}

/// <inheritdoc />
public string BaseUrl { get; set; }

Expand Down
13 changes: 13 additions & 0 deletions test/Flurl.Test/Http/HeadersTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Flurl.Http;
using Flurl.Http.Configuration;
Expand Down Expand Up @@ -147,6 +148,18 @@ public class ClientHeadersTests : HeadersTestsBase<IFlurlClient>
{
protected override IFlurlClient CreateContainer() => new FlurlClient();
protected override IFlurlRequest GetRequest(IFlurlClient cli) => cli.Request("http://api.com");

[Test]
public void can_copy_multi_value_header_from_HttpClient() {
var userAgent = "Mozilla/5.0 (X11; Linux i686; rv:109.0) Gecko/20100101 Firefox/120.0";
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("user-agent", userAgent);
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("foo", new[] {"a", "b", "c"});
var flurlClient = new FlurlClient(httpClient);

Assert.AreEqual(userAgent, flurlClient.Headers[0].Value);
Assert.AreEqual("a,b,c", flurlClient.Headers[1].Value);
}
}

[TestFixture]
Expand Down

0 comments on commit 439afe9

Please sign in to comment.