Skip to content

Commit 8d6cf0f

Browse files
authored
feat: Use HttpWebRequest instead of HttpClient on .NET Framework (#1853)
1 parent 7da3e59 commit 8d6cf0f

37 files changed

+1792
-481
lines changed

.editorconfig

+23-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
root = true
66

7+
file_header_template = Copyright 2020 New Relic, Inc. All rights reserved.\nSPDX-License-Identifier: Apache-2.0
8+
79
[*]
810
insert_final_newline = true
911
indent_style = space
@@ -62,10 +64,17 @@ dotnet_naming_style.pascal_case_style.capitalization = pascal_case
6264
# Use PascalCase for constant fields
6365
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
6466
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
65-
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
67+
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
6668
dotnet_naming_symbols.constant_fields.applicable_kinds = field
6769
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
6870
dotnet_naming_symbols.constant_fields.required_modifiers = const
71+
dotnet_style_operator_placement_when_wrapping = beginning_of_line
72+
tab_width = 4
73+
end_of_line = crlf
74+
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
75+
dotnet_style_prefer_compound_assignment = true:suggestion
76+
dotnet_style_prefer_simplified_interpolation = true:suggestion
77+
dotnet_style_namespace_match_folder = true:suggestion
6978

7079
###############################
7180
# C# Code Style Rules #
@@ -139,6 +148,19 @@ csharp_space_after_dot = false
139148
# Wrapping preferences
140149
csharp_preserve_single_line_statements = true
141150
csharp_preserve_single_line_blocks = true
151+
csharp_using_directive_placement = outside_namespace:silent
152+
csharp_prefer_simple_using_statement = true:suggestion
153+
csharp_style_namespace_declarations = block_scoped:silent
154+
csharp_style_prefer_method_group_conversion = true:silent
155+
csharp_style_prefer_top_level_statements = true:silent
156+
csharp_style_expression_bodied_lambdas = true:silent
157+
csharp_style_expression_bodied_local_functions = false:silent
158+
csharp_style_prefer_null_check_over_type_check = true:suggestion
159+
csharp_style_prefer_local_over_anonymous_function = true:suggestion
160+
csharp_style_prefer_index_operator = true:suggestion
161+
162+
# error if file doesn't have the required header
163+
dotnet_diagnostic.SA1633.severity = error
142164

143165
##################################
144166
# Visual Basic Code Style Rules #
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
namespace NewRelic.Agent.Core.DataTransport.Client
5+
{
6+
/// <summary>
7+
/// Constants shared between implementations of IHttpClient
8+
/// </summary>
9+
public static class Constants
10+
{
11+
public const string EmptyResponseBody = "{}";
12+
public const int CompressMinimumByteLength = 20;
13+
public const int ProtocolVersion = 17;
14+
public const string LicenseKeyParameterName = "license_key";
15+
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using NewRelic.Agent.Core.Logging;
5+
using NewRelic.Core;
6+
7+
namespace NewRelic.Agent.Core.DataTransport.Client
8+
{
9+
/// <summary>
10+
/// Simple wrapper for audit logging, shared by implementations of IHttpClient
11+
/// </summary>
12+
public static class DataTransportAuditLogger
13+
{
14+
/// <summary>
15+
/// This represents the direction or flow of data. Used for audit logs.
16+
/// </summary>
17+
public enum AuditLogDirection
18+
{
19+
Sent = 1,
20+
Received = 2
21+
}
22+
23+
/// <summary>
24+
/// This represents the origin or source of data. Used for audit logs.
25+
/// </summary>
26+
public enum AuditLogSource
27+
{
28+
Collector = 1,
29+
Beacon = 2,
30+
InstrumentedApp = 3
31+
}
32+
33+
public const string AuditLogFormat = "Data {0} from the {1} : {2}";
34+
private const string LicenseKeyParameterName = "license_key";
35+
36+
public static void Log(AuditLogDirection direction, AuditLogSource source, string uri)
37+
{
38+
if (AuditLog.IsAuditLogEnabled)
39+
{
40+
var message = string.Format(AuditLogFormat, direction, source,
41+
Strings.ObfuscateLicenseKeyInAuditLog(uri, LicenseKeyParameterName));
42+
AuditLog.Log(message);
43+
}
44+
}
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System;
5+
using System.Net;
6+
using System.Net.Http;
7+
using System.Threading.Tasks;
8+
using NewRelic.Agent.Core.DataTransport.Client.Interfaces;
9+
using NewRelic.Core.Logging;
10+
11+
namespace NewRelic.Agent.Core.DataTransport.Client
12+
{
13+
/// <summary>
14+
/// Abstract base shared by implementations of IHttpClient
15+
/// </summary>
16+
public abstract class HttpClientBase : IHttpClient
17+
{
18+
protected bool _diagnoseConnectionError = true;
19+
protected static IWebProxy _proxy = null;
20+
21+
protected HttpClientBase(IWebProxy proxy)
22+
{
23+
_proxy = proxy;
24+
}
25+
26+
public abstract Task<IHttpResponse> SendAsync(IHttpRequest request);
27+
28+
public virtual void Dispose()
29+
{
30+
#if !NETFRAMEWORK
31+
if (_lazyHttpClient.IsValueCreated)
32+
_lazyHttpClient.Value.Dispose();
33+
#endif
34+
}
35+
36+
37+
protected void DiagnoseConnectionError(string host)
38+
{
39+
_diagnoseConnectionError = false;
40+
try
41+
{
42+
if (!IPAddress.TryParse(host, out _))
43+
{
44+
Dns.GetHostEntry(host);
45+
}
46+
}
47+
catch (Exception)
48+
{
49+
Log.ErrorFormat("Unable to resolve host name \"{0}\"", host);
50+
}
51+
52+
TestConnection();
53+
}
54+
55+
protected void TestConnection()
56+
{
57+
const string testAddress = "http://www.google.com";
58+
try
59+
{
60+
#if NETFRAMEWORK
61+
using (var wc = new WebClient())
62+
{
63+
wc.Proxy = _proxy;
64+
65+
wc.DownloadString(testAddress);
66+
}
67+
#else
68+
_lazyHttpClient.Value.GetAsync(testAddress).GetAwaiter().GetResult();
69+
#endif
70+
Log.InfoFormat("Connection test to \"{0}\" succeeded", testAddress);
71+
}
72+
catch (Exception)
73+
{
74+
var message = $"Connection test to \"{testAddress}\" failed.";
75+
if (_proxy != null)
76+
{
77+
message += $" Check your proxy settings ({_proxy.GetProxy(new Uri(testAddress))})";
78+
}
79+
80+
Log.Error(message);
81+
}
82+
}
83+
84+
#if !NETFRAMEWORK
85+
// use a single HttpClient for all TestConnection() invocations
86+
private readonly Lazy<HttpClient> _lazyHttpClient = new Lazy<HttpClient>(() => new HttpClient(new HttpClientHandler() { Proxy = _proxy }));
87+
#endif
88+
}
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#if !NETFRAMEWORK
5+
using System;
6+
using System.Net.Http;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using NewRelic.Agent.Configuration;
10+
using NewRelic.Agent.Core.Config;
11+
using NewRelic.Agent.Core.DataTransport.Client.Interfaces;
12+
13+
namespace NewRelic.Agent.Core.DataTransport.Client
14+
{
15+
public class HttpClientWrapper : IHttpClientWrapper
16+
{
17+
private readonly HttpClient _httpClient;
18+
private readonly int _timeoutMilliseconds;
19+
20+
public HttpClientWrapper(HttpClient client, int timeoutMilliseconds)
21+
{
22+
_httpClient = client;
23+
_timeoutMilliseconds = timeoutMilliseconds;
24+
}
25+
26+
27+
public void Dispose()
28+
{
29+
_httpClient.Dispose();
30+
}
31+
32+
public async Task<IHttpResponseMessageWrapper> SendAsync(HttpRequestMessage message)
33+
{
34+
var cts = new CancellationTokenSource(_timeoutMilliseconds);
35+
return new HttpResponseMessageWrapper(await _httpClient.SendAsync(message, cts.Token));
36+
}
37+
38+
public TimeSpan Timeout
39+
{
40+
get
41+
{
42+
return _httpClient.Timeout;
43+
}
44+
set
45+
{
46+
_httpClient.Timeout = value;
47+
}
48+
}
49+
}
50+
}
51+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#if !NETFRAMEWORK
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Net.Http.Headers;
8+
using NewRelic.Agent.Core.DataTransport.Client.Interfaces;
9+
10+
namespace NewRelic.Agent.Core.DataTransport.Client
11+
{
12+
/// <summary>
13+
/// HttpContentHeaders wrapper to enable mocking in unit tests
14+
/// </summary>
15+
public class HttpContentHeadersWrapper : IHttpContentHeadersWrapper
16+
{
17+
private readonly HttpContentHeaders _headers;
18+
19+
public HttpContentHeadersWrapper(HttpContentHeaders headers)
20+
{
21+
_headers = headers;
22+
}
23+
24+
public ICollection<string> ContentEncoding => _headers.ContentEncoding.ToList();
25+
}
26+
}
27+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#if !NETFRAMEWORK
5+
using System.IO;
6+
using System.Net.Http;
7+
using System.Threading.Tasks;
8+
using NewRelic.Agent.Core.DataTransport.Client.Interfaces;
9+
10+
namespace NewRelic.Agent.Core.DataTransport.Client
11+
{
12+
/// <summary>
13+
/// HttpContent wrapper to enable mocking in unit tests
14+
/// </summary>
15+
public class HttpContentWrapper : IHttpContentWrapper
16+
{
17+
private readonly HttpContent _httpContent;
18+
19+
public HttpContentWrapper(HttpContent httpContent)
20+
{
21+
_httpContent = httpContent;
22+
}
23+
24+
public Task<Stream> ReadAsStreamAsync()
25+
{
26+
return _httpContent.ReadAsStreamAsync();
27+
}
28+
29+
public IHttpContentHeadersWrapper Headers => new HttpContentHeadersWrapper(_httpContent.Headers);
30+
}
31+
}
32+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Text;
7+
using NewRelic.Agent.Configuration;
8+
using NewRelic.Agent.Core.DataTransport.Client.Interfaces;
9+
10+
namespace NewRelic.Agent.Core.DataTransport.Client
11+
{
12+
/// <summary>
13+
/// Abstraction of a client request
14+
/// </summary>
15+
public class HttpRequest : IHttpRequest
16+
{
17+
private readonly IConfiguration _configuration;
18+
private Uri _uri;
19+
20+
public HttpRequest(IConfiguration configuration)
21+
{
22+
_configuration = configuration;
23+
Content = new NRHttpContent(_configuration);
24+
}
25+
26+
public IConnectionInfo ConnectionInfo { get; set; }
27+
public string Endpoint { get; set; }
28+
public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>();
29+
public Uri Uri => _uri ??= GetUri(Endpoint, ConnectionInfo); // cache the Uri
30+
31+
public IHttpContent Content { get; }
32+
public Guid RequestGuid { get; set; }
33+
34+
private Uri GetUri(string method, IConnectionInfo connectionInfo)
35+
{
36+
var uri = new StringBuilder("/agent_listener/invoke_raw_method?method=")
37+
.Append(method)
38+
.Append($"&{Constants.LicenseKeyParameterName}=")
39+
.Append(_configuration.AgentLicenseKey)
40+
.Append("&marshal_format=json")
41+
.Append("&protocol_version=")
42+
.Append(Constants.ProtocolVersion);
43+
44+
if (_configuration.AgentRunId != null)
45+
{
46+
uri.Append("&run_id=").Append(_configuration.AgentRunId);
47+
}
48+
49+
var uriBuilder = new UriBuilder(connectionInfo.HttpProtocol, connectionInfo.Host, connectionInfo.Port, uri.ToString());
50+
51+
return new Uri(uriBuilder.Uri.ToString().Replace("%3F", "?"));
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)