From 5b2863058acafcf89dd78735bdec23638a29a0fb Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Fri, 11 Apr 2025 13:44:21 +0900 Subject: [PATCH 1/2] Add support the Azure AI, Optimize the HTTPClient object uses. --- .../Properties/Resources.Designer.cs | 15 +- .../Properties/Resources.resx | 7 +- .../WelsonJS.Launcher/ResourceServer.cs | 20 +- .../ResourceTools/AzureAi.cs | 139 ------------- .../ResourceTools/Completion.cs | 7 +- .../WelsonJS.Launcher/ResourceTools/Config.cs | 39 ---- .../ResourceTools/DevTools.cs | 16 +- .../ResourceTools/DnsQuery.cs | 8 +- .../ResourceTools/Settings.cs | 85 ++++++++ .../WelsonJS.Launcher/ResourceTools/Tfa.cs | 7 +- .../WelsonJS.Launcher/ResourceTools/Whois.cs | 46 ++--- .../WelsonJS.Launcher.csproj | 3 +- WelsonJS.Toolkit/WelsonJS.Launcher/app.config | 3 +- .../WelsonJS.Launcher/editor.html | 189 ++++++++++++++---- 14 files changed, 311 insertions(+), 273 deletions(-) delete mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/AzureAi.cs delete mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Config.cs create mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Settings.cs diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs index c9d723c..e7b3b1c 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs @@ -70,11 +70,20 @@ namespace WelsonJS.Launcher.Properties { } /// - /// https://ai-catswords656881030318.services.ai.azure.com/models/chat/completions?api-version=2024-05-01-preview과(와) 유사한 지역화된 문자열을 찾습니다. + /// 2024-05-01-preview과(와) 유사한 지역화된 문자열을 찾습니다. /// - internal static string AzureAiServiceUrl { + internal static string AzureAiServiceApiVersion { get { - return ResourceManager.GetString("AzureAiServiceUrl", resourceCulture); + return ResourceManager.GetString("AzureAiServiceApiVersion", resourceCulture); + } + } + + /// + /// https://ai-catswords656881030318.services.ai.azure.com/과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string AzureAiServicePrefix { + get { + return ResourceManager.GetString("AzureAiServicePrefix", resourceCulture); } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx index 311ac5c..5d55a0e 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx @@ -163,8 +163,8 @@ - - https://ai-catswords656881030318.services.ai.azure.com/models/chat/completions?api-version=2024-05-01-preview + + https://ai-catswords656881030318.services.ai.azure.com/ 1.1.1.1 @@ -181,4 +181,7 @@ https://catswords.blob.core.windows.net/welsonjs/ + + 2024-05-01-preview + \ No newline at end of file diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs index c218177..5568e3e 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs @@ -23,8 +23,8 @@ namespace WelsonJS.Launcher private string _prefix; private string _resourceName; private List _tools = new List(); - private const int _blobTimeout = 5000; - private readonly HttpClient _blobClient = new HttpClient(); + private const int _httpClientTimeout = 5000; + private readonly HttpClient _httpClient = new HttpClient(); private readonly string _defaultMimeType = "application/octet-stream"; public ResourceServer(string prefix, string resourceName) @@ -33,15 +33,15 @@ namespace WelsonJS.Launcher _listener = new HttpListener(); _listener.Prefixes.Add(prefix); _resourceName = resourceName; - _blobClient.Timeout = TimeSpan.FromMilliseconds(_blobTimeout); + _httpClient.Timeout = TimeSpan.FromMilliseconds(_httpClientTimeout); // Add resource tools - _tools.Add(new ResourceTools.Completion(this)); - _tools.Add(new ResourceTools.Config(this)); - _tools.Add(new ResourceTools.DevTools(this)); - _tools.Add(new ResourceTools.DnsQuery(this)); - _tools.Add(new ResourceTools.Tfa(this)); - _tools.Add(new ResourceTools.Whois(this)); + _tools.Add(new ResourceTools.Completion(this, _httpClient)); + _tools.Add(new ResourceTools.Settings(this, _httpClient)); + _tools.Add(new ResourceTools.DevTools(this, _httpClient)); + _tools.Add(new ResourceTools.DnsQuery(this, _httpClient)); + _tools.Add(new ResourceTools.Tfa(this, _httpClient)); + _tools.Add(new ResourceTools.Whois(this, _httpClient)); } public string GetPrefix() @@ -158,7 +158,7 @@ namespace WelsonJS.Launcher HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.UserAgent.ParseAdd(context.Request.UserAgent); - HttpResponseMessage response = await _blobClient.SendAsync(request); + HttpResponseMessage response = await _httpClient.SendAsync(request); if (!response.IsSuccessStatusCode) { diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/AzureAi.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/AzureAi.cs deleted file mode 100644 index 147e78a..0000000 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/AzureAi.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; - -namespace WelsonJS.Launcher.ResourceTools -{ - public class AzureAi : IResourceTool - { - private ResourceServer Server; - private const string Prefix = "azure-ai/"; - private const int ChunkSize = 4096; - private readonly string AzureAiServiceUrl; - private readonly string AzureAiServiceApiKey; - - public AzureAi(ResourceServer server) - { - Server = server; - - AzureAiServiceUrl = Program.GetAppConfig("AzureAiServiceUrl"); - AzureAiServiceApiKey = Program.GetAppConfig("AzureAiServiceApiKey"); - } - - public bool CanHandle(string path) - { - return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase); - } - - public async Task HandleAsync(HttpListenerContext context, string path) - { - string apiKey = AzureAiServiceApiKey; - if (string.IsNullOrEmpty(apiKey)) - { - WriteError(context, "Missing the API key.", HttpStatusCode.BadRequest); - return; - } - - string requestBody = ReadRequestBody(context); - bool isStreaming = ContainsSequentialKeywords(requestBody, new[] { "\"stream\"", "true" }, 30); - - try - { - using (var response = await SendAzureRequestAsync(apiKey, requestBody, isStreaming)) - { - await ForwardResponseAsync(context, response, isStreaming); - } - } - catch (HttpRequestException ex) - { - WriteError(context, "Request error: " + ex.Message, HttpStatusCode.BadGateway); - } - } - - private string ReadRequestBody(HttpListenerContext context) - { - using (var reader = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding)) - { - return reader.ReadToEnd(); - } - } - - private async Task SendAzureRequestAsync(string apiKey, string requestBody, bool isStreaming) - { - using (var client = new HttpClient()) - { - var requestMessage = new HttpRequestMessage(HttpMethod.Post, AzureAiServiceUrl); - requestMessage.Headers.Add("api-key", apiKey); - requestMessage.Content = new StringContent(requestBody, Encoding.UTF8, "application/json"); - - var completionOption = isStreaming - ? HttpCompletionOption.ResponseHeadersRead - : HttpCompletionOption.ResponseContentRead; - - return await client.SendAsync(requestMessage, completionOption); - } - } - - private async Task ForwardResponseAsync(HttpListenerContext context, HttpResponseMessage response, bool isStreaming) - { - context.Response.StatusCode = (int)response.StatusCode; - context.Response.ContentType = response.Content.Headers.ContentType?.ToString() ?? "application/json"; - - using (var responseStream = await response.Content.ReadAsStreamAsync()) - { - if (isStreaming) - { - context.Response.SendChunked = true; - context.Response.Headers.Add("Transfer-Encoding", "chunked"); - context.Response.Headers.Add("Cache-Control", "no-cache"); - - byte[] buffer = new byte[ChunkSize]; - int bytesRead; - while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length)) > 0) - { - await context.Response.OutputStream.WriteAsync(buffer, 0, bytesRead); - context.Response.OutputStream.Flush(); - } - } - else - { - await responseStream.CopyToAsync(context.Response.OutputStream); - } - } - } - - private void WriteError(HttpListenerContext context, string message, HttpStatusCode statusCode) - { - context.Response.StatusCode = (int)statusCode; - using (var writer = new StreamWriter(context.Response.OutputStream, Encoding.UTF8)) - { - writer.Write(message); - } - } - - public static bool ContainsSequentialKeywords(string input, string[] keywords, int maxDistance) - { - if (input == null || keywords == null || keywords.Length < 2) - return false; - - int position = 0; - - for (int i = 0; i < keywords.Length; i++) - { - int index = input.IndexOf(keywords[i], position, StringComparison.OrdinalIgnoreCase); - if (index < 0) - return false; - - if (i > 0 && (index - position) > maxDistance) - return false; - - position = index + keywords[i].Length; - } - - return true; - } - } -} \ No newline at end of file diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs index d9d6d81..ad4488a 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Completion.cs @@ -8,18 +8,21 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml.Linq; +using System.Net.Http; namespace WelsonJS.Launcher.ResourceTools { public class Completion : IResourceTool { - private ResourceServer Server; + private readonly ResourceServer Server; + private readonly HttpClient _httpClient; private const string Prefix = "completion/"; private List Executables = new List(); - public Completion(ResourceServer server) + public Completion(ResourceServer server, HttpClient httpClient) { Server = server; + _httpClient = httpClient; new Task(() => { diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Config.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Config.cs deleted file mode 100644 index 6358766..0000000 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Config.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Net; -using System.Threading.Tasks; - -namespace WelsonJS.Launcher.ResourceTools -{ - public class Config : IResourceTool - { - private ResourceServer Server; - private const string Prefix = "config/"; - - public Config(ResourceServer server) - { - Server = server; - } - - public bool CanHandle(string path) - { - return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase); - } - - public async Task HandleAsync(HttpListenerContext context, string path) - { - await Task.Delay(0); - - string configName = path.Substring(Prefix.Length); - - try - { - string configValue = Program.GetAppConfig(configName); - Server.ServeResource(context, configValue, "text/plain"); - } - catch (Exception ex) - { - Server.ServeResource(context, $"Failed to process Config request. {ex.Message}", "application/xml", 500); - } - } - } -} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DevTools.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DevTools.cs index 890e9fa..20d40c9 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DevTools.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DevTools.cs @@ -8,12 +8,13 @@ namespace WelsonJS.Launcher.ResourceTools public class DevTools : IResourceTool { private ResourceServer Server; + private readonly HttpClient _httpClient; private const string Prefix = "devtools/"; - private const double Timeout = 5000; - public DevTools(ResourceServer server) + public DevTools(ResourceServer server, HttpClient httpClient) { Server = server; + _httpClient = httpClient; } public bool CanHandle(string path) @@ -27,15 +28,10 @@ namespace WelsonJS.Launcher.ResourceTools try { - using (HttpClient client = new HttpClient()) - { - client.Timeout = TimeSpan.FromMilliseconds(Timeout); + string url = Program.GetAppConfig("DevToolsPrefix") + endpoint; + string data = await _httpClient.GetStringAsync(url); - string url = Program.GetAppConfig("DevToolsPrefix") + endpoint; - string data = await client.GetStringAsync(url); - - Server.ServeResource(context, data, "application/json"); - } + Server.ServeResource(context, data, "application/json"); } catch (Exception ex) { diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DnsQuery.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DnsQuery.cs index fa4ca2a..7403a63 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DnsQuery.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/DnsQuery.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; @@ -10,16 +11,19 @@ namespace WelsonJS.Launcher.ResourceTools { public class DnsQuery : IResourceTool { - private ResourceServer Server; + private readonly ResourceServer Server; + private readonly HttpClient _httpClient; private const string Prefix = "dns-query/"; private string DnsServer; private const int DnsPort = 53; private const int Timeout = 5000; private static readonly Random _random = new Random(); - public DnsQuery(ResourceServer server) + public DnsQuery(ResourceServer server, HttpClient httpClient) { Server = server; + _httpClient = httpClient; + DnsServer = Program.GetAppConfig("DnsServerAddress"); } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Settings.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Settings.cs new file mode 100644 index 0000000..4886404 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Settings.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Reflection; +using System.Resources; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace WelsonJS.Launcher.ResourceTools +{ + public class Settings : IResourceTool + { + private readonly ResourceServer Server; + private readonly HttpClient _httpClient; + private const string Prefix = "settings"; + + public Settings(ResourceServer server, HttpClient httpClient) + { + Server = server; + _httpClient = httpClient; + } + + public bool CanHandle(string path) + { + return path.Equals(Prefix, StringComparison.OrdinalIgnoreCase); + } + + public async Task HandleAsync(HttpListenerContext context, string path) + { + await Task.Delay(0); + + // Get current namespace (e.g., WelsonJS.Launcher) + string ns = typeof(Program).Namespace; + + // Build resource base name (e.g., WelsonJS.Launcher.Properties.Resources) + string resourceBaseName = ns + ".Properties.Resources"; + + // Load resource strings using ResourceManager + var resourceManager = new ResourceManager(resourceBaseName, Assembly.GetExecutingAssembly()); + var resourceSet = resourceManager.GetResourceSet( + System.Globalization.CultureInfo.CurrentCulture, + true, + true + ); + + var resourceStrings = new Dictionary(); + foreach (System.Collections.DictionaryEntry entry in resourceSet) + { + string key = (string)entry.Key; + object value = entry.Value; + + if (value is string strValue) + { + resourceStrings[key] = strValue; + } + } + + // Load settings from app.config + var appConfig = ConfigurationManager.AppSettings.AllKeys + .ToDictionary(k => k, k => ConfigurationManager.AppSettings[k]); + + // Merge keys from both sources (app.config has priority) + var allKeys = new HashSet(resourceStrings.Keys.Concat(appConfig.Keys)); + var finalConfig = new Dictionary(); + + foreach (var key in allKeys) + { + if (appConfig.ContainsKey(key)) + finalConfig[key] = appConfig[key]; + else if (resourceStrings.ContainsKey(key)) + finalConfig[key] = resourceStrings[key]; + } + + // Generate XML using XElement + XElement xml = new XElement("settings", + finalConfig.Select(kv => new XElement(kv.Key, kv.Value)) + ); + + Server.ServeResource(context, xml.ToString(), "application/xml"); + } + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Tfa.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Tfa.cs index 52de1d9..0685888 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Tfa.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Tfa.cs @@ -4,18 +4,21 @@ using System.Security.Cryptography; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; +using System.Net.Http; namespace WelsonJS.Launcher.ResourceTools { public class Tfa : IResourceTool { - private ResourceServer Server; + private readonly ResourceServer Server; + private readonly HttpClient _httpClient; private const string Prefix = "tfa/"; private const string Base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - public Tfa(ResourceServer server) + public Tfa(ResourceServer server, HttpClient httpClient) { Server = server; + _httpClient = httpClient; } public bool CanHandle(string path) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs index 7efe928..148da61 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceTools/Whois.cs @@ -8,13 +8,14 @@ namespace WelsonJS.Launcher.ResourceTools { public class Whois : IResourceTool { - private ResourceServer Server; + private readonly ResourceServer Server; + private readonly HttpClient _httpClient; private const string Prefix = "whois/"; - private const int Timeout = 5000; - public Whois(ResourceServer server) + public Whois(ResourceServer server, HttpClient httpClient) { Server = server; + _httpClient = httpClient; } public bool CanHandle(string path) @@ -32,32 +33,27 @@ namespace WelsonJS.Launcher.ResourceTools return; } - using (var client = new HttpClient()) + string clientAddress = Program.GetAppConfig("WhoisClientAddress"); + + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, Program.GetAppConfig("WhoisServerUrl")) { - client.Timeout = TimeSpan.FromMilliseconds(Timeout); + Content = new StringContent($"query={Uri.EscapeDataString(query)}&ip={clientAddress}", Encoding.UTF8, "application/x-www-form-urlencoded") + }; - string clientAddress = Program.GetAppConfig("WhoisClientAddress"); + request.Headers.Add("Accept", "*/*"); + request.Headers.Add("User-Agent", context.Request.UserAgent); + _httpClient.DefaultRequestHeaders.Referrer = new Uri(Program.GetAppConfig("WhoisReferrerUrl")); - HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, Program.GetAppConfig("WhoisServerUrl")) - { - Content = new StringContent($"query={Uri.EscapeDataString(query)}&ip={clientAddress}", Encoding.UTF8, "application/x-www-form-urlencoded") - }; + try + { + HttpResponseMessage response = await _httpClient.SendAsync(request); + string responseBody = await response.Content.ReadAsStringAsync(); - request.Headers.Add("Accept", "*/*"); - request.Headers.Add("User-Agent", context.Request.UserAgent); - client.DefaultRequestHeaders.Referrer = new Uri(Program.GetAppConfig("WhoisReferrerUrl")); - - try - { - HttpResponseMessage response = await client.SendAsync(request); - string responseBody = await response.Content.ReadAsStringAsync(); - - Server.ServeResource(context, responseBody, "text/plain", (int)response.StatusCode); - } - catch (Exception ex) - { - Server.ServeResource(context, $"Failed to process WHOIS request. {ex.Message}", "application/xml", 500); - } + Server.ServeResource(context, responseBody, "text/plain", (int)response.StatusCode); + } + catch (Exception ex) + { + Server.ServeResource(context, $"Failed to process WHOIS request. {ex.Message}", "application/xml", 500); } } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj index 731acee..21be0a4 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj @@ -74,8 +74,7 @@ - - + diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config index 2e92fd9..063a683 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config @@ -5,8 +5,9 @@ - + + diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html b/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html index 3e49309..342d8c8 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/editor.html @@ -57,7 +57,19 @@ Copilot - Generative + + + + Generative AI