diff --git a/README.md b/README.md
index 57bcebd..e2df4ca 100644
--- a/README.md
+++ b/README.md
@@ -32,13 +32,16 @@ Http2Client requires the native TLS library from the original [bogdanfinn/tls-cl
**Installation Instructions:**
1. Download the appropriate library file for your platform from the table above
-2. Place the native library in your application's output directory, or
-3. Specify the custom path using the `WithLibraryPath()` method in your code
+2. Place the native library in your application's output directory, or specify a custom path
+3. Initialize the library once at application startup using `Http2Client.Initialize()`
**Example for Windows:**
```csharp
+// Initialize once at application startup
+Http2Client.Initialize("tls-client-windows-64-1.11.0.dll");
+
+// Create clients as needed
using var client = new HttpClientBuilder()
- .WithLibraryPath("tls-client-windows-64-1.11.0.dll")
.Build();
```
@@ -56,9 +59,11 @@ using Http2Client.Builders;
using Http2Client.Core.Enums;
using Http2Client.Core.Request;
+// Initialize native library once at application startup
+Http2Client.Initialize("tls-client-windows-64-1.11.0.dll");
+
// Create an Http2Client instance using the builder
using var client = new HttpClientBuilder()
- .WithLibraryPath("tls-client-windows-64-1.11.0.dll")
.WithBrowserType(BrowserType.Chrome133)
.WithUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.WithTimeout(TimeSpan.FromSeconds(30))
@@ -118,7 +123,6 @@ using var client = new HttpClientBuilder()
| Method | Description |
|--------|-------------|
-| `WithLibraryPath(string)` | Sets the path to the native TLS library. |
| `WithBrowserType(BrowserType)` | Sets the browser fingerprint to mimic. |
| `WithUserAgent(string)` | Sets the User-Agent header. |
| `WithTimeout(TimeSpan)` | Sets the request timeout. |
@@ -142,8 +146,10 @@ using var client = new HttpClientBuilder()
### Advanced Example
```csharp
+// Initialize library once at startup
+Http2Client.Initialize("tls-client-windows-64-1.11.0.dll");
+
using var client = new HttpClientBuilder()
- .WithLibraryPath("tls-client-windows-64-1.11.0.dll")
.WithBrowserType(BrowserType.Firefox132)
.WithUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
.WithProxy("http://127.0.0.1:8888", isRotating: true)
@@ -228,7 +234,6 @@ var request = new HttpRequest
```csharp
using var client = new HttpClientBuilder()
- .WithLibraryPath("tls-client-windows-64-1.11.0.dll")
.WithBrowserType(BrowserType.Chrome133)
.Build();
@@ -265,7 +270,6 @@ var response = client.Send(request);
```csharp
using var client = new HttpClientBuilder()
- .WithLibraryPath("tls-client-windows-64-1.11.0.dll")
.WithBrowserType(BrowserType.Chrome133)
.WithProxy("http://proxy.example.com:8080")
.Build();
diff --git a/examples/Program.cs b/examples/Program.cs
index ca54176..9073dff 100644
--- a/examples/Program.cs
+++ b/examples/Program.cs
@@ -8,15 +8,22 @@
internal class Program
{
- private const string PATH_LIB = "Native\\tls-client-windows-64-1.11.0.dll";
-
private static void Main()
{
- //BasicGetRequest();
- //PostJsonRequest();
- //CookieHandling();
- //HeadersAndProxy();
- ErrorHandlingAndTimeouts();
+ Http2Client.Http2Client.Initialize("Native\\tls-client-windows-64-1.11.0.dll");
+
+ try
+ {
+ BasicGetRequest();
+ PostJsonRequest();
+ CookieHandling();
+ HeadersAndProxy();
+ ErrorHandlingAndTimeouts();
+ }
+ finally
+ {
+ Http2Client.Http2Client.Cleanup();
+ }
Console.ReadLine();
}
@@ -27,7 +34,6 @@ private static void BasicGetRequest()
using var client = new HttpClientBuilder()
.WithUserAgent("Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36")
- .WithLibraryPath(PATH_LIB)
.WithRandomTlsExtensions()
.Build();
@@ -50,7 +56,6 @@ private static void PostJsonRequest()
using var client = new HttpClientBuilder()
.WithUserAgent("Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36")
- .WithLibraryPath(PATH_LIB)
.WithHeader("Content-Type", "application/json")
.WithCookies()
.Build();
@@ -83,7 +88,6 @@ private static void CookieHandling()
using var client = new HttpClientBuilder()
.WithUserAgent("Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36")
- .WithLibraryPath(PATH_LIB)
.WithCookies(true)
.Build();
@@ -118,7 +122,6 @@ private static void HeadersAndProxy()
using var client = new HttpClientBuilder()
.WithUserAgent("Custom-Agent/1.0")
- .WithLibraryPath(PATH_LIB)
.WithHeader("Accept", "application/json")
.WithHeader("Accept-Language", "en-US,en;q=0.9")
.WithHeader("Accept-Encoding", "gzip, deflate, br")
@@ -150,7 +153,6 @@ private static void ErrorHandlingAndTimeouts()
using var client = new HttpClientBuilder()
.WithUserAgent("Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36")
- .WithLibraryPath(PATH_LIB)
.WithTimeout(TimeSpan.FromSeconds(10))
.WithCatchPanics(true)
.WithInsecureSkipVerify(false)
diff --git a/src/Builders/HttpClientBuilder.cs b/src/Builders/HttpClientBuilder.cs
index 22a9b40..280c218 100644
--- a/src/Builders/HttpClientBuilder.cs
+++ b/src/Builders/HttpClientBuilder.cs
@@ -195,17 +195,6 @@ public HttpClientBuilder WithRandomTlsExtensions(bool enable = true)
return this;
}
- ///
- /// Sets native library path. Auto-detected if not set.
- ///
- public HttpClientBuilder WithLibraryPath(string libraryPath)
- {
- ThrowException.FileNotExists(libraryPath, nameof(libraryPath));
-
- _options.LibraryPath = libraryPath;
- return this;
- }
-
///
/// Sets User-Agent header.
///
diff --git a/src/Http2Client.cs b/src/Http2Client.cs
index cb74c69..0972aa7 100644
--- a/src/Http2Client.cs
+++ b/src/Http2Client.cs
@@ -15,7 +15,6 @@ namespace Http2Client;
public sealed class Http2Client : IDisposable
{
private readonly Http2ClientOptions _options;
- private readonly NativeWrapper _wrapper;
private bool _disposed;
///
@@ -33,6 +32,11 @@ public sealed class Http2Client : IDisposable
///
public bool IsDisposed => _disposed;
+ ///
+ /// True if native library is loaded and ready to use.
+ ///
+ public static bool IsInitialized => NativeWrapper.IsInitialized;
+
///
/// Create client with custom options.
///
@@ -41,7 +45,6 @@ public Http2Client(Http2ClientOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_options.Validate();
- _wrapper = NativeWrapper.Load(_options.LibraryPath);
}
///
@@ -51,6 +54,23 @@ public Http2Client() : this(new Http2ClientOptions())
{
}
+ ///
+ /// Initialize native library once. Call this before creating any Http2Client instances.
+ ///
+ /// Path to native TLS library
+ public static void Initialize(string path)
+ {
+ NativeWrapper.Initialize(path);
+ }
+
+ ///
+ /// Cleanup native library resources. Call at application shutdown.
+ ///
+ public static void Cleanup()
+ {
+ NativeWrapper.Cleanup();
+ }
+
///
/// Send HTTP request. Main method for making requests.
///
@@ -67,7 +87,7 @@ public Http2Client() : this(new Http2ClientOptions())
try
{
- var responseJson = _wrapper.Request(Serializer.SerializeToBytes(prepared));
+ var responseJson = NativeWrapper.Request(Serializer.SerializeToBytes(prepared));
response = Serializer.Deserialize(responseJson);
return response;
}
@@ -80,7 +100,7 @@ public Http2Client() : this(new Http2ClientOptions())
// Clean up native memory for this response
if (response != null && !string.IsNullOrEmpty(response.Id))
{
- _wrapper.FreeMemory(response.Id);
+ NativeWrapper.FreeMemory(response.Id);
}
}
}
@@ -100,7 +120,7 @@ public Http2Client() : this(new Http2ClientOptions())
SessionId = _options.SessionId,
};
- var responseJson = _wrapper.GetCookiesFromSession(Serializer.SerializeToBytes(payload));
+ var responseJson = NativeWrapper.GetCookiesFromSession(Serializer.SerializeToBytes(payload));
return Serializer.Deserialize(responseJson);
}
@@ -122,7 +142,7 @@ public Http2Client() : this(new Http2ClientOptions())
Cookies = [.. cookies]
};
- var responseJson = _wrapper.AddCookiesToSession(Serializer.SerializeToBytes(payload));
+ var responseJson = NativeWrapper.AddCookiesToSession(Serializer.SerializeToBytes(payload));
return Serializer.Deserialize(responseJson);
}
@@ -135,7 +155,7 @@ public bool DestroySession()
try
{
var payload = new { sessionId = _options.SessionId };
- var responseJson = _wrapper.DestroySession(Serializer.SerializeToBytes(payload));
+ var responseJson = NativeWrapper.DestroySession(Serializer.SerializeToBytes(payload));
return !string.IsNullOrEmpty(responseJson);
}
catch
@@ -153,7 +173,7 @@ public bool DestroyAllSessions()
{
try
{
- var responseJson = _wrapper.DestroyAllSessions();
+ var responseJson = NativeWrapper.DestroyAllSessions();
return !string.IsNullOrEmpty(responseJson);
}
catch
@@ -286,11 +306,9 @@ private void Dispose(bool disposing)
}
catch
{
- // If session cleanup fails, we still want to dispose the wrapper
+ // If session cleanup fails, we still want to continue disposal
// Better to leak a session than crash during disposal
}
-
- _wrapper?.Dispose();
}
_disposed = true;
diff --git a/src/Http2Client.csproj b/src/Http2Client.csproj
index d24c040..7307721 100644
--- a/src/Http2Client.csproj
+++ b/src/Http2Client.csproj
@@ -6,7 +6,7 @@
enable
AnyCPU;x64
Http2Client
- 1.1.3
+ 1.1.4
Rckov
Rckov
Http2Client
diff --git a/src/Http2ClientOptions.cs b/src/Http2ClientOptions.cs
index 4581af5..d4779f0 100644
--- a/src/Http2ClientOptions.cs
+++ b/src/Http2ClientOptions.cs
@@ -103,11 +103,6 @@ public class Http2ClientOptions
///
public bool WithRandomTlsExtensionOrder { get; set; }
- ///
- /// Path to native TLS library. Auto-detected if not specified.
- ///
- public string? LibraryPath { get; set; } = GetDefaultLibraryPath();
-
///
/// User-Agent header value.
///
@@ -132,8 +127,6 @@ public string? UserAgent
///
public void Validate()
{
- ThrowException.FileNotExists(LibraryPath, nameof(LibraryPath));
-
if (!string.IsNullOrEmpty(ProxyUrl))
{
ThrowException.IsUri(ProxyUrl, nameof(ProxyUrl));
@@ -178,7 +171,6 @@ public Http2ClientOptions Clone()
WithDefaultCookieJar = WithDefaultCookieJar,
WithoutCookieJar = WithoutCookieJar,
WithRandomTlsExtensionOrder = WithRandomTlsExtensionOrder,
- LibraryPath = LibraryPath
};
// Deep copy headers so changes to the clone don't mess with the original
@@ -189,14 +181,4 @@ public Http2ClientOptions Clone()
return clone;
}
-
- ///
- /// Gets default native library path for current platform.
- ///
- private static string GetDefaultLibraryPath()
- {
- // .dll / .so / .dylib
- var extension = PlatformSupport.GetNativeLibraryExtension();
- return PlatformSupport.GetRuntimePath($"tls-client.{extension}");
- }
}
\ No newline at end of file
diff --git a/src/Native/NativeWrapper.cs b/src/Native/NativeWrapper.cs
index acf9d58..b37bcc2 100644
--- a/src/Native/NativeWrapper.cs
+++ b/src/Native/NativeWrapper.cs
@@ -8,7 +8,7 @@ namespace Http2Client.Native;
///
/// Wrapper for native Go TLS library.
///
-internal class NativeWrapper : IDisposable
+internal static class NativeWrapper
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr RequestDelegate(byte[] payload);
@@ -28,46 +28,45 @@ internal class NativeWrapper : IDisposable
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr DestroyAllDelegate();
- private readonly IntPtr _libraryHandle;
- private readonly RequestDelegate _requestDelegate;
- private readonly FreeMemoryDelegate _freeMemoryDelegate;
- private readonly GetCookiesFromSessionDelegate _getCookiesDelegate;
- private readonly AddCookiesToSessionDelegate _addCookiesDelegate;
- private readonly DestroySessionDelegate _destroySessionDelegate;
- private readonly DestroyAllDelegate _destroyAllDelegate;
+ private static IntPtr _libraryHandle;
+ private static RequestDelegate? _requestDelegate;
+ private static FreeMemoryDelegate? _freeMemoryDelegate;
+ private static GetCookiesFromSessionDelegate? _getCookiesDelegate;
+ private static AddCookiesToSessionDelegate? _addCookiesDelegate;
+ private static DestroySessionDelegate? _destroySessionDelegate;
+ private static DestroyAllDelegate? _destroyAllDelegate;
- ///
- /// Creates wrapper from loaded library. Use Load() instead.
- ///
- private NativeWrapper(IntPtr libraryHandle)
- {
- _libraryHandle = libraryHandle;
-
- // Find all the functions we need in the loaded library
- _requestDelegate = GetDelegate("request");
- _freeMemoryDelegate = GetDelegate("freeMemory");
- _getCookiesDelegate = GetDelegate("getCookiesFromSession");
- _addCookiesDelegate = GetDelegate("addCookiesToSession");
- _destroySessionDelegate = GetDelegate("destroySession");
- _destroyAllDelegate = GetDelegate("destroyAll");
- }
+ public static bool IsInitialized { get; private set; }
///
- /// Loads native library and creates wrapper. Cleans up on failure.
+ /// Initializes native library once. Call at application startup.
///
- public static NativeWrapper Load(string? path)
+ public static void Initialize(string? path)
{
+ if (IsInitialized)
+ {
+ return;
+ }
+
ThrowException.FileNotExists(path);
- var handleLib = NativeLoader.LoadLibrary(path);
+ _libraryHandle = NativeLoader.LoadLibrary(path);
try
{
- return new NativeWrapper(handleLib);
+ // Find all the functions we need in the loaded library
+ _requestDelegate = GetDelegate("request");
+ _freeMemoryDelegate = GetDelegate("freeMemory");
+ _getCookiesDelegate = GetDelegate("getCookiesFromSession");
+ _addCookiesDelegate = GetDelegate("addCookiesToSession");
+ _destroySessionDelegate = GetDelegate("destroySession");
+ _destroyAllDelegate = GetDelegate("destroyAll");
+
+ IsInitialized = true;
}
catch
{
- // If wrapper creation fails, clean up the loaded library
- NativeLoader.FreeLibrary(handleLib);
+ // If initialization fails, clean up the loaded library
+ NativeLoader.FreeLibrary(_libraryHandle);
throw;
}
}
@@ -75,55 +74,98 @@ public static NativeWrapper Load(string? path)
///
/// Sends HTTP request through native library.
///
- public string Request(byte[] payload)
+ public static string Request(byte[] payload)
{
- return ExecuteFunction(() => _requestDelegate(payload));
+ ThrowIsNotLoaded();
+ return ExecuteFunction(() => _requestDelegate!(payload));
}
///
/// Gets cookies from session for URL.
///
- public string GetCookiesFromSession(byte[] payload)
+ public static string GetCookiesFromSession(byte[] payload)
{
- return ExecuteFunction(() => _getCookiesDelegate(payload));
+ ThrowIsNotLoaded();
+ return ExecuteFunction(() => _getCookiesDelegate!(payload));
}
///
/// Adds cookies to session for automatic sending.
///
- public string AddCookiesToSession(byte[] payload)
+ public static string AddCookiesToSession(byte[] payload)
{
- return ExecuteFunction(() => _addCookiesDelegate(payload));
+ ThrowIsNotLoaded();
+ return ExecuteFunction(() => _addCookiesDelegate!(payload));
}
///
/// Destroys session and frees memory.
///
- public string DestroySession(byte[] payload)
+ public static string DestroySession(byte[] payload)
{
- return ExecuteFunction(() => _destroySessionDelegate(payload));
+ ThrowIsNotLoaded();
+ return ExecuteFunction(() => _destroySessionDelegate!(payload));
}
///
/// Destroys ALL sessions. Breaks other client instances!
///
- public string DestroyAllSessions()
+ public static string DestroyAllSessions()
{
- return ExecuteFunction(() => _destroyAllDelegate());
+ ThrowIsNotLoaded();
+ return ExecuteFunction(() => _destroyAllDelegate!());
}
///
/// Frees response memory. Called automatically.
///
- public void FreeMemory(string responseId)
+ public static void FreeMemory(string responseId)
+ {
+ ThrowIsNotLoaded();
+ _freeMemoryDelegate!(responseId);
+ }
+
+ ///
+ /// Cleanup native library resources. Call at application shutdown.
+ ///
+ public static void Cleanup()
+ {
+ if (_libraryHandle != IntPtr.Zero)
+ {
+ NativeLoader.FreeLibrary(_libraryHandle);
+ }
+
+ _libraryHandle = IntPtr.Zero;
+
+ // Clear delegates
+ _requestDelegate = null;
+ _freeMemoryDelegate = null;
+ _getCookiesDelegate = null;
+ _addCookiesDelegate = null;
+ _destroySessionDelegate = null;
+ _destroyAllDelegate = null;
+
+ //
+ IsInitialized = false;
+ }
+
+ ///
+ /// Ensures library is initialized before use.
+ ///
+ private static void ThrowIsNotLoaded()
{
- _freeMemoryDelegate(responseId);
+ if (IsInitialized)
+ {
+ return;
+ }
+
+ throw new InvalidOperationException("Native library is not initialized. Call Http2Client.Initialize(libraryPath) before creating any Http2Client instances.");
}
///
/// Finds function in library and creates delegate.
///
- private T GetDelegate(string functionName)
+ private static T GetDelegate(string functionName)
{
var functionPtr = NativeLoader.GetProcAddress(_libraryHandle, functionName);
@@ -149,9 +191,4 @@ private static string ExecuteFunction(Func nativeFunction)
return Marshal.PtrToStringAnsi(resultPtr)!;
}
-
- public void Dispose()
- {
- NativeLoader.FreeLibrary(_libraryHandle);
- }
}
\ No newline at end of file
diff --git a/tests/Builders/HttpClientBuilderTests.cs b/tests/Builders/HttpClientBuilderTests.cs
index cc0b179..6e79bcd 100644
--- a/tests/Builders/HttpClientBuilderTests.cs
+++ b/tests/Builders/HttpClientBuilderTests.cs
@@ -52,7 +52,6 @@ public void WithUserAgent_Null_Throws()
public void Chaining_Works()
{
var options = new HttpClientBuilder()
- .WithLibraryPath(TestConstants.LibraryPath)
.WithBrowserType(BrowserType.Firefox132)
.WithTimeout(TimeSpan.FromSeconds(30))
.WithProxy("http://proxy:8080", true)
diff --git a/tests/Http2ClientOptionsTests.cs b/tests/Http2ClientOptionsTests.cs
index ed33495..b1484b9 100644
--- a/tests/Http2ClientOptionsTests.cs
+++ b/tests/Http2ClientOptionsTests.cs
@@ -8,6 +8,11 @@ namespace Http2Client.Test;
public class Http2ClientOptionsTests
{
+ static Http2ClientOptionsTests()
+ {
+ Http2Client.Initialize(TestConstants.LibraryPath);
+ }
+
[Fact]
public void SetsDefaults()
{
@@ -51,7 +56,6 @@ public void Validate_Cookies_Throws()
{
var options = new Http2ClientOptions
{
- LibraryPath = TestConstants.LibraryPath,
WithDefaultCookieJar = true,
WithoutCookieJar = true
};
@@ -67,7 +71,6 @@ public void Validate_IP_Throws()
{
var options = new Http2ClientOptions
{
- LibraryPath = TestConstants.LibraryPath,
DisableIPv4 = true,
DisableIPv6 = true
};
@@ -83,7 +86,6 @@ public void Validate_Timeout_Throws()
{
var options = new Http2ClientOptions
{
- LibraryPath = TestConstants.LibraryPath,
Timeout = TimeSpan.Zero
};
@@ -95,20 +97,12 @@ public void Validate_ProxyUrl_Throws()
{
var options = new Http2ClientOptions
{
- LibraryPath = TestConstants.LibraryPath,
ProxyUrl = "invalid-url"
};
options.Invoking(o => o.Validate()).Should().Throw();
}
- [Fact]
- public void Validate_LibraryPath_Throws()
- {
- var options = new Http2ClientOptions { LibraryPath = "nonexistent.dll" };
- options.Invoking(o => o.Validate()).Should().Throw();
- }
-
[Fact]
public void Clone_Works()
{
diff --git a/tests/Http2ClientTests.cs b/tests/Http2ClientTests.cs
index 03c99a7..2f182ec 100644
--- a/tests/Http2ClientTests.cs
+++ b/tests/Http2ClientTests.cs
@@ -6,6 +6,11 @@ namespace Http2Client.Test;
public class Http2ClientTests
{
+ static Http2ClientTests()
+ {
+ Http2Client.Initialize(TestConstants.LibraryPath);
+ }
+
[Fact]
public void Ctor_Null_Throws()
{
@@ -16,7 +21,7 @@ public void Ctor_Null_Throws()
[Fact]
public void Ctor_ValidOptions_Works()
{
- var options = new Http2ClientOptions { LibraryPath = TestConstants.LibraryPath };
+ var options = new Http2ClientOptions();
using var client = new Http2Client(options);
@@ -28,8 +33,7 @@ public void Ctor_ValidOptions_Works()
[Fact]
public void Dispose_Works()
{
- var options = new Http2ClientOptions { LibraryPath = TestConstants.LibraryPath };
- var client = new Http2Client(options);
+ var client = new Http2Client();
client.Dispose();
@@ -39,8 +43,7 @@ public void Dispose_Works()
[Fact]
public void Send_NullRequest_Throws()
{
- var options = new Http2ClientOptions { LibraryPath = TestConstants.LibraryPath };
- using var client = new Http2Client(options);
+ using var client = new Http2Client();
var action = () => client.Send(null!);
action.Should().Throw();
diff --git a/tests/Http2ClientWebTests.cs b/tests/Http2ClientWebTests.cs
index 5437d75..20fac55 100644
--- a/tests/Http2ClientWebTests.cs
+++ b/tests/Http2ClientWebTests.cs
@@ -14,6 +14,11 @@ public class Http2ClientWebTests
{
private Http2Client? _client;
+ static Http2ClientWebTests()
+ {
+ Http2Client.Initialize(TestConstants.LibraryPath);
+ }
+
[Fact]
public void TlsPeet_Works()
{
@@ -131,7 +136,6 @@ public void Headers_Work()
private Http2Client CreateClient()
{
return new HttpClientBuilder()
- .WithLibraryPath(TestConstants.LibraryPath)
.WithBrowserType(BrowserType.Chrome133)
.WithUserAgent("Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36")
.WithCookies()