diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/AssemblyLoader.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/AssemblyLoader.cs index 8d0873c..28575be 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/AssemblyLoader.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/AssemblyLoader.cs @@ -1,6 +1,6 @@ // AssemblyLoader.cs // SPDX-License-Identifier: GPL-3.0-or-later -// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors +// SPDX-FileCopyrightText: Namhyeon Go , 2025 Catswords OSS and WelsonJS Contributors // https://github.com/gnh1201/welsonjs // using System; @@ -36,10 +36,26 @@ namespace WelsonJS.Launcher private static bool _registered; 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 -------------------- @@ -152,20 +168,20 @@ namespace WelsonJS.Launcher 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()."); } 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."); } AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve; _registered = true; - Logger.Info("AssemblyLoader: AssemblyResolve handler registered."); + Logger?.Info("AssemblyLoader: AssemblyResolve handler registered."); } } @@ -193,11 +209,11 @@ namespace WelsonJS.Launcher try { if (!SetDllDirectory(cacheDir)) - Logger.Warn("SetDllDirectory failed for: {0}", cacheDir); + Logger?.Warn("SetDllDirectory failed for: {0}", cacheDir); } catch (Exception ex) { - Logger.Warn("SetDllDirectory threw exception: {0}", ex.Message); + Logger?.Warn("SetDllDirectory threw exception: {0}", ex.Message); } foreach (string raw in fileNames) @@ -212,11 +228,11 @@ namespace WelsonJS.Launcher { string url = $"{BaseUrl.TrimEnd('/')}/native/{ownerAssemblyName}/{versionString}/{fileName}"; DownloadFile(url, localPath); - Logger.Info("Downloaded native module: {0}", fileName); + Logger?.Info("Downloaded native module: {0}", fileName); } else { - Logger.Info("Using cached native module: {0}", localPath); + Logger?.Info("Using cached native module: {0}", localPath); } EnsureSignedFileOrThrow(localPath, fileName); @@ -225,12 +241,12 @@ namespace WelsonJS.Launcher if (h == IntPtr.Zero) { 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})"); } 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) { - Logger.Info("AssemblyResolve: {0}", args.Name); + Logger?.Info("AssemblyResolve: {0}", args.Name); AssemblyName req = new AssemblyName(args.Name); string simpleName = req.Name; @@ -274,7 +290,7 @@ namespace WelsonJS.Launcher var entryName = entry.GetName().Name; 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; } } @@ -294,16 +310,16 @@ namespace WelsonJS.Launcher { string url = $"{BaseUrl.TrimEnd('/')}/managed/{simpleName}/{versionStr}/{simpleName}.dll"; DownloadFile(url, dllPath); - Logger.Info("Downloaded managed assembly: {0}", simpleName); + Logger?.Info("Downloaded managed assembly: {0}", simpleName); } else { - Logger.Info("Using cached managed assembly: {0}", dllPath); + Logger?.Info("Using cached managed assembly: {0}", 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; } @@ -327,15 +343,15 @@ namespace WelsonJS.Launcher bool isDll = url.EndsWith(".dll", StringComparison.OrdinalIgnoreCase); // *.dll.gz 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; } if (!downloaded) { - Logger.Info("Downloading file from: {0}", url); + Logger?.Info("Downloading file from: {0}", url); res = Http.GetAsync(url).GetAwaiter().GetResult(); res.EnsureSuccessStatusCode(); @@ -345,7 +361,7 @@ namespace WelsonJS.Launcher s.CopyTo(fs); } - Logger.Info("Downloaded file to: {0}", dest); + Logger?.Info("Downloaded file to: {0}", dest); } if (!File.Exists(dest)) @@ -353,9 +369,14 @@ namespace WelsonJS.Launcher 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) { - Logger.Error("Error downloading {0}: {1}", url, ex.Message); + Logger?.Error("Unexpected error downloading {0}: {1}", url, ex.Message); throw; } 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"; try { - using (var res = Http.GetAsync(gzUrl).GetAwaiter().GetResult()) + using (var res = LegacyHttp.GetAsync(gzUrl).GetAwaiter().GetResult()) { if (res.StatusCode == HttpStatusCode.NotFound) + { + Logger?.Info("No gzipped variant at {0}; falling back to uncompressed URL.", gzUrl); return false; + } res.EnsureSuccessStatusCode(); @@ -385,14 +409,23 @@ namespace WelsonJS.Launcher gz.CopyTo(fs); } + if (File.Exists(dest)) + File.Delete(dest); + File.Move(tempFile, dest); 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) { - 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 { @@ -404,12 +437,10 @@ namespace WelsonJS.Launcher } 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)) { - 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); } @@ -438,17 +469,17 @@ namespace WelsonJS.Launcher if (status == FileSignatureStatus.Valid) { - Logger.Info("Signature OK: {0}", logicalName); + Logger?.Info("Signature OK: {0}", logicalName); return; } 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); } - Logger.Error("BLOCKED invalid signature: {0}", logicalName); + Logger?.Error("BLOCKED invalid signature: {0}", logicalName); throw new InvalidOperationException("Invalid signature: " + logicalName); }