mirror of
https://github.com/gnh1201/welsonjs.git
synced 2025-12-08 15:24:07 +00:00
Merge pull request #360 from gnh1201/dev
Separate HttpClient instances for raw and compressed HTTP transfer modes
This commit is contained in:
commit
f23705240d
|
|
@ -1,6 +1,6 @@
|
||||||
// AssemblyLoader.cs
|
// AssemblyLoader.cs
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
|
// SPDX-FileCopyrightText: Namhyeon Go <gnh1201@catswords.re.kr>, 2025 Catswords OSS and WelsonJS Contributors
|
||||||
// https://github.com/gnh1201/welsonjs
|
// https://github.com/gnh1201/welsonjs
|
||||||
//
|
//
|
||||||
using System;
|
using System;
|
||||||
|
|
@ -36,10 +36,26 @@ namespace WelsonJS.Launcher
|
||||||
private static bool _registered;
|
private static bool _registered;
|
||||||
|
|
||||||
private static readonly string LoaderNamespace = typeof(AssemblyLoader).Namespace ?? "WelsonJS.Launcher";
|
private static readonly string LoaderNamespace = typeof(AssemblyLoader).Namespace ?? "WelsonJS.Launcher";
|
||||||
private static readonly HttpClient Http = new HttpClient
|
private static readonly HttpClientHandler LegacyHttpHandler = new HttpClientHandler
|
||||||
{
|
{
|
||||||
Timeout = TimeSpan.FromSeconds(300) // 5 minutes
|
AutomaticDecompression = DecompressionMethods.None
|
||||||
};
|
};
|
||||||
|
private static readonly HttpClientHandler HttpHandler = new HttpClientHandler
|
||||||
|
{
|
||||||
|
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
|
||||||
|
};
|
||||||
|
private static readonly HttpClient LegacyHttp = CreateClient(LegacyHttpHandler); // Does not send Accept-Encoding (gzip, deflate)
|
||||||
|
private static readonly HttpClient Http = CreateClient(HttpHandler); // Sends Accept-Encoding (gzip, deflate) and auto-decompresses
|
||||||
|
|
||||||
|
private static HttpClient CreateClient(HttpMessageHandler handler)
|
||||||
|
{
|
||||||
|
var client = new HttpClient(handler, disposeHandler: false)
|
||||||
|
{
|
||||||
|
Timeout = TimeSpan.FromSeconds(300) // 5 minutes
|
||||||
|
};
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------- kernel32 native loading --------------------
|
// -------------------- kernel32 native loading --------------------
|
||||||
|
|
||||||
|
|
@ -152,20 +168,20 @@ namespace WelsonJS.Launcher
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(BaseUrl))
|
if (string.IsNullOrWhiteSpace(BaseUrl))
|
||||||
{
|
{
|
||||||
Logger.Error("AssemblyLoader.Register() called but BaseUrl is not set.");
|
Logger?.Error("AssemblyLoader.Register() called but BaseUrl is not set.");
|
||||||
throw new InvalidOperationException("AssemblyLoader.BaseUrl must be configured before Register().");
|
throw new InvalidOperationException("AssemblyLoader.BaseUrl must be configured before Register().");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!BaseUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
if (!BaseUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Logger.Error("AssemblyLoader.BaseUrl must use HTTPS for security.");
|
Logger?.Error("AssemblyLoader.BaseUrl must use HTTPS for security.");
|
||||||
throw new InvalidOperationException("AssemblyLoader.BaseUrl must use HTTPS.");
|
throw new InvalidOperationException("AssemblyLoader.BaseUrl must use HTTPS.");
|
||||||
}
|
}
|
||||||
|
|
||||||
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
|
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
|
||||||
_registered = true;
|
_registered = true;
|
||||||
|
|
||||||
Logger.Info("AssemblyLoader: AssemblyResolve handler registered.");
|
Logger?.Info("AssemblyLoader: AssemblyResolve handler registered.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,11 +209,11 @@ namespace WelsonJS.Launcher
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!SetDllDirectory(cacheDir))
|
if (!SetDllDirectory(cacheDir))
|
||||||
Logger.Warn("SetDllDirectory failed for: {0}", cacheDir);
|
Logger?.Warn("SetDllDirectory failed for: {0}", cacheDir);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.Warn("SetDllDirectory threw exception: {0}", ex.Message);
|
Logger?.Warn("SetDllDirectory threw exception: {0}", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (string raw in fileNames)
|
foreach (string raw in fileNames)
|
||||||
|
|
@ -212,11 +228,11 @@ namespace WelsonJS.Launcher
|
||||||
{
|
{
|
||||||
string url = $"{BaseUrl.TrimEnd('/')}/native/{ownerAssemblyName}/{versionString}/{fileName}";
|
string url = $"{BaseUrl.TrimEnd('/')}/native/{ownerAssemblyName}/{versionString}/{fileName}";
|
||||||
DownloadFile(url, localPath);
|
DownloadFile(url, localPath);
|
||||||
Logger.Info("Downloaded native module: {0}", fileName);
|
Logger?.Info("Downloaded native module: {0}", fileName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Info("Using cached native module: {0}", localPath);
|
Logger?.Info("Using cached native module: {0}", localPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
EnsureSignedFileOrThrow(localPath, fileName);
|
EnsureSignedFileOrThrow(localPath, fileName);
|
||||||
|
|
@ -225,12 +241,12 @@ namespace WelsonJS.Launcher
|
||||||
if (h == IntPtr.Zero)
|
if (h == IntPtr.Zero)
|
||||||
{
|
{
|
||||||
int errorCode = Marshal.GetLastWin32Error();
|
int errorCode = Marshal.GetLastWin32Error();
|
||||||
Logger.Error("LoadLibrary failed for {0} with error code {1}", localPath, errorCode);
|
Logger?.Error("LoadLibrary failed for {0} with error code {1}", localPath, errorCode);
|
||||||
throw new InvalidOperationException($"Failed to load native module: {fileName} (error: {errorCode})");
|
throw new InvalidOperationException($"Failed to load native module: {fileName} (error: {errorCode})");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Info("Loaded native module: {0}", fileName);
|
Logger?.Info("Loaded native module: {0}", fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -261,7 +277,7 @@ namespace WelsonJS.Launcher
|
||||||
|
|
||||||
private static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
|
private static Assembly OnAssemblyResolve(object sender, ResolveEventArgs args)
|
||||||
{
|
{
|
||||||
Logger.Info("AssemblyResolve: {0}", args.Name);
|
Logger?.Info("AssemblyResolve: {0}", args.Name);
|
||||||
|
|
||||||
AssemblyName req = new AssemblyName(args.Name);
|
AssemblyName req = new AssemblyName(args.Name);
|
||||||
string simpleName = req.Name;
|
string simpleName = req.Name;
|
||||||
|
|
@ -274,7 +290,7 @@ namespace WelsonJS.Launcher
|
||||||
var entryName = entry.GetName().Name;
|
var entryName = entry.GetName().Name;
|
||||||
if (string.Equals(simpleName, entryName, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(simpleName, entryName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Logger.Info("AssemblyResolve: skipping entry assembly {0}", simpleName);
|
Logger?.Info("AssemblyResolve: skipping entry assembly {0}", simpleName);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -294,16 +310,16 @@ namespace WelsonJS.Launcher
|
||||||
{
|
{
|
||||||
string url = $"{BaseUrl.TrimEnd('/')}/managed/{simpleName}/{versionStr}/{simpleName}.dll";
|
string url = $"{BaseUrl.TrimEnd('/')}/managed/{simpleName}/{versionStr}/{simpleName}.dll";
|
||||||
DownloadFile(url, dllPath);
|
DownloadFile(url, dllPath);
|
||||||
Logger.Info("Downloaded managed assembly: {0}", simpleName);
|
Logger?.Info("Downloaded managed assembly: {0}", simpleName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Info("Using cached managed assembly: {0}", dllPath);
|
Logger?.Info("Using cached managed assembly: {0}", dllPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!File.Exists(dllPath))
|
if (!File.Exists(dllPath))
|
||||||
{
|
{
|
||||||
Logger.Warn("AssemblyResolve: managed assembly not found after download attempt: {0}", simpleName);
|
Logger?.Warn("AssemblyResolve: managed assembly not found after download attempt: {0}", simpleName);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -327,15 +343,15 @@ namespace WelsonJS.Launcher
|
||||||
bool isDll = url.EndsWith(".dll", StringComparison.OrdinalIgnoreCase); // *.dll.gz
|
bool isDll = url.EndsWith(".dll", StringComparison.OrdinalIgnoreCase); // *.dll.gz
|
||||||
bool downloaded = false;
|
bool downloaded = false;
|
||||||
|
|
||||||
if (isDll && TryDownloadGzipToFile(gzUrl, dest))
|
if (isDll && TryDownloadCompressedFile(gzUrl, dest))
|
||||||
{
|
{
|
||||||
Logger.Info("Downloaded and decompressed gzip file to: {0}", dest);
|
Logger?.Info("Downloaded and decompressed file to: {0}", dest);
|
||||||
downloaded = true;
|
downloaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!downloaded)
|
if (!downloaded)
|
||||||
{
|
{
|
||||||
Logger.Info("Downloading file from: {0}", url);
|
Logger?.Info("Downloading file from: {0}", url);
|
||||||
res = Http.GetAsync(url).GetAwaiter().GetResult();
|
res = Http.GetAsync(url).GetAwaiter().GetResult();
|
||||||
res.EnsureSuccessStatusCode();
|
res.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
|
@ -345,7 +361,7 @@ namespace WelsonJS.Launcher
|
||||||
s.CopyTo(fs);
|
s.CopyTo(fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Info("Downloaded file to: {0}", dest);
|
Logger?.Info("Downloaded file to: {0}", dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!File.Exists(dest))
|
if (!File.Exists(dest))
|
||||||
|
|
@ -353,9 +369,14 @@ namespace WelsonJS.Launcher
|
||||||
throw new FileNotFoundException("File not found after download", dest);
|
throw new FileNotFoundException("File not found after download", dest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
Logger?.Error("Network or I/O error downloading {0}: {1}", url, ex.Message);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.Error("Error downloading {0}: {1}", url, ex.Message);
|
Logger?.Error("Unexpected error downloading {0}: {1}", url, ex.Message);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
@ -365,16 +386,19 @@ namespace WelsonJS.Launcher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static bool TryDownloadGzipToFile(string gzUrl, string dest)
|
private static bool TryDownloadCompressedFile(string gzUrl, string dest)
|
||||||
{
|
{
|
||||||
string tempFile = dest + ".tmp";
|
string tempFile = dest + ".tmp";
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var res = Http.GetAsync(gzUrl).GetAwaiter().GetResult())
|
using (var res = LegacyHttp.GetAsync(gzUrl).GetAwaiter().GetResult())
|
||||||
{
|
{
|
||||||
if (res.StatusCode == HttpStatusCode.NotFound)
|
if (res.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
Logger?.Info("No gzipped variant at {0}; falling back to uncompressed URL.", gzUrl);
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
res.EnsureSuccessStatusCode();
|
res.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
|
@ -385,14 +409,23 @@ namespace WelsonJS.Launcher
|
||||||
gz.CopyTo(fs);
|
gz.CopyTo(fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (File.Exists(dest))
|
||||||
|
File.Delete(dest);
|
||||||
|
|
||||||
File.Move(tempFile, dest);
|
File.Move(tempFile, dest);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
Logger?.Warn("Network or I/O error downloading compressed file from {0}: {1}", gzUrl, ex.Message);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger?.Warn("Failed to download or decompress gzipped file from {0}: {1}", gzUrl, ex.Message);
|
Logger?.Error("Unexpected error downloading compressed file from {0}: {1}", gzUrl, ex.Message);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -404,12 +437,10 @@ namespace WelsonJS.Launcher
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger?.Info("Failed to delete temp file {0}: {1}", tempFile, ex.Message);
|
Logger?.Info("Failed to delete temporary file {0}: {1}", tempFile, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -430,7 +461,7 @@ namespace WelsonJS.Launcher
|
||||||
{
|
{
|
||||||
if (!File.Exists(path))
|
if (!File.Exists(path))
|
||||||
{
|
{
|
||||||
Logger.Error("File does not exist for signature verification: {0}", logicalName);
|
Logger?.Error("File does not exist for signature verification: {0}", logicalName);
|
||||||
throw new FileNotFoundException("File not found for signature verification: " + logicalName, path);
|
throw new FileNotFoundException("File not found for signature verification: " + logicalName, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -438,17 +469,17 @@ namespace WelsonJS.Launcher
|
||||||
|
|
||||||
if (status == FileSignatureStatus.Valid)
|
if (status == FileSignatureStatus.Valid)
|
||||||
{
|
{
|
||||||
Logger.Info("Signature OK: {0}", logicalName);
|
Logger?.Info("Signature OK: {0}", logicalName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status == FileSignatureStatus.NoSignature)
|
if (status == FileSignatureStatus.NoSignature)
|
||||||
{
|
{
|
||||||
Logger.Error("BLOCKED unsigned binary: {0}", logicalName);
|
Logger?.Error("BLOCKED unsigned binary: {0}", logicalName);
|
||||||
throw new InvalidOperationException("Unsigned binary blocked: " + logicalName);
|
throw new InvalidOperationException("Unsigned binary blocked: " + logicalName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Error("BLOCKED invalid signature: {0}", logicalName);
|
Logger?.Error("BLOCKED invalid signature: {0}", logicalName);
|
||||||
throw new InvalidOperationException("Invalid signature: " + logicalName);
|
throw new InvalidOperationException("Invalid signature: " + logicalName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user