Refactor an endpoints of the web based editor

This commit is contained in:
Namhyeon Go 2025-04-05 12:53:28 +09:00
parent 96dbe7b2cd
commit d8fa8779de
15 changed files with 529 additions and 420 deletions

View File

@ -0,0 +1,11 @@
using System.Net;
using System.Threading.Tasks;
namespace WelsonJS.Launcher
{
public interface IResourceTool
{
bool CanHandle(string path);
Task HandleAsync(HttpListenerContext context, string path);
}
}

View File

@ -5,7 +5,6 @@ using System.IO.Compression;
using System.Security.Principal;
using System.Threading.Tasks;
using System.Windows.Forms;
using WelsonJS.Launcher.Tools;
namespace WelsonJS.Launcher
{

View File

@ -5,7 +5,6 @@ using System.Linq;
using System.Threading;
using System.Windows.Forms;
using System.Configuration;
using WelsonJS.Launcher.Tools;
namespace WelsonJS.Launcher
{

View File

@ -69,6 +69,15 @@ namespace WelsonJS.Launcher.Properties {
}
}
/// <summary>
/// http://localhost:9222/과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>
internal static string DevToolsPrefix {
get {
return ResourceManager.GetString("DevToolsPrefix", resourceCulture);
}
}
/// <summary>
/// (아이콘)과(와) 유사한 System.Drawing.Icon 형식의 지역화된 리소스를 찾습니다.
/// </summary>

View File

@ -157,4 +157,7 @@
<data name="ResourceServerPrefix" xml:space="preserve">
<value>http://localhost:3000/</value>
</data>
<data name="DevToolsPrefix" xml:space="preserve">
<value>http://localhost:9222/</value>
</data>
</root>

View File

@ -0,0 +1,209 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Linq;
namespace WelsonJS.Launcher
{
public class ResourceServer
{
private readonly HttpListener _listener;
private CancellationTokenSource _cts;
private Task _serverTask;
private bool _isRunning;
private string _prefix;
private string _resourceName;
private List<IResourceTool> _resourceTools = new List<IResourceTool>();
public ResourceServer(string prefix, string resourceName)
{
_prefix = prefix;
_listener = new HttpListener();
_listener.Prefixes.Add(prefix);
_resourceName = resourceName;
// Add resource tools
_resourceTools.Add(new ResourceTools.Completion(this));
_resourceTools.Add(new ResourceTools.Config(this));
_resourceTools.Add(new ResourceTools.DevTools(this));
_resourceTools.Add(new ResourceTools.Tfa(this));
_resourceTools.Add(new ResourceTools.Whois(this));
}
public string GetPrefix()
{
return _prefix;
}
public void Start()
{
if (_isRunning) return;
_isRunning = true;
_cts = new CancellationTokenSource();
_listener.Start();
// Open the web browser
Program.OpenWebBrowser(_prefix);
// Run a task with cancellation token
_serverTask = Task.Run(() => ListenLoop(_cts.Token));
}
public void Stop()
{
_isRunning = false;
_cts.Cancel();
_listener.Stop();
MessageBox.Show("Server stopped.");
}
public bool IsRunning()
{
return _isRunning;
}
private async Task ListenLoop(CancellationToken token)
{
while (!token.IsCancellationRequested && _isRunning)
{
try
{
await ProcessRequest(await _listener.GetContextAsync());
}
catch (Exception ex)
{
if (token.IsCancellationRequested || !_isRunning) break;
MessageBox.Show($"Error: {ex.Message}");
}
}
}
private async Task ProcessRequest(HttpListenerContext context)
{
string path = context.Request.Url.AbsolutePath.TrimStart('/');
// Serve the favicon.ico file
if ("favicon.ico".Equals(path, StringComparison.OrdinalIgnoreCase))
{
ServeResource(context, GetResource("favicon"), "image/x-icon");
return;
}
// Serve from a resource tool
foreach(var tool in _resourceTools)
{
if (tool.CanHandle(path))
{
await tool.HandleAsync(context, path);
return;
}
}
// Serve from a resource name
ServeResource(context, GetResource(_resourceName), "text/html");
}
public void ServeResource(HttpListenerContext context)
{
ServeResource(context, "<error>Not Found</error>", "application/xml", 404);
}
public void ServeResource(HttpListenerContext context, byte[] data, string mimeType = "text/html", int statusCode = 200)
{
string xmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
if (data == null) {
data = Encoding.UTF8.GetBytes(xmlHeader + "\r\n<error>Could not find the resource.</error>");
mimeType = "application/xml";
statusCode = 404;
}
context.Response.StatusCode = statusCode;
context.Response.ContentType = mimeType;
context.Response.ContentLength64 = data.Length;
using (Stream outputStream = context.Response.OutputStream)
{
outputStream.Write(data, 0, data.Length);
}
}
public void ServeResource(HttpListenerContext context, string data, string mimeType = "text/html", int statusCode = 200)
{
string xmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
if (data == null)
{
data = xmlHeader + "\r\n<error>Could not find the resource.</error>";
mimeType = "application/xml";
statusCode = 404;
}
else if (mimeType == "application/xml" && !data.StartsWith("<?xml"))
{
data = xmlHeader + "\r\n" + data;
}
ServeResource(context, Encoding.UTF8.GetBytes(data), mimeType, statusCode);
}
private byte[] GetResource(string resourceName)
{
// Try to fetch embedded resource.
byte[] data = GetEmbeddedResource(typeof(Program).Namespace + "." + resourceName);
if (data != null) return data;
// Fallback: Try to fetch resource from ResourceManager.
return GetResourceFromManager(resourceName);
}
private byte[] GetEmbeddedResource(string fullResourceName)
{
Assembly assembly = Assembly.GetExecutingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(fullResourceName))
{
if (stream != null)
{
using (MemoryStream memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
}
return null;
}
private byte[] GetResourceFromManager(string resourceName)
{
object resourceObject = Properties.Resources.ResourceManager.GetObject(resourceName);
switch (resourceObject)
{
case byte[] resourceBytes:
return resourceBytes;
case System.Drawing.Icon icon:
return ConvertIconToBytes(icon);
default:
return null;
}
}
private byte[] ConvertIconToBytes(System.Drawing.Icon icon)
{
using (MemoryStream memoryStream = new MemoryStream())
{
icon.Save(memoryStream);
return memoryStream.ToArray();
}
}
}
}

View File

@ -3,29 +3,72 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace WelsonJS.Launcher.Tools
namespace WelsonJS.Launcher.ResourceTools
{
public class ExecutablesCollector
public class Completion : IResourceTool
{
private List<string> executables = new List<string>();
private ResourceServer Server;
private const string Prefix = "completion/";
private List<string> Executables = new List<string>();
public ExecutablesCollector()
public Completion(ResourceServer server)
{
Server = server;
new Task(() =>
{
executables.AddRange(GetInstalledSoftwareExecutables());
executables.AddRange(GetExecutablesFromPath());
executables.AddRange(GetExecutablesFromNetFx());
Executables.AddRange(GetInstalledSoftwareExecutables());
Executables.AddRange(GetExecutablesFromPath());
Executables.AddRange(GetExecutablesFromNetFx());
}).Start();
}
public List<string> GetExecutables()
public bool CanHandle(string path)
{
return executables;
return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase);
}
public async Task HandleAsync(HttpListenerContext context, string path)
{
string word = path.Substring(Prefix.Length);
try
{
CompletionItem[] completionItems = Executables
.Where(exec => exec.IndexOf(word, 0, StringComparison.OrdinalIgnoreCase) > -1)
.Take(100) // Limit the number of results
.Select(exec => new CompletionItem
{
Label = Path.GetFileName(exec),
Kind = "Text",
Documentation = $"An executable file: {exec}",
InsertText = exec
})
.ToArray();
XElement response = new XElement("suggestions",
completionItems.Select(item => new XElement("item",
new XElement("label", item.Label),
new XElement("kind", item.Kind),
new XElement("documentation", item.Documentation),
new XElement("insertText", item.InsertText)
))
);
Server.ServeResource(context, response.ToString(), "application/xml");
}
catch (Exception ex)
{
Server.ServeResource(context, $"<error>Failed to process completion request. {ex.Message}</error>", "application/xml", 500);
}
await Task.Delay(0);
}
private List<string> GetInstalledSoftwareExecutables()
@ -157,4 +200,12 @@ namespace WelsonJS.Launcher.Tools
return executables;
}
}
public class CompletionItem
{
public string Label { get; set; }
public string Kind { get; set; }
public string Documentation { get; set; }
public string InsertText { get; set; }
}
}

View File

@ -0,0 +1,39 @@
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)
{
string configName = path.Substring(Prefix.Length);
try
{
string configValue = Program.GetAppConfig(configName);
Server.ServeResource(context, configValue, "text/plain");
}
catch (Exception ex)
{
Server.ServeResource(context, $"<error>Failed to process Config request. {ex.Message}</error>", "application/xml", 500);
}
await Task.Delay(0);
}
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace WelsonJS.Launcher.ResourceTools
{
public class DevTools : IResourceTool
{
private ResourceServer Server;
private const string Prefix = "devtools/";
public DevTools(ResourceServer server) {
Server = server;
}
public bool CanHandle(string path)
{
return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase);
}
public async Task HandleAsync(HttpListenerContext context, string path)
{
string endpoint = path.Substring(Prefix.Length);
try
{
using (HttpClient client = new HttpClient())
{
string url = Program.GetAppConfig("DevToolsPrefix") + endpoint;
string data = await client.GetStringAsync(url);
Server.ServeResource(context, data, "application/json");
}
}
catch (Exception ex)
{
Server.ServeResource(context, $"<error>Failed to process DevTools request. {ex.Message}</error>", "application/xml", 500);
}
}
}
}

View File

@ -1,22 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
namespace WelsonJS.Launcher.Tools
namespace WelsonJS.Launcher.ResourceTools
{
public class DnsQuery
{
private static readonly Random _random = new Random();
private readonly string _dnsServer;
private ResourceServer Server;
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(string dnsServer = "8.8.8.8")
public DnsQuery(ResourceServer server, string dnsServer = "8.8.8.8")
{
_dnsServer = dnsServer;
Server = server;
DnsServer = dnsServer;
}
public bool CanHandle(string path)
{
return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase);
}
public async Task HandleAsync(HttpListenerContext context, string path)
{
string query = path.Substring(Prefix.Length);
if (string.IsNullOrWhiteSpace(query) || query.Length > 255)
{
Server.ServeResource(context, "<error>Invalid query parameter</error>", "application/xml", 400);
return;
}
try
{
Dictionary<string, List<string>> allRecords = QueryAll(query);
StringBuilder result = new StringBuilder();
foreach (var recordType in allRecords.Keys)
{
result.AppendLine($"\n{recordType} Records:");
foreach (var record in allRecords[recordType])
{
result.AppendLine(record);
}
}
string data = result.ToString();
Server.ServeResource(context, data, "text/plain", 200);
}
catch (Exception ex)
{
Server.ServeResource(context, $"<error>Failed to process DNS query. {ex.Message}</error>", "application/xml", 500);
}
await Task.Delay(0);
}
public List<string> QueryA(string domain) => QueryDns(domain, 1);
@ -62,7 +106,7 @@ namespace WelsonJS.Launcher.Tools
}
// Basic domain format validation
if (domain.Length > 255 ||
if (domain.Length > 255 ||
!domain.Split('.').All(part => part.Length > 0 && part.Length <= 63))
{
records.Add("Error: Invalid domain format");
@ -71,7 +115,7 @@ namespace WelsonJS.Launcher.Tools
try
{
UdpClient udpClient = new UdpClient(_dnsServer, DnsPort);
UdpClient udpClient = new UdpClient(DnsServer, DnsPort);
udpClient.Client.ReceiveTimeout = Timeout;
byte[] request = CreateDnsQuery(domain, type);

View File

@ -2,13 +2,42 @@
using System.Linq;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
namespace WelsonJS.Launcher.Tools
namespace WelsonJS.Launcher.ResourceTools
{
public class Tfa
public class Tfa : IResourceTool
{
private ResourceServer Server;
private const string Prefix = "tfa/";
private const string Base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
public Tfa(ResourceServer server)
{
Server = server;
}
public bool CanHandle(string path)
{
return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase);
}
public async Task HandleAsync(HttpListenerContext context, string path)
{
string endpoint = path.Substring(Prefix.Length);
if (endpoint.Equals("pubkey"))
{
Server.ServeResource(context, GetPubKey(), "text/plain", 200);
return;
}
Server.ServeResource(context);
await Task.Delay(0);
}
public int GetOtp(string key)
{
byte[] binaryKey = DecodeBase32(key.Replace(" ", ""));

View File

@ -0,0 +1,63 @@
using System.Net;
using System.Threading.Tasks;
using System;
using System.Net.Http;
using System.Text;
namespace WelsonJS.Launcher.ResourceTools
{
public class Whois : IResourceTool
{
private ResourceServer Server;
private const string Prefix = "whois/";
public Whois(ResourceServer server)
{
Server = server;
}
public bool CanHandle(string path)
{
return path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase);
}
public async Task HandleAsync(HttpListenerContext context, string path)
{
string query = path.Substring(Prefix.Length);
if (string.IsNullOrWhiteSpace(query) || query.Length > 255)
{
Server.ServeResource(context, "<error>Invalid query parameter</error>", "application/xml", 400);
return;
}
string whoisServerUrl = "https://xn--c79as89aj0e29b77z.xn--3e0b707e";
using (var client = new HttpClient())
{
client.Timeout = TimeSpan.FromSeconds(10);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"{whoisServerUrl}/kor/whois.jsc")
{
Content = new StringContent($"query={Uri.EscapeDataString(query)}&ip=141.101.82.1", Encoding.UTF8, "application/x-www-form-urlencoded")
};
request.Headers.Add("Accept", "*/*");
request.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.3124.77");
client.DefaultRequestHeaders.Referrer = new Uri($"{whoisServerUrl}/kor/whois/whois.jsp");
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, $"<error>Failed to process WHOIS request. {ex.Message}</error>", "application/xml", 500);
}
}
}
}
}

View File

@ -1,395 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Linq;
namespace WelsonJS.Launcher.Tools
{
public class ResourceServer
{
private readonly HttpListener _listener;
private CancellationTokenSource _cts;
private Task _serverTask;
private bool _isRunning;
private string _prefix;
private string _resourceName;
private ExecutablesCollector _executablesCollector;
public ResourceServer(string prefix, string resourceName)
{
_prefix = prefix;
_listener = new HttpListener();
_listener.Prefixes.Add(prefix);
_resourceName = resourceName;
_executablesCollector = new ExecutablesCollector();
}
public string GetPrefix()
{
return _prefix;
}
public void Start()
{
if (_isRunning) return;
_isRunning = true;
_cts = new CancellationTokenSource();
_listener.Start();
// Open the web browser
Program.OpenWebBrowser(_prefix);
// Run a task with cancellation token
_serverTask = Task.Run(() => ListenLoop(_cts.Token));
}
public void Stop()
{
_isRunning = false;
_cts.Cancel();
_listener.Stop();
MessageBox.Show("Server stopped.");
}
public bool IsRunning()
{
return _isRunning;
}
private async Task ListenLoop(CancellationToken token)
{
while (!token.IsCancellationRequested && _isRunning)
{
try
{
await ProcessRequest(await _listener.GetContextAsync());
}
catch (Exception ex)
{
if (token.IsCancellationRequested || !_isRunning) break;
MessageBox.Show($"Error: {ex.Message}");
}
}
}
private async Task ProcessRequest(HttpListenerContext context)
{
string path = context.Request.Url.AbsolutePath.TrimStart('/');
// Serve the favicon.ico file
if ("favicon.ico".Equals(path, StringComparison.OrdinalIgnoreCase))
{
ServeResource(context, GetResource("favicon"), "image/x-icon");
return;
}
// Serve the code completion (word suggestion)
const string completionPrefix = "completion/";
if (path.StartsWith(completionPrefix, StringComparison.OrdinalIgnoreCase))
{
ServeCompletion(context, path.Substring(completionPrefix.Length));
return;
}
// Serve the DevTools Protocol
const string devtoolsPrefix = "devtools/";
if (path.StartsWith(devtoolsPrefix, StringComparison.OrdinalIgnoreCase))
{
await ServeDevTools(context, path.Substring(devtoolsPrefix.Length));
return;
}
// Serve WHOIS request (use KRNIC server)
const string whoisPrefix = "whois/";
if (path.StartsWith(whoisPrefix, StringComparison.OrdinalIgnoreCase))
{
await ServeWhoisRequest(context, path.Substring(whoisPrefix.Length));
return;
}
// Serve DNS Query request (use Google DNS server)
const string dnsQueryPrefix = "dns-query/";
if (path.StartsWith(dnsQueryPrefix, StringComparison.OrdinalIgnoreCase))
{
ServeDnsQueryRequest(context, path.Substring(dnsQueryPrefix.Length));
return;
}
// Serve TFA request
const string tfaPrefix = "tfa/";
if (path.StartsWith(tfaPrefix, StringComparison.OrdinalIgnoreCase))
{
ServeTfaRequest(context, path.Substring(tfaPrefix.Length));
return;
}
// Serve a value of App Config request
const string configPrefix = "config/";
if (path.StartsWith(configPrefix, StringComparison.OrdinalIgnoreCase))
{
ServeConfigRequest(context, path.Substring(configPrefix.Length));
return;
}
// Serve a resource
ServeResource(context, GetResource(_resourceName), "text/html");
}
private void ServeCompletion(HttpListenerContext context, string word)
{
try
{
List<string> executables = _executablesCollector.GetExecutables();
CompletionItem[] completionItems = executables
.Where(exec => exec.IndexOf(word, 0, StringComparison.OrdinalIgnoreCase) > -1)
.Take(100) // Limit the number of results
.Select(exec => new CompletionItem
{
Label = Path.GetFileName(exec),
Kind = "Text",
Documentation = $"An executable file: {exec}",
InsertText = exec
})
.ToArray();
XElement response = new XElement("suggestions",
completionItems.Select(item => new XElement("item",
new XElement("label", item.Label),
new XElement("kind", item.Kind),
new XElement("documentation", item.Documentation),
new XElement("insertText", item.InsertText)
))
);
ServeResource(context, response.ToString(), "application/xml");
}
catch (Exception ex)
{
ServeResource(context, $"<error>Failed to process completion request. {ex.Message}</error>", "application/xml", 500);
}
}
private async Task ServeDevTools(HttpListenerContext context, string endpoint)
{
try
{
using (HttpClient client = new HttpClient())
{
string url = "http://localhost:9222/" + endpoint;
string data = await client.GetStringAsync(url);
ServeResource(context, data, "application/json");
}
}
catch (Exception ex)
{
ServeResource(context, $"<error>Failed to process DevTools request. {ex.Message}</error>", "application/xml", 500);
}
}
private async Task ServeWhoisRequest(HttpListenerContext context, string query)
{
if (string.IsNullOrWhiteSpace(query) || query.Length > 255)
{
ServeResource(context, "<error>Invalid query parameter</error>", "application/xml", 400);
return;
}
string whoisServerUrl = "https://xn--c79as89aj0e29b77z.xn--3e0b707e";
using (var client = new HttpClient())
{
client.Timeout = TimeSpan.FromSeconds(10);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"{whoisServerUrl}/kor/whois.jsc")
{
Content = new StringContent($"query={Uri.EscapeDataString(query)}&ip=141.101.82.1", Encoding.UTF8, "application/x-www-form-urlencoded")
};
request.Headers.Add("Accept", "*/*");
request.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.3124.77");
client.DefaultRequestHeaders.Referrer = new Uri($"{whoisServerUrl}/kor/whois/whois.jsp");
try
{
HttpResponseMessage response = await client.SendAsync(request);
string responseBody = await response.Content.ReadAsStringAsync();
ServeResource(context, responseBody, "text/plain", (int)response.StatusCode);
}
catch (Exception ex)
{
ServeResource(context, $"<error>Failed to process WHOIS request. {ex.Message}</error>", "application/xml", 500);
}
}
}
private void ServeDnsQueryRequest(HttpListenerContext context, string query)
{
if (string.IsNullOrWhiteSpace(query) || query.Length > 255)
{
ServeResource(context, "<error>Invalid query parameter</error>", "application/xml", 400);
return;
}
try
{
DnsQuery dns = new DnsQuery();
Dictionary<string, List<string>> allRecords = dns.QueryAll(query);
StringBuilder result = new StringBuilder();
foreach (var recordType in allRecords.Keys)
{
result.AppendLine($"\n{recordType} Records:");
foreach (var record in allRecords[recordType])
{
result.AppendLine(record);
}
}
string data = result.ToString();
ServeResource(context, data, "text/plain", 200);
}
catch (Exception ex)
{
ServeResource(context, $"<error>Failed to process DNS query. {ex.Message}</error>", "application/xml", 500);
}
}
private void ServeTfaRequest(HttpListenerContext context, string endpoint)
{
Tfa _tfa = new Tfa();
if (endpoint.Equals("pubkey"))
{
ServeResource(context, _tfa.GetPubKey(), "text/plain", 200);
return;
}
ServeResource(context);
}
public void ServeConfigRequest(HttpListenerContext context, string key)
{
string value = Program.GetAppConfig(key);
if (!String.IsNullOrEmpty(value))
{
ServeResource(context, value, "text/plain", 200);
return;
}
ServeResource(context);
}
private void ServeResource(HttpListenerContext context)
{
ServeResource(context, "<error>Not Found</error>", "application/xml", 404);
}
private void ServeResource(HttpListenerContext context, byte[] data, string mimeType = "text/html", int statusCode = 200)
{
string xmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
if (data == null) {
data = Encoding.UTF8.GetBytes(xmlHeader + "\r\n<error>Could not find the resource.</error>");
mimeType = "application/xml";
statusCode = 404;
}
context.Response.StatusCode = statusCode;
context.Response.ContentType = mimeType;
context.Response.ContentLength64 = data.Length;
using (Stream outputStream = context.Response.OutputStream)
{
outputStream.Write(data, 0, data.Length);
}
}
private void ServeResource(HttpListenerContext context, string data, string mimeType = "text/html", int statusCode = 200)
{
string xmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
if (data == null)
{
data = xmlHeader + "\r\n<error>Could not find the resource.</error>";
mimeType = "application/xml";
statusCode = 404;
}
else if (mimeType == "application/xml" && !data.StartsWith("<?xml"))
{
data = xmlHeader + "\r\n" + data;
}
ServeResource(context, Encoding.UTF8.GetBytes(data), mimeType, statusCode);
}
private byte[] GetResource(string resourceName)
{
// Try to fetch embedded resource.
byte[] data = GetEmbeddedResource(typeof(Program).Namespace + "." + resourceName);
if (data != null) return data;
// Fallback: Try to fetch resource from ResourceManager.
return GetResourceFromManager(resourceName);
}
private byte[] GetEmbeddedResource(string fullResourceName)
{
Assembly assembly = Assembly.GetExecutingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(fullResourceName))
{
if (stream != null)
{
using (MemoryStream memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
}
return null;
}
private byte[] GetResourceFromManager(string resourceName)
{
object resourceObject = Properties.Resources.ResourceManager.GetObject(resourceName);
switch (resourceObject)
{
case byte[] resourceBytes:
return resourceBytes;
case System.Drawing.Icon icon:
return ConvertIconToBytes(icon);
default:
return null;
}
}
private byte[] ConvertIconToBytes(System.Drawing.Icon icon)
{
using (MemoryStream memoryStream = new MemoryStream())
{
icon.Save(memoryStream);
return memoryStream.ToArray();
}
}
}
public class CompletionItem
{
public string Label { get; set; }
public string Kind { get; set; }
public string Documentation { get; set; }
public string InsertText { get; set; }
}
}

View File

@ -73,14 +73,19 @@
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Compile Include="Tools\DnsQuery.cs" />
<Compile Include="IResourceTool.cs" />
<Compile Include="ResourceTools\Config.cs" />
<Compile Include="ResourceTools\Completion.cs" />
<Compile Include="ResourceTools\DevTools.cs" />
<Compile Include="ResourceTools\DnsQuery.cs" />
<Compile Include="ResourceTools\Tfa.cs" />
<Compile Include="ResourceTools\Whois.cs" />
<Compile Include="EnvForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="EnvForm.Designer.cs">
<DependentUpon>EnvForm.cs</DependentUpon>
</Compile>
<Compile Include="Tools\ExecutablesCollector.cs" />
<Compile Include="InstancesForm.cs">
<SubType>Form</SubType>
</Compile>
@ -101,8 +106,7 @@
<Compile Include="GlobalSettingsForm.Designer.cs">
<DependentUpon>GlobalSettingsForm.cs</DependentUpon>
</Compile>
<Compile Include="Tools\ResourceServer.cs" />
<Compile Include="Tools\Tfa.cs" />
<Compile Include="ResourceServer.cs" />
<EmbeddedResource Include="EnvForm.resx">
<DependentUpon>EnvForm.cs</DependentUpon>
</EmbeddedResource>
@ -161,5 +165,6 @@
<ItemGroup>
<EmbeddedResource Include="editor.html" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -4,6 +4,7 @@
<add key="ResourceServerPrefix" value="http://localhost:3000/"/>
<add key="RepositoryUrl" value="https://github.com/gnh1201/welsonjs"/>
<add key="CopilotUrl" value="https://copilot.microsoft.com/"/>
<add key="DevToolsPrefix" value="http://localhost:9222/"/>
<add key="AzureAiServiceUrl" value="https://ai-catswords656881030318.services.ai.azure.com/models/chat/completions?api-version=2024-05-01-preview"/>
<add key="AzureAiServiceApiKey" value=""/>
</appSettings>