Add CORS support to ResourceServer

Implemented CORS handling in ResourceServer, including preflight (OPTIONS) request handling and configurable allowed origins via the new ResourceServerAllowOrigins app setting. Updated resources and configuration files to support the new setting.
This commit is contained in:
Namhyeon Go 2025-10-03 17:35:34 +09:00
parent 8cfff666e4
commit b9e39dd9c7
4 changed files with 111 additions and 1 deletions

View File

@ -331,6 +331,15 @@ namespace WelsonJS.Launcher.Properties {
}
}
/// <summary>
/// 과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>
internal static string ResourceServerAllowOrigins {
get {
return ResourceManager.GetString("ResourceServerAllowOrigins", resourceCulture);
}
}
/// <summary>
/// true과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>

View File

@ -220,4 +220,7 @@
<data name="IpQueryApiPrefix2" xml:space="preserve">
<value>https://api.abuseipdb.com/api/v2/</value>
</data>
<data name="ResourceServerAllowOrigins" xml:space="preserve">
<value />
</data>
</root>

View File

@ -353,6 +353,11 @@ namespace WelsonJS.Launcher
public async Task ServeResource(HttpListenerContext context, byte[] data, string mimeType = "text/html", int statusCode = 200)
{
if (HandleCorsPreflight(context))
return;
TryApplyCors(context);
string xmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
if (data == null)
@ -461,6 +466,98 @@ namespace WelsonJS.Launcher
_logger?.Error($"Failed to fetch a blob config. Exception: {ex}");
}
}
private static string[] GetAllowedOrigins()
{
// 1. Try explicit ResourceServerAllowOrigins config
var raw = Program.GetAppConfig("ResourceServerAllowOrigins");
if (string.IsNullOrEmpty(raw))
{
// 2. Fallback: parse from ResourceServerPrefix
var prefix = Program.GetAppConfig("ResourceServerPrefix");
if (!string.IsNullOrEmpty(prefix))
{
try
{
var uri = new Uri(prefix);
var origin = uri.GetLeftPart(UriPartial.Authority); // protocol + host + port
return new[] { origin };
}
catch
{
return Array.Empty<string>();
}
}
return Array.Empty<string>();
}
// Split configured list
var parts = raw.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim())
.Where(s => !string.IsNullOrEmpty(s))
.ToArray();
return parts;
}
private static bool TryApplyCors(HttpListenerContext context)
{
var origin = context.Request.Headers["Origin"];
if (string.IsNullOrEmpty(origin))
return false;
var allowed = GetAllowedOrigins();
if (allowed.Length == 0)
return false;
var respHeaders = context.Response.Headers;
respHeaders["Vary"] = "Origin";
if (allowed.Any(a => a == "*"))
{
respHeaders["Access-Control-Allow-Origin"] = "*";
return true;
}
if (allowed.Contains(origin, StringComparer.OrdinalIgnoreCase))
{
respHeaders["Access-Control-Allow-Origin"] = origin;
respHeaders["Access-Control-Allow-Credentials"] = "true";
return true;
}
return false;
}
private static bool HandleCorsPreflight(HttpListenerContext context)
{
if (!string.Equals(context.Request.HttpMethod, "OPTIONS", StringComparison.OrdinalIgnoreCase))
return false;
if (string.IsNullOrEmpty(context.Request.Headers["Origin"]))
return false;
// Apply CORS headers once here
TryApplyCors(context);
var requestHeaders = context.Request.Headers["Access-Control-Request-Headers"];
var requestMethod = context.Request.Headers["Access-Control-Request-Method"];
var h = context.Response.Headers;
h["Access-Control-Allow-Methods"] = string.IsNullOrEmpty(requestMethod)
? "GET, POST, PUT, DELETE, OPTIONS"
: requestMethod;
h["Access-Control-Allow-Headers"] = string.IsNullOrEmpty(requestHeaders)
? "Content-Type, Authorization, X-Requested-With"
: requestHeaders;
h["Access-Control-Max-Age"] = "600";
context.Response.StatusCode = 204;
context.Response.ContentLength64 = 0;
context.Response.OutputStream.Close();
return true;
}
}
[XmlRoot("blobConfig")]

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="ResourceServerPrefix" value="http://localhost:3000/"/>
<add key="ResourceServerAllowOrigins" value=""/>
<add key="ResourceServerAutoStart" value="true"/>
<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="ChromiumDevToolsPrefix" value="http://localhost:9222/"/>