Skip to content

Commit f04badc

Browse files
Fix User-Agent usage in SignalR (#30764)
1 parent a331618 commit f04badc

File tree

5 files changed

+62
-3
lines changed

5 files changed

+62
-3
lines changed

src/Components/test/E2ETest/Tests/SignalRClientTest.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,21 @@ public void SignalRClientWorksWithWebSockets()
6666
Browser.Equal("SignalR Client: Echo WebSockets",
6767
() => Browser.FindElements(By.CssSelector("li")).FirstOrDefault()?.Text);
6868
}
69+
70+
[Fact]
71+
public void SignalRClientSendsUserAgent()
72+
{
73+
Browser.Exists(By.Id("hub-url")).SendKeys(
74+
new Uri(_apiServerFixture.RootUri, "/subdir/chathub").AbsoluteUri);
75+
var target = new SelectElement(Browser.Exists(By.Id("transport-type")));
76+
target.SelectByText("LongPolling");
77+
Browser.Exists(By.Id("hub-connect")).Click();
78+
79+
Browser.Equal("SignalR Client: Echo LongPolling",
80+
() => Browser.FindElements(By.CssSelector("li")).FirstOrDefault()?.Text);
81+
82+
Browser.Exists(By.Id("hub-useragent")).Click();
83+
Assert.NotNull(Browser.FindElement(By.Id("useragent")).Text);
84+
}
6985
}
7086
}

src/Components/test/testassets/BasicTestApp/SignalRClientComponent.razor

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
<option value=@HttpTransportType.WebSockets>WebSockets</option>
1414
</select>
1515
<button id="hub-connect" @onclick="Connect">Connect</button>
16+
<button id="hub-useragent" @onclick="GetUserAgentHeader">GetUserAgent</button>
1617
</p>
1718

1819
<div>Connected: @IsConnected</div>
20+
<div id="useragent">User-Agent: @userAgent</div>
1921

2022
<ul id="messagesList">
2123
@foreach (var message in messages)
@@ -29,6 +31,7 @@
2931
private HttpTransportType transportType;
3032
private HubConnection hubConnection;
3133
private List<string> messages = new List<string>();
34+
private string userAgent;
3235

3336
protected async Task Connect()
3437
{
@@ -47,6 +50,12 @@
4750
await hubConnection.SendAsync("SendMessage", "SignalR Client", $"Echo {transportType}");
4851
}
4952

53+
protected async Task GetUserAgentHeader()
54+
{
55+
userAgent = await hubConnection.InvokeAsync<string>("GetHeaderValue", "X-SignalR-User-Agent");
56+
StateHasChanged();
57+
}
58+
5059
public bool IsConnected =>
5160
hubConnection != null && hubConnection.State == HubConnectionState.Connected;
5261

src/Components/test/testassets/TestServer/ChatHub.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Threading.Tasks;
56
using Microsoft.AspNetCore.SignalR;
67

@@ -12,5 +13,25 @@ public async Task SendMessage(string user, string message)
1213
{
1314
await Clients.All.SendAsync("ReceiveMessage", user, message);
1415
}
16+
17+
public string GetHeaderValue(string headerName)
18+
{
19+
var context = Context.GetHttpContext();
20+
21+
if (context == null)
22+
{
23+
throw new InvalidOperationException("Unable to get HttpContext from request.");
24+
}
25+
26+
var headers = context.Request.Headers;
27+
28+
if (headers == null)
29+
{
30+
throw new InvalidOperationException("Unable to get headers from context.");
31+
}
32+
33+
headers.TryGetValue(headerName, out var val);
34+
return val.ToString();
35+
}
1536
}
1637
}

src/SignalR/clients/csharp/Http.Connections.Client/src/HttpConnection.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -597,8 +597,7 @@ private HttpClient CreateHttpClient()
597597
var httpClient = new HttpClient(httpMessageHandler);
598598
httpClient.Timeout = HttpClientTimeout;
599599

600-
// Start with the user agent header
601-
httpClient.DefaultRequestHeaders.Add(Constants.UserAgent, Constants.UserAgentHeader);
600+
var userSetUserAgent = false;
602601

603602
// Apply any headers configured on the HttpConnectionOptions
604603
if (_httpConnectionOptions?.Headers != null)
@@ -608,6 +607,7 @@ private HttpClient CreateHttpClient()
608607
// Check if the key is User-Agent and remove if empty string then replace if it exists.
609608
if (string.Equals(header.Key, Constants.UserAgent, StringComparison.OrdinalIgnoreCase))
610609
{
610+
userSetUserAgent = true;
611611
if (string.IsNullOrEmpty(header.Value))
612612
{
613613
httpClient.DefaultRequestHeaders.Remove(header.Key);
@@ -629,6 +629,14 @@ private HttpClient CreateHttpClient()
629629
}
630630
}
631631

632+
// Apply default user agent only if user hasn't specified one (empty or not)
633+
// Don't pre-emptively set this, some frameworks (mono) have different user agent format rules,
634+
// so allowing a user to set an empty one avoids throwing on those frameworks.
635+
if (!userSetUserAgent)
636+
{
637+
httpClient.DefaultRequestHeaders.Add(Constants.UserAgent, Constants.UserAgentHeader);
638+
}
639+
632640
httpClient.DefaultRequestHeaders.Remove("X-Requested-With");
633641
// Tell auth middleware to 401 instead of redirecting
634642
httpClient.DefaultRequestHeaders.Add("X-Requested-With", "XMLHttpRequest");

src/SignalR/clients/csharp/Http.Connections.Client/src/Internal/Constants.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@ namespace Microsoft.AspNetCore.Http.Connections.Client.Internal
1111
{
1212
internal static class Constants
1313
{
14-
public const string UserAgent = "User-Agent";
14+
public static readonly string UserAgent = "User-Agent";
1515
public static readonly string UserAgentHeader;
1616

1717
static Constants()
1818
{
19+
if (OperatingSystem.IsBrowser())
20+
{
21+
UserAgent = "X-SignalR-User-Agent";
22+
}
23+
1924
var assemblyVersion = typeof(Constants)
2025
.Assembly
2126
.GetCustomAttributes<AssemblyInformationalVersionAttribute>()

0 commit comments

Comments
 (0)