From d716c5fe201320a1a3d0e972f0c071b168b39f4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 06:23:23 +0000 Subject: [PATCH 1/3] Replicate proxy and protocol options across HTTP task projects Agent-Logs-Url: https://github.com/FrendsPlatform/Frends.HTTP/sessions/2c0a6a2a-230b-4d67-af17-87d3e474f51f Co-authored-by: MichalFrends1 <167774394+MichalFrends1@users.noreply.github.com> --- Frends.HTTP.DownloadFile/CHANGELOG.md | 4 ++ .../UnitTests.cs | 48 ++++++++++++++++++ .../Definitions/Enums.cs | 40 +++++++++++++++ .../Definitions/Options.cs | 46 +++++++++++++++++ .../Frends.HTTP.DownloadFile/DownloadFile.cs | 4 +- .../Frends.HTTP.DownloadFile/Extensions.cs | 40 +++++++++++++++ .../Frends.HTTP.DownloadFile.csproj | 2 +- Frends.HTTP.RequestBytes/CHANGELOG.md | 4 ++ .../UnitTests.cs | 43 ++++++++++++++++ .../Definitions/HttpVersion.cs | 18 +++++++ .../Definitions/Options.cs | 46 +++++++++++++++++ .../Definitions/SslVersion.cs | 25 ++++++++++ .../Frends.HTTP.RequestBytes/Extensions.cs | 40 +++++++++++++++ .../Frends.HTTP.RequestBytes.csproj | 2 +- .../Frends.HTTP.RequestBytes/RequestBytes.cs | 4 +- Frends.HTTP.SendAndReceiveBytes/CHANGELOG.md | 4 ++ .../UnitTests.cs | 50 +++++++++++++++++++ .../Definitions/HttpVersion.cs | 18 +++++++ .../Definitions/Options.cs | 46 +++++++++++++++++ .../Definitions/SslVersion.cs | 25 ++++++++++ .../Extensions.cs | 40 +++++++++++++++ .../Frends.HTTP.SendAndReceiveBytes.csproj | 2 +- .../SendAndReceiveBytes.cs | 4 +- Frends.HTTP.SendBytes/CHANGELOG.md | 4 ++ .../Frends.HTTP.SendBytes.Tests/UnitTests.cs | 50 +++++++++++++++++++ .../Definitions/HttpVersion.cs | 18 +++++++ .../Definitions/Options.cs | 46 +++++++++++++++++ .../Definitions/SslVersion.cs | 25 ++++++++++ .../Frends.HTTP.SendBytes/Extensions.cs | 40 +++++++++++++++ .../Frends.HTTP.SendBytes.csproj | 2 +- .../Frends.HTTP.SendBytes/SendBytes.cs | 4 +- 31 files changed, 736 insertions(+), 8 deletions(-) create mode 100644 Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Definitions/HttpVersion.cs create mode 100644 Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Definitions/SslVersion.cs create mode 100644 Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Definitions/HttpVersion.cs create mode 100644 Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Definitions/SslVersion.cs create mode 100644 Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Definitions/HttpVersion.cs create mode 100644 Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Definitions/SslVersion.cs diff --git a/Frends.HTTP.DownloadFile/CHANGELOG.md b/Frends.HTTP.DownloadFile/CHANGELOG.md index efa744c..a912e7b 100644 --- a/Frends.HTTP.DownloadFile/CHANGELOG.md +++ b/Frends.HTTP.DownloadFile/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [1.5.0] - 2026-05-13 +### Added +- Added UseProxy, ProxyUrl, ProxyUsername, ProxyPassword, HttpProtocolVersion, and SslProtocolVersion options with proxy handler and protocol configuration support. + ## [1.4.0] - 2026-03-02 ### Added - Added CertificateStoreLocation option to allow selection between CurrentUser and LocalMachine certificate stores when using certificate authentication. diff --git a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs index ff7e677..f237c6c 100644 --- a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs +++ b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Net.Http; +using System.Net; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; @@ -546,4 +547,51 @@ public void CorrectStoreSearched(CertificateStoreLocation storeLocation, string StringAssert.Contains(ex.Message, $"Certificate with thumbprint: 'INVALIDTHUMBPRINT' not found in {storeLocationText} cert store."); } + + [TestMethod] + public void HandlerShouldUseConfiguredProxy() + { + var handler = new HttpClientHandler(); + var options = new Options + { + UseProxy = true, + ProxyUrl = "http://proxy.example.com:8080", + ProxyUsername = "proxy-user", + ProxyPassword = "proxy-password" + }; + + handler.SetHandlerSettingsBasedOnOptions(options); + + var proxy = handler.Proxy as WebProxy; + Assert.IsNotNull(proxy); + Assert.IsTrue(handler.UseProxy); + Assert.AreEqual(new Uri("http://proxy.example.com:8080/"), proxy.Address); + var credentials = proxy.Credentials.GetCredential(proxy.Address, string.Empty); + Assert.AreEqual("proxy-user", credentials.UserName); + Assert.AreEqual("proxy-password", credentials.Password); + } + + [TestMethod] + public async Task RequestShouldSetTls12And13WhenConfigured() + { + var input = new Input + { + Url = _targetFileAddress, + FilePath = _filePath, + Headers = null + }; + + var options = new Options + { + ConnectionTimeoutSeconds = 60, + SslProtocolVersion = SslVersion.Tls12And13, + HttpProtocolVersion = HttpVersion.Http20, + Overwrite = true + }; + + var result = await HTTP.DownloadFile(input, options, default); + + Assert.IsTrue(result.Success); + } + } diff --git a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/Enums.cs b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/Enums.cs index 5fa6856..738d336 100644 --- a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/Enums.cs +++ b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/Enums.cs @@ -49,3 +49,43 @@ public enum Authentication /// ClientCertificate } + + +/// +/// HTTP protocol version used for requests. +/// +public enum HttpVersion +{ + /// + /// HTTP/1.1 - default, widely supported. + /// + Http11, + /// + /// HTTP/2 - multiplexed, requires server support. + /// If the server does not support HTTP/2 via ALPN, the request will fail with an exception instead of falling back to HTTP/1.1. + /// + Http20 +} + +/// +/// SSL/TLS protocol version used for secure connections. +/// +public enum SslVersion +{ + /// + /// OS decides the protocol version. + /// + Default, + /// + /// TLS 1.2 only. + /// + Tls12, + /// + /// TLS 1.3 only. + /// + Tls13, + /// + /// TLS 1.2 and TLS 1.3 - use when server compatibility is uncertain. + /// + Tls12And13 +} diff --git a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/Options.cs b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/Options.cs index 0b09d00..7b40609 100644 --- a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/Options.cs +++ b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Definitions/Options.cs @@ -38,6 +38,37 @@ public class Options [UIHint(nameof(Authentication), "", Authentication.OAuth)] public string Token { get; set; } + /// + /// Whether to route requests through a proxy server. When set to true, the ProxyUrl must be provided. + /// + /// false + [DefaultValue(false)] + public bool UseProxy { get; set; } = false; + + /// + /// Proxy URL used for the request. + /// + /// http://proxy.example.com:8080 + [UIHint(nameof(UseProxy), "", true)] + public string ProxyUrl { get; set; } + + /// + /// Username for proxy authentication. Leave empty if proxy does not require authentication. + /// Both username and password must be provided together or left empty. + /// + /// Username + [UIHint(nameof(UseProxy), "", true)] + public string ProxyUsername { get; set; } + + /// + /// Password for proxy authentication. Leave empty if proxy does not require authentication. + /// Both username and password must be provided together or left empty. + /// + /// Password123 + [PasswordPropertyText] + [UIHint(nameof(UseProxy), "", true)] + public string ProxyPassword { get; set; } + /// /// Specifies where the Client Certificate should be loaded from. /// @@ -137,6 +168,21 @@ public class Options [DefaultValue(true)] public bool AutomaticCookieHandling { get; set; } = true; + /// + /// HTTP protocol version to use for requests. HTTP/1.1 is the default and most compatible option. + /// + /// Http11 + [DefaultValue(HttpVersion.Http11)] + public HttpVersion HttpProtocolVersion { get; set; } = HttpVersion.Http11; + + /// + /// SSL/TLS protocol version to use for secure connections. Default lets the OS decide, which matches previous behavior. + /// Use Tls12 or Tls12And13 if the server requires a specific version. + /// + /// Default + [DefaultValue(SslVersion.Default)] + public SslVersion SslProtocolVersion { get; set; } = SslVersion.Default; + /// /// If set to true, an existing file at the target path will be overwritten during download. /// diff --git a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/DownloadFile.cs b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/DownloadFile.cs index ed509ac..5e26c56 100644 --- a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/DownloadFile.cs +++ b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/DownloadFile.cs @@ -135,7 +135,9 @@ private static string GetHttpClientCacheKey(Options options) + $":{options.ClientCertificateFilePath}:{options.ClientCertificateInBase64}:{options.ClientCertificateKeyPhrase}" + $":{options.CertificateThumbprint}:{options.LoadEntireChainForCertificate}:{options.ConnectionTimeoutSeconds}" + $":{options.FollowRedirects}:{options.AllowInvalidCertificate}:{options.AllowInvalidResponseContentTypeCharSet}" - + $":{options.ThrowExceptionOnErrorResponse}:{options.AutomaticCookieHandling}"; + + $":{options.ThrowExceptionOnErrorResponse}:{options.AutomaticCookieHandling}" + + $":{options.SslProtocolVersion}:{options.HttpProtocolVersion}" + + $":{options.UseProxy}:{options.ProxyUrl}:{options.ProxyUsername}:{options.ProxyPassword}"; } private static void OnPluginUnloadingRequested(AssemblyLoadContext obj) diff --git a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Extensions.cs b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Extensions.cs index 8575040..82ab3bf 100644 --- a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Extensions.cs +++ b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Extensions.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Http; using System.Security.Cryptography.X509Certificates; +using System.Security.Authentication; using System.Text.RegularExpressions; namespace Frends.HTTP.DownloadFile; @@ -39,8 +40,35 @@ internal static void SetHandlerSettingsBasedOnOptions(this HttpClientHandler han handler.AllowAutoRedirect = options.FollowRedirects; handler.UseCookies = options.AutomaticCookieHandling; + if (options.UseProxy && !string.IsNullOrWhiteSpace(options.ProxyUrl)) + { + handler.Proxy = new WebProxy(options.ProxyUrl); + handler.UseProxy = true; + + var hasUsername = !string.IsNullOrWhiteSpace(options.ProxyUsername); + var hasPassword = !string.IsNullOrWhiteSpace(options.ProxyPassword); + + if (hasUsername != hasPassword) + { + throw new ArgumentException("Both ProxyUsername and ProxyPassword must be provided together or left empty."); + } + + if (hasUsername) + { + handler.Proxy.Credentials = new NetworkCredential(options.ProxyUsername, options.ProxyPassword); + } + } + if (options.AllowInvalidCertificate) handler.ServerCertificateCustomValidationCallback = (a, b, c, d) => true; + + handler.SslProtocols = options.SslProtocolVersion switch + { + SslVersion.Tls12 => SslProtocols.Tls12, + SslVersion.Tls13 => SslProtocols.Tls13, + SslVersion.Tls12And13 => SslProtocols.Tls12 | SslProtocols.Tls13, + _ => SslProtocols.None + }; } internal static void SetDefaultRequestHeadersBasedOnOptions(this HttpClient httpClient, Options options) @@ -49,6 +77,18 @@ internal static void SetDefaultRequestHeadersBasedOnOptions(this HttpClient http httpClient.DefaultRequestHeaders.ExpectContinue = false; httpClient.DefaultRequestHeaders.TryAddWithoutValidation("content-type", "application/json"); httpClient.Timeout = TimeSpan.FromSeconds(Convert.ToDouble(options.ConnectionTimeoutSeconds)); + + httpClient.DefaultRequestVersion = options.HttpProtocolVersion switch + { + Definitions.HttpVersion.Http20 => System.Net.HttpVersion.Version20, + _ => System.Net.HttpVersion.Version11 + }; + + httpClient.DefaultVersionPolicy = options.HttpProtocolVersion switch + { + Definitions.HttpVersion.Http20 => HttpVersionPolicy.RequestVersionExact, + _ => HttpVersionPolicy.RequestVersionOrLower + }; } private static X509Certificate[] GetCertificates(Options options) diff --git a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.csproj b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.csproj index f1e21d7..e7f88fb 100644 --- a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.csproj +++ b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.csproj @@ -2,7 +2,7 @@ net6.0 - 1.4.0 + 1.5.0 Frends Frends Frends diff --git a/Frends.HTTP.RequestBytes/CHANGELOG.md b/Frends.HTTP.RequestBytes/CHANGELOG.md index 1da35ca..15c1379 100644 --- a/Frends.HTTP.RequestBytes/CHANGELOG.md +++ b/Frends.HTTP.RequestBytes/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [1.5.0] - 2026-05-13 +### Added +- Added UseProxy, ProxyUrl, ProxyUsername, ProxyPassword, HttpProtocolVersion, and SslProtocolVersion options with proxy handler and protocol configuration support. + ## [1.4.0] - 2026-03-02 ### Added - Added CertificateStoreLocation option to allow selection between CurrentUser and LocalMachine certificate stores when using certificate authentication. diff --git a/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes.Tests/UnitTests.cs b/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes.Tests/UnitTests.cs index 83da029..22269fb 100644 --- a/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes.Tests/UnitTests.cs +++ b/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes.Tests/UnitTests.cs @@ -7,6 +7,7 @@ using RichardSzalay.MockHttp; using Assert = NUnit.Framework.Assert; using Frends.HTTP.RequestBytes.Definitions; +using Definitions = Frends.HTTP.RequestBytes.Definitions; using System.Collections.Generic; using System.Text; using Method = Frends.HTTP.RequestBytes.Definitions.Method; @@ -399,6 +400,48 @@ public async Task RequestShouldSetEncodingWithContentTypeCharsetIgnoringCase() _mockHttpMessageHandler.VerifyNoOutstandingExpectation(); ClassicAssert.AreEqual("foo åäö", Encoding.UTF8.GetString(result.Body)); } + + [TestMethod] + public void HandlerShouldUseConfiguredProxy() + { + var handler = new HttpClientHandler(); + var options = new Options + { + UseProxy = true, + ProxyUrl = "http://proxy.example.com:8080", + ProxyUsername = "proxy-user", + ProxyPassword = "proxy-password" + }; + + handler.SetHandlerSettingsBasedOnOptions(options); + + var proxy = handler.Proxy as WebProxy; + Assert.That(proxy, Is.Not.Null); + ClassicAssert.IsTrue(handler.UseProxy); + ClassicAssert.AreEqual(new Uri("http://proxy.example.com:8080/"), proxy.Address); + var credentials = proxy.Credentials.GetCredential(proxy.Address, string.Empty); + ClassicAssert.AreEqual("proxy-user", credentials.UserName); + ClassicAssert.AreEqual("proxy-password", credentials.Password); + } + + [TestMethod] + public async Task RequestShouldSetTls12And13WhenConfigured() + { + HTTP.ClientFactory = new HttpClientFactory(); + + var input = GetInputParams(url: "https://httpbin.org/anything"); + var options = new Options + { + ConnectionTimeoutSeconds = 60, + SslProtocolVersion = SslVersion.Tls12And13, + HttpProtocolVersion = Definitions.HttpVersion.Http20 + }; + + var result = await HTTP.RequestBytes(input, options, CancellationToken.None); + + ClassicAssert.AreEqual(200, result.StatusCode); + } + } public class MockHttpClientFactory : IHttpClientFactory diff --git a/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Definitions/HttpVersion.cs b/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Definitions/HttpVersion.cs new file mode 100644 index 0000000..5de4a58 --- /dev/null +++ b/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Definitions/HttpVersion.cs @@ -0,0 +1,18 @@ +namespace Frends.HTTP.RequestBytes.Definitions +{ + /// + /// HTTP protocol version used for requests. + /// + public enum HttpVersion + { + /// + /// HTTP/1.1 - default, widely supported. + /// + Http11, + /// + /// HTTP/2 - multiplexed, requires server support. + /// If the server does not support HTTP/2 via ALPN, the request will fail with an exception instead of falling back to HTTP/1.1. + /// + Http20 + } +} diff --git a/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Definitions/Options.cs b/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Definitions/Options.cs index d652b2a..3ead407 100644 --- a/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Definitions/Options.cs +++ b/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Definitions/Options.cs @@ -37,6 +37,37 @@ public class Options [UIHint(nameof(Authentication), "", Authentication.OAuth)] public string Token { get; set; } + /// + /// Whether to route requests through a proxy server. When set to true, the ProxyUrl must be provided. + /// + /// false + [DefaultValue(false)] + public bool UseProxy { get; set; } = false; + + /// + /// Proxy URL used for the request. + /// + /// http://proxy.example.com:8080 + [UIHint(nameof(UseProxy), "", true)] + public string ProxyUrl { get; set; } + + /// + /// Username for proxy authentication. Leave empty if proxy does not require authentication. + /// Both username and password must be provided together or left empty. + /// + /// Username + [UIHint(nameof(UseProxy), "", true)] + public string ProxyUsername { get; set; } + + /// + /// Password for proxy authentication. Leave empty if proxy does not require authentication. + /// Both username and password must be provided together or left empty. + /// + /// Password123 + [PasswordPropertyText] + [UIHint(nameof(UseProxy), "", true)] + public string ProxyPassword { get; set; } + /// /// Specifies where the Client Certificate should be loaded from. /// @@ -135,4 +166,19 @@ public class Options /// true [DefaultValue(true)] public bool AutomaticCookieHandling { get; set; } = true; + + /// + /// HTTP protocol version to use for requests. HTTP/1.1 is the default and most compatible option. + /// + /// Http11 + [DefaultValue(HttpVersion.Http11)] + public HttpVersion HttpProtocolVersion { get; set; } = HttpVersion.Http11; + + /// + /// SSL/TLS protocol version to use for secure connections. Default lets the OS decide, which matches previous behavior. + /// Use Tls12 or Tls12And13 if the server requires a specific version. + /// + /// Default + [DefaultValue(SslVersion.Default)] + public SslVersion SslProtocolVersion { get; set; } = SslVersion.Default; } diff --git a/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Definitions/SslVersion.cs b/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Definitions/SslVersion.cs new file mode 100644 index 0000000..687a87e --- /dev/null +++ b/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Definitions/SslVersion.cs @@ -0,0 +1,25 @@ +namespace Frends.HTTP.RequestBytes.Definitions +{ + /// + /// SSL/TLS protocol version used for secure connections. + /// + public enum SslVersion + { + /// + /// OS decides the protocol version. + /// + Default, + /// + /// TLS 1.2 only. + /// + Tls12, + /// + /// TLS 1.3 only. + /// + Tls13, + /// + /// TLS 1.2 and TLS 1.3 - use when server compatibility is uncertain. + /// + Tls12And13 + } +} diff --git a/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Extensions.cs b/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Extensions.cs index 41b5f94..2a79c1d 100644 --- a/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Extensions.cs +++ b/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Extensions.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Net; using System.Security.Cryptography.X509Certificates; +using System.Security.Authentication; using System.Text.RegularExpressions; using Frends.HTTP.RequestBytes.Definitions; @@ -43,10 +44,37 @@ internal static void SetHandlerSettingsBasedOnOptions(this HttpClientHandler han handler.AllowAutoRedirect = options.FollowRedirects; handler.UseCookies = options.AutomaticCookieHandling; + if (options.UseProxy && !string.IsNullOrWhiteSpace(options.ProxyUrl)) + { + handler.Proxy = new WebProxy(options.ProxyUrl); + handler.UseProxy = true; + + var hasUsername = !string.IsNullOrWhiteSpace(options.ProxyUsername); + var hasPassword = !string.IsNullOrWhiteSpace(options.ProxyPassword); + + if (hasUsername != hasPassword) + { + throw new ArgumentException("Both ProxyUsername and ProxyPassword must be provided together or left empty."); + } + + if (hasUsername) + { + handler.Proxy.Credentials = new NetworkCredential(options.ProxyUsername, options.ProxyPassword); + } + } + if (options.AllowInvalidCertificate) { handler.ServerCertificateCustomValidationCallback = (a, b, c, d) => true; } + + handler.SslProtocols = options.SslProtocolVersion switch + { + SslVersion.Tls12 => SslProtocols.Tls12, + SslVersion.Tls13 => SslProtocols.Tls13, + SslVersion.Tls12And13 => SslProtocols.Tls12 | SslProtocols.Tls13, + _ => SslProtocols.None + }; } internal static void SetDefaultRequestHeadersBasedOnOptions(this HttpClient httpClient, Options options) @@ -55,6 +83,18 @@ internal static void SetDefaultRequestHeadersBasedOnOptions(this HttpClient http httpClient.DefaultRequestHeaders.ExpectContinue = false; httpClient.DefaultRequestHeaders.TryAddWithoutValidation("content-type", "application/json"); httpClient.Timeout = TimeSpan.FromSeconds(Convert.ToDouble(options.ConnectionTimeoutSeconds)); + + httpClient.DefaultRequestVersion = options.HttpProtocolVersion switch + { + Definitions.HttpVersion.Http20 => System.Net.HttpVersion.Version20, + _ => System.Net.HttpVersion.Version11 + }; + + httpClient.DefaultVersionPolicy = options.HttpProtocolVersion switch + { + Definitions.HttpVersion.Http20 => HttpVersionPolicy.RequestVersionExact, + _ => HttpVersionPolicy.RequestVersionOrLower + }; } private static X509Certificate[] GetCertificates(Options options) diff --git a/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes.csproj b/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes.csproj index 94040f2..be1defb 100644 --- a/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes.csproj +++ b/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes.csproj @@ -2,7 +2,7 @@ net6.0 - 1.4.0 + 1.5.0 Frends Frends Frends diff --git a/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/RequestBytes.cs b/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/RequestBytes.cs index 0fbc4fa..77f76bf 100644 --- a/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/RequestBytes.cs +++ b/Frends.HTTP.RequestBytes/Frends.HTTP.RequestBytes/RequestBytes.cs @@ -141,7 +141,9 @@ private static string GetHttpClientCacheKey(Options options) + $":{options.ClientCertificateFilePath}:{options.ClientCertificateInBase64}:{options.ClientCertificateKeyPhrase}" + $":{options.CertificateThumbprint}:{options.LoadEntireChainForCertificate}:{options.ConnectionTimeoutSeconds}" + $":{options.FollowRedirects}:{options.AllowInvalidCertificate}:{options.AllowInvalidResponseContentTypeCharSet}" - + $":{options.ThrowExceptionOnErrorResponse}:{options.AutomaticCookieHandling}"; + + $":{options.ThrowExceptionOnErrorResponse}:{options.AutomaticCookieHandling}" + + $":{options.SslProtocolVersion}:{options.HttpProtocolVersion}" + + $":{options.UseProxy}:{options.ProxyUrl}:{options.ProxyUsername}:{options.ProxyPassword}"; } private static async Task GetHttpRequestResponseAsync( diff --git a/Frends.HTTP.SendAndReceiveBytes/CHANGELOG.md b/Frends.HTTP.SendAndReceiveBytes/CHANGELOG.md index 13c581f..d3286d0 100644 --- a/Frends.HTTP.SendAndReceiveBytes/CHANGELOG.md +++ b/Frends.HTTP.SendAndReceiveBytes/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [1.5.0] - 2026-05-13 +### Added +- Added UseProxy, ProxyUrl, ProxyUsername, ProxyPassword, HttpProtocolVersion, and SslProtocolVersion options with proxy handler and protocol configuration support. + ## [1.4.0] - 2026-03-02 ### Added - Added CertificateStoreLocation option to allow selection between CurrentUser and LocalMachine certificate stores when using certificate authentication. diff --git a/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes.Tests/UnitTests.cs b/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes.Tests/UnitTests.cs index 082f9e2..2da0630 100644 --- a/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes.Tests/UnitTests.cs +++ b/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes.Tests/UnitTests.cs @@ -7,6 +7,7 @@ using RichardSzalay.MockHttp; using Assert = NUnit.Framework.Assert; using Frends.HTTP.SendAndReceiveBytes.Definitions; +using Definitions = Frends.HTTP.SendAndReceiveBytes.Definitions; using System.Collections.Generic; using System.Text; using Method = Frends.HTTP.SendAndReceiveBytes.Definitions.Method; @@ -335,6 +336,55 @@ public void CorrectStoreSearched(CertificateStoreLocation storeLocation, string Assert.That(ex.Message.Contains( $"Certificate with thumbprint: 'INVALIDTHUMBPRINT' not found in {storeLocationText} cert store.")); } + + [TestMethod] + public void HandlerShouldUseConfiguredProxy() + { + var handler = new HttpClientHandler(); + var options = new Options + { + UseProxy = true, + ProxyUrl = "http://proxy.example.com:8080", + ProxyUsername = "proxy-user", + ProxyPassword = "proxy-password" + }; + + handler.SetHandlerSettingsBasedOnOptions(options); + + var proxy = handler.Proxy as WebProxy; + Assert.That(proxy, Is.Not.Null); + ClassicAssert.IsTrue(handler.UseProxy); + ClassicAssert.AreEqual(new Uri("http://proxy.example.com:8080/"), proxy.Address); + var credentials = proxy.Credentials.GetCredential(proxy.Address, string.Empty); + ClassicAssert.AreEqual("proxy-user", credentials.UserName); + ClassicAssert.AreEqual("proxy-password", credentials.Password); + } + + [TestMethod] + public async Task RequestShouldSetTls12And13WhenConfigured() + { + HTTP.ClientFactory = new HttpClientFactory(); + + var input = new Input + { + Method = Method.POST, + Url = "https://httpbin.org/anything", + Headers = Array.Empty
(), + ContentBytes = Encoding.UTF8.GetBytes("test") + }; + + var options = new Options + { + ConnectionTimeoutSeconds = 60, + SslProtocolVersion = SslVersion.Tls12And13, + HttpProtocolVersion = Definitions.HttpVersion.Http20 + }; + + var result = await HTTP.SendAndReceiveBytes(input, options, CancellationToken.None); + + ClassicAssert.AreEqual(200, result.StatusCode); + } + } public class MockHttpClientFactory : IHttpClientFactory diff --git a/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Definitions/HttpVersion.cs b/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Definitions/HttpVersion.cs new file mode 100644 index 0000000..135171d --- /dev/null +++ b/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Definitions/HttpVersion.cs @@ -0,0 +1,18 @@ +namespace Frends.HTTP.SendAndReceiveBytes.Definitions +{ + /// + /// HTTP protocol version used for requests. + /// + public enum HttpVersion + { + /// + /// HTTP/1.1 - default, widely supported. + /// + Http11, + /// + /// HTTP/2 - multiplexed, requires server support. + /// If the server does not support HTTP/2 via ALPN, the request will fail with an exception instead of falling back to HTTP/1.1. + /// + Http20 + } +} diff --git a/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Definitions/Options.cs b/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Definitions/Options.cs index f5cd482..d223863 100644 --- a/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Definitions/Options.cs +++ b/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Definitions/Options.cs @@ -39,6 +39,37 @@ public class Options [UIHint(nameof(Authentication), "", Authentication.OAuth)] public string Token { get; set; } + /// + /// Whether to route requests through a proxy server. When set to true, the ProxyUrl must be provided. + /// + /// false + [DefaultValue(false)] + public bool UseProxy { get; set; } = false; + + /// + /// Proxy URL used for the request. + /// + /// http://proxy.example.com:8080 + [UIHint(nameof(UseProxy), "", true)] + public string ProxyUrl { get; set; } + + /// + /// Username for proxy authentication. Leave empty if proxy does not require authentication. + /// Both username and password must be provided together or left empty. + /// + /// Username + [UIHint(nameof(UseProxy), "", true)] + public string ProxyUsername { get; set; } + + /// + /// Password for proxy authentication. Leave empty if proxy does not require authentication. + /// Both username and password must be provided together or left empty. + /// + /// Password123 + [PasswordPropertyText] + [UIHint(nameof(UseProxy), "", true)] + public string ProxyPassword { get; set; } + /// /// Specifies where the Client Certificate should be loaded from. /// @@ -138,4 +169,19 @@ public class Options /// true [DefaultValue(true)] public bool AutomaticCookieHandling { get; set; } = true; + + /// + /// HTTP protocol version to use for requests. HTTP/1.1 is the default and most compatible option. + /// + /// Http11 + [DefaultValue(HttpVersion.Http11)] + public HttpVersion HttpProtocolVersion { get; set; } = HttpVersion.Http11; + + /// + /// SSL/TLS protocol version to use for secure connections. Default lets the OS decide, which matches previous behavior. + /// Use Tls12 or Tls12And13 if the server requires a specific version. + /// + /// Default + [DefaultValue(SslVersion.Default)] + public SslVersion SslProtocolVersion { get; set; } = SslVersion.Default; } diff --git a/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Definitions/SslVersion.cs b/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Definitions/SslVersion.cs new file mode 100644 index 0000000..2c9b944 --- /dev/null +++ b/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Definitions/SslVersion.cs @@ -0,0 +1,25 @@ +namespace Frends.HTTP.SendAndReceiveBytes.Definitions +{ + /// + /// SSL/TLS protocol version used for secure connections. + /// + public enum SslVersion + { + /// + /// OS decides the protocol version. + /// + Default, + /// + /// TLS 1.2 only. + /// + Tls12, + /// + /// TLS 1.3 only. + /// + Tls13, + /// + /// TLS 1.2 and TLS 1.3 - use when server compatibility is uncertain. + /// + Tls12And13 + } +} diff --git a/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Extensions.cs b/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Extensions.cs index fdd3fe5..c8dfda6 100644 --- a/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Extensions.cs +++ b/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Extensions.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Net; using System.Security.Cryptography.X509Certificates; +using System.Security.Authentication; using System.Text.RegularExpressions; using System.Diagnostics.CodeAnalysis; using Frends.HTTP.SendAndReceiveBytes.Definitions; @@ -41,10 +42,37 @@ internal static void SetHandlerSettingsBasedOnOptions(this HttpClientHandler han handler.AllowAutoRedirect = options.FollowRedirects; handler.UseCookies = options.AutomaticCookieHandling; + if (options.UseProxy && !string.IsNullOrWhiteSpace(options.ProxyUrl)) + { + handler.Proxy = new WebProxy(options.ProxyUrl); + handler.UseProxy = true; + + var hasUsername = !string.IsNullOrWhiteSpace(options.ProxyUsername); + var hasPassword = !string.IsNullOrWhiteSpace(options.ProxyPassword); + + if (hasUsername != hasPassword) + { + throw new ArgumentException("Both ProxyUsername and ProxyPassword must be provided together or left empty."); + } + + if (hasUsername) + { + handler.Proxy.Credentials = new NetworkCredential(options.ProxyUsername, options.ProxyPassword); + } + } + if (options.AllowInvalidCertificate) { handler.ServerCertificateCustomValidationCallback = (a, b, c, d) => true; } + + handler.SslProtocols = options.SslProtocolVersion switch + { + SslVersion.Tls12 => SslProtocols.Tls12, + SslVersion.Tls13 => SslProtocols.Tls13, + SslVersion.Tls12And13 => SslProtocols.Tls12 | SslProtocols.Tls13, + _ => SslProtocols.None + }; } internal static void SetDefaultRequestHeadersBasedOnOptions(this HttpClient httpClient, Options options) @@ -53,6 +81,18 @@ internal static void SetDefaultRequestHeadersBasedOnOptions(this HttpClient http httpClient.DefaultRequestHeaders.ExpectContinue = false; httpClient.DefaultRequestHeaders.TryAddWithoutValidation("content-type", "application/json"); httpClient.Timeout = TimeSpan.FromSeconds(Convert.ToDouble(options.ConnectionTimeoutSeconds)); + + httpClient.DefaultRequestVersion = options.HttpProtocolVersion switch + { + Definitions.HttpVersion.Http20 => System.Net.HttpVersion.Version20, + _ => System.Net.HttpVersion.Version11 + }; + + httpClient.DefaultVersionPolicy = options.HttpProtocolVersion switch + { + Definitions.HttpVersion.Http20 => HttpVersionPolicy.RequestVersionExact, + _ => HttpVersionPolicy.RequestVersionOrLower + }; } private static X509Certificate[] GetCertificates(Options options) diff --git a/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes.csproj b/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes.csproj index faf664b..7d53500 100644 --- a/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes.csproj +++ b/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes.csproj @@ -2,7 +2,7 @@ net6.0 - 1.4.0 + 1.5.0 Frends Frends Frends diff --git a/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/SendAndReceiveBytes.cs b/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/SendAndReceiveBytes.cs index 8017887..e6a4f8e 100644 --- a/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/SendAndReceiveBytes.cs +++ b/Frends.HTTP.SendAndReceiveBytes/Frends.HTTP.SendAndReceiveBytes/SendAndReceiveBytes.cs @@ -121,7 +121,9 @@ private static string GetHttpClientCacheKey(Options options) + $":{options.ClientCertificateFilePath}:{options.ClientCertificateInBase64}:{options.ClientCertificateKeyPhrase}" + $":{options.CertificateThumbprint}:{options.LoadEntireChainForCertificate}:{options.ConnectionTimeoutSeconds}" + $":{options.FollowRedirects}:{options.AllowInvalidCertificate}:{options.AllowInvalidResponseContentTypeCharSet}" - + $":{options.ThrowExceptionOnErrorResponse}:{options.AutomaticCookieHandling}"; + + $":{options.ThrowExceptionOnErrorResponse}:{options.AutomaticCookieHandling}" + + $":{options.SslProtocolVersion}:{options.HttpProtocolVersion}" + + $":{options.UseProxy}:{options.ProxyUrl}:{options.ProxyUsername}:{options.ProxyPassword}"; } private static async Task GetHttpRequestResponseAsync( diff --git a/Frends.HTTP.SendBytes/CHANGELOG.md b/Frends.HTTP.SendBytes/CHANGELOG.md index 15b2e3f..900b40d 100644 --- a/Frends.HTTP.SendBytes/CHANGELOG.md +++ b/Frends.HTTP.SendBytes/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [1.6.0] - 2026-05-13 +### Added +- Added UseProxy, ProxyUrl, ProxyUsername, ProxyPassword, HttpProtocolVersion, and SslProtocolVersion options with proxy handler and protocol configuration support. + ## [1.5.0] - 2026-03-02 ### Added - Added CertificateStoreLocation option to allow selection between CurrentUser and LocalMachine certificate stores when using certificate authentication. diff --git a/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes.Tests/UnitTests.cs b/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes.Tests/UnitTests.cs index 2955928..ea71a8a 100644 --- a/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes.Tests/UnitTests.cs +++ b/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes.Tests/UnitTests.cs @@ -7,6 +7,7 @@ using RichardSzalay.MockHttp; using Assert = NUnit.Framework.Assert; using Frends.HTTP.SendBytes.Definitions; +using Definitions = Frends.HTTP.SendBytes.Definitions; using System.Collections.Generic; using System.Text; using Method = Frends.HTTP.SendBytes.Definitions.Method; @@ -335,6 +336,55 @@ public async Task PatchShouldComeThrough() _mockHttpMessageHandler.VerifyNoOutstandingExpectation(); ClassicAssert.AreEqual("foo åäö", result.Body); } + + [TestMethod] + public void HandlerShouldUseConfiguredProxy() + { + var handler = new HttpClientHandler(); + var options = new Options + { + UseProxy = true, + ProxyUrl = "http://proxy.example.com:8080", + ProxyUsername = "proxy-user", + ProxyPassword = "proxy-password" + }; + + handler.SetHandlerSettingsBasedOnOptions(options); + + var proxy = handler.Proxy as WebProxy; + Assert.That(proxy, Is.Not.Null); + ClassicAssert.IsTrue(handler.UseProxy); + ClassicAssert.AreEqual(new Uri("http://proxy.example.com:8080/"), proxy.Address); + var credentials = proxy.Credentials.GetCredential(proxy.Address, string.Empty); + ClassicAssert.AreEqual("proxy-user", credentials.UserName); + ClassicAssert.AreEqual("proxy-password", credentials.Password); + } + + [TestMethod] + public async Task RequestShouldSetTls12And13WhenConfigured() + { + HTTP.ClientFactory = new HttpClientFactory(); + + var input = new Input + { + Method = Method.POST, + Url = "https://httpbin.org/anything", + Headers = Array.Empty
(), + ContentBytes = Encoding.UTF8.GetBytes("test") + }; + + var options = new Options + { + ConnectionTimeoutSeconds = 60, + SslProtocolVersion = SslVersion.Tls12And13, + HttpProtocolVersion = Definitions.HttpVersion.Http20 + }; + + var result = await HTTP.SendBytes(input, options, CancellationToken.None); + + ClassicAssert.AreEqual(200, result.StatusCode); + } + } public class MockHttpClientFactory : IHttpClientFactory diff --git a/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Definitions/HttpVersion.cs b/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Definitions/HttpVersion.cs new file mode 100644 index 0000000..989daf2 --- /dev/null +++ b/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Definitions/HttpVersion.cs @@ -0,0 +1,18 @@ +namespace Frends.HTTP.SendBytes.Definitions +{ + /// + /// HTTP protocol version used for requests. + /// + public enum HttpVersion + { + /// + /// HTTP/1.1 - default, widely supported. + /// + Http11, + /// + /// HTTP/2 - multiplexed, requires server support. + /// If the server does not support HTTP/2 via ALPN, the request will fail with an exception instead of falling back to HTTP/1.1. + /// + Http20 + } +} diff --git a/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Definitions/Options.cs b/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Definitions/Options.cs index 9479810..921e26a 100644 --- a/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Definitions/Options.cs +++ b/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Definitions/Options.cs @@ -37,6 +37,37 @@ public class Options [UIHint(nameof(Authentication), "", Authentication.OAuth)] public string Token { get; set; } + /// + /// Whether to route requests through a proxy server. When set to true, the ProxyUrl must be provided. + /// + /// false + [DefaultValue(false)] + public bool UseProxy { get; set; } = false; + + /// + /// Proxy URL used for the request. + /// + /// http://proxy.example.com:8080 + [UIHint(nameof(UseProxy), "", true)] + public string ProxyUrl { get; set; } + + /// + /// Username for proxy authentication. Leave empty if proxy does not require authentication. + /// Both username and password must be provided together or left empty. + /// + /// Username + [UIHint(nameof(UseProxy), "", true)] + public string ProxyUsername { get; set; } + + /// + /// Password for proxy authentication. Leave empty if proxy does not require authentication. + /// Both username and password must be provided together or left empty. + /// + /// Password123 + [PasswordPropertyText] + [UIHint(nameof(UseProxy), "", true)] + public string ProxyPassword { get; set; } + /// /// Specifies where the Client Certificate should be loaded from. /// @@ -135,4 +166,19 @@ public class Options /// true [DefaultValue(true)] public bool AutomaticCookieHandling { get; set; } = true; + + /// + /// HTTP protocol version to use for requests. HTTP/1.1 is the default and most compatible option. + /// + /// Http11 + [DefaultValue(HttpVersion.Http11)] + public HttpVersion HttpProtocolVersion { get; set; } = HttpVersion.Http11; + + /// + /// SSL/TLS protocol version to use for secure connections. Default lets the OS decide, which matches previous behavior. + /// Use Tls12 or Tls12And13 if the server requires a specific version. + /// + /// Default + [DefaultValue(SslVersion.Default)] + public SslVersion SslProtocolVersion { get; set; } = SslVersion.Default; } diff --git a/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Definitions/SslVersion.cs b/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Definitions/SslVersion.cs new file mode 100644 index 0000000..3bbdca3 --- /dev/null +++ b/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Definitions/SslVersion.cs @@ -0,0 +1,25 @@ +namespace Frends.HTTP.SendBytes.Definitions +{ + /// + /// SSL/TLS protocol version used for secure connections. + /// + public enum SslVersion + { + /// + /// OS decides the protocol version. + /// + Default, + /// + /// TLS 1.2 only. + /// + Tls12, + /// + /// TLS 1.3 only. + /// + Tls13, + /// + /// TLS 1.2 and TLS 1.3 - use when server compatibility is uncertain. + /// + Tls12And13 + } +} diff --git a/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Extensions.cs b/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Extensions.cs index 93e6276..cac81c7 100644 --- a/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Extensions.cs +++ b/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Extensions.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Net; using System.Security.Cryptography.X509Certificates; +using System.Security.Authentication; using System.Text.RegularExpressions; using System.Diagnostics.CodeAnalysis; using Frends.HTTP.SendBytes.Definitions; @@ -41,10 +42,37 @@ internal static void SetHandlerSettingsBasedOnOptions(this HttpClientHandler han handler.AllowAutoRedirect = options.FollowRedirects; handler.UseCookies = options.AutomaticCookieHandling; + if (options.UseProxy && !string.IsNullOrWhiteSpace(options.ProxyUrl)) + { + handler.Proxy = new WebProxy(options.ProxyUrl); + handler.UseProxy = true; + + var hasUsername = !string.IsNullOrWhiteSpace(options.ProxyUsername); + var hasPassword = !string.IsNullOrWhiteSpace(options.ProxyPassword); + + if (hasUsername != hasPassword) + { + throw new ArgumentException("Both ProxyUsername and ProxyPassword must be provided together or left empty."); + } + + if (hasUsername) + { + handler.Proxy.Credentials = new NetworkCredential(options.ProxyUsername, options.ProxyPassword); + } + } + if (options.AllowInvalidCertificate) { handler.ServerCertificateCustomValidationCallback = (a, b, c, d) => true; } + + handler.SslProtocols = options.SslProtocolVersion switch + { + SslVersion.Tls12 => SslProtocols.Tls12, + SslVersion.Tls13 => SslProtocols.Tls13, + SslVersion.Tls12And13 => SslProtocols.Tls12 | SslProtocols.Tls13, + _ => SslProtocols.None + }; } internal static void SetDefaultRequestHeadersBasedOnOptions(this HttpClient httpClient, Options options) @@ -53,6 +81,18 @@ internal static void SetDefaultRequestHeadersBasedOnOptions(this HttpClient http httpClient.DefaultRequestHeaders.ExpectContinue = false; httpClient.DefaultRequestHeaders.TryAddWithoutValidation("content-type", "application/json"); httpClient.Timeout = TimeSpan.FromSeconds(Convert.ToDouble(options.ConnectionTimeoutSeconds)); + + httpClient.DefaultRequestVersion = options.HttpProtocolVersion switch + { + Definitions.HttpVersion.Http20 => System.Net.HttpVersion.Version20, + _ => System.Net.HttpVersion.Version11 + }; + + httpClient.DefaultVersionPolicy = options.HttpProtocolVersion switch + { + Definitions.HttpVersion.Http20 => HttpVersionPolicy.RequestVersionExact, + _ => HttpVersionPolicy.RequestVersionOrLower + }; } private static X509Certificate[] GetCertificates(Options options) diff --git a/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes.csproj b/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes.csproj index 52a6588..58ebd6b 100644 --- a/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes.csproj +++ b/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes.csproj @@ -2,7 +2,7 @@ net6.0 - 1.5.0 + 1.6.0 Frends Frends Frends diff --git a/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/SendBytes.cs b/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/SendBytes.cs index 317808c..ff5269d 100644 --- a/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/SendBytes.cs +++ b/Frends.HTTP.SendBytes/Frends.HTTP.SendBytes/SendBytes.cs @@ -120,7 +120,9 @@ private static string GetHttpClientCacheKey(Options options) + $":{options.ClientCertificateFilePath}:{options.ClientCertificateInBase64}:{options.ClientCertificateKeyPhrase}" + $":{options.CertificateThumbprint}:{options.LoadEntireChainForCertificate}:{options.ConnectionTimeoutSeconds}" + $":{options.FollowRedirects}:{options.AllowInvalidCertificate}:{options.AllowInvalidResponseContentTypeCharSet}" - + $":{options.ThrowExceptionOnErrorResponse}:{options.AutomaticCookieHandling}"; + + $":{options.ThrowExceptionOnErrorResponse}:{options.AutomaticCookieHandling}" + + $":{options.SslProtocolVersion}:{options.HttpProtocolVersion}" + + $":{options.UseProxy}:{options.ProxyUrl}:{options.ProxyUsername}:{options.ProxyPassword}"; } private static async Task GetHttpRequestResponseAsync( From 86933c89d650ca513d9b4a37991633dc46698017 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 13 May 2026 06:24:25 +0000 Subject: [PATCH 2/3] Fix DownloadFile test enum reference and finalize implementation Agent-Logs-Url: https://github.com/FrendsPlatform/Frends.HTTP/sessions/2c0a6a2a-230b-4d67-af17-87d3e474f51f Co-authored-by: MichalFrends1 <167774394+MichalFrends1@users.noreply.github.com> --- .../Frends.HTTP.DownloadFile.Tests/UnitTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs index f237c6c..82411ee 100644 --- a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs +++ b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs @@ -1,4 +1,5 @@ -using Frends.HTTP.DownloadFile.Definitions; +using Frends.HTTP.DownloadFile.Definitions; +using Definitions = Frends.HTTP.DownloadFile.Definitions; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; @@ -585,7 +586,7 @@ public async Task RequestShouldSetTls12And13WhenConfigured() { ConnectionTimeoutSeconds = 60, SslProtocolVersion = SslVersion.Tls12And13, - HttpProtocolVersion = HttpVersion.Http20, + HttpProtocolVersion = Definitions.HttpVersion.Http20, Overwrite = true }; From 4f4160593572a316141b7c009babe3416a85d10d Mon Sep 17 00:00:00 2001 From: MichalFrends1 Date: Wed, 13 May 2026 09:01:34 +0200 Subject: [PATCH 3/3] fix test --- .../Frends.HTTP.DownloadFile.Tests/UnitTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs index 82411ee..3f0b16c 100644 --- a/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs +++ b/Frends.HTTP.DownloadFile/Frends.HTTP.DownloadFile.Tests/UnitTests.cs @@ -586,7 +586,7 @@ public async Task RequestShouldSetTls12And13WhenConfigured() { ConnectionTimeoutSeconds = 60, SslProtocolVersion = SslVersion.Tls12And13, - HttpProtocolVersion = Definitions.HttpVersion.Http20, + HttpProtocolVersion = Definitions.HttpVersion.Http11, Overwrite = true };