diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs
index e40782d..66bbb56 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs
@@ -60,15 +60,6 @@ namespace WelsonJS.Launcher.Properties {
}
}
- ///
- /// https://ajax.aspnetcdn.com/과(와) 유사한 지역화된 문자열을 찾습니다.
- ///
- internal static string AspNetCdnPrefix {
- get {
- return ResourceManager.GetString("AspNetCdnPrefix", resourceCulture);
- }
- }
-
///
/// 과(와) 유사한 지역화된 문자열을 찾습니다.
///
@@ -96,6 +87,15 @@ namespace WelsonJS.Launcher.Properties {
}
}
+ ///
+ /// https://catswords.blob.core.windows.net/welsonjs/blob.config.xml과(와) 유사한 지역화된 문자열을 찾습니다.
+ ///
+ internal static string BlobConfigUrl {
+ get {
+ return ResourceManager.GetString("BlobConfigUrl", resourceCulture);
+ }
+ }
+
///
/// https://catswords.blob.core.windows.net/welsonjs/과(와) 유사한 지역화된 문자열을 찾습니다.
///
@@ -105,15 +105,6 @@ namespace WelsonJS.Launcher.Properties {
}
}
- ///
- /// https://cdnjs.cloudflare.com/과(와) 유사한 지역화된 문자열을 찾습니다.
- ///
- internal static string CdnJsPrefix {
- get {
- return ResourceManager.GetString("CdnJsPrefix", resourceCulture);
- }
- }
-
///
/// https://copilot.microsoft.com/과(와) 유사한 지역화된 문자열을 찾습니다.
///
@@ -141,24 +132,6 @@ namespace WelsonJS.Launcher.Properties {
}
}
- ///
- /// https://esm.run/과(와) 유사한 지역화된 문자열을 찾습니다.
- ///
- internal static string EsmRunPrefix {
- get {
- return ResourceManager.GetString("EsmRunPrefix", resourceCulture);
- }
- }
-
- ///
- /// https://esm.sh/과(와) 유사한 지역화된 문자열을 찾습니다.
- ///
- internal static string EsmShPrefix {
- get {
- return ResourceManager.GetString("EsmShPrefix", resourceCulture);
- }
- }
-
///
/// (아이콘)과(와) 유사한 System.Drawing.Icon 형식의 지역화된 리소스를 찾습니다.
///
@@ -170,11 +143,11 @@ namespace WelsonJS.Launcher.Properties {
}
///
- /// https://ajax.googleapis.com/과(와) 유사한 지역화된 문자열을 찾습니다.
+ /// 90과(와) 유사한 지역화된 문자열을 찾습니다.
///
- internal static string GoogleApisPrefix {
+ internal static string HttpClientTimeout {
get {
- return ResourceManager.GetString("GoogleApisPrefix", resourceCulture);
+ return ResourceManager.GetString("HttpClientTimeout", resourceCulture);
}
}
@@ -268,42 +241,6 @@ namespace WelsonJS.Launcher.Properties {
}
}
- ///
- /// https://code.jquery.com/과(와) 유사한 지역화된 문자열을 찾습니다.
- ///
- internal static string JqueryCdnPrefix {
- get {
- return ResourceManager.GetString("JqueryCdnPrefix", resourceCulture);
- }
- }
-
- ///
- /// https://cdn.jsdelivr.net/과(와) 유사한 지역화된 문자열을 찾습니다.
- ///
- internal static string JsDeliverPrefix {
- get {
- return ResourceManager.GetString("JsDeliverPrefix", resourceCulture);
- }
- }
-
- ///
- /// https://polyfill-fastly.io/과(와) 유사한 지역화된 문자열을 찾습니다.
- ///
- internal static string PolyfillPrefix {
- get {
- return ResourceManager.GetString("PolyfillPrefix", resourceCulture);
- }
- }
-
- ///
- /// https://raw.githubusercontent.com/과(와) 유사한 지역화된 문자열을 찾습니다.
- ///
- internal static string RawGitHubPrefix {
- get {
- return ResourceManager.GetString("RawGitHubPrefix", resourceCulture);
- }
- }
-
///
/// https://github.com/gnh1201/welsonjs과(와) 유사한 지역화된 문자열을 찾습니다.
///
@@ -322,24 +259,6 @@ namespace WelsonJS.Launcher.Properties {
}
}
- ///
- /// https://www.skypack.dev/과(와) 유사한 지역화된 문자열을 찾습니다.
- ///
- internal static string SkypackPrefix {
- get {
- return ResourceManager.GetString("SkypackPrefix", resourceCulture);
- }
- }
-
- ///
- /// https://unpkg.com/과(와) 유사한 지역화된 문자열을 찾습니다.
- ///
- internal static string UnpkgPrefix {
- get {
- return ResourceManager.GetString("UnpkgPrefix", resourceCulture);
- }
- }
-
///
/// 141.101.82.1과(와) 유사한 지역화된 문자열을 찾습니다.
///
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx
index 5e041d3..ed4a71f 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx
@@ -184,37 +184,10 @@
2024-05-01-preview
-
- https://cdnjs.cloudflare.com/
+
+ https://catswords.blob.core.windows.net/welsonjs/blob.config.xml
-
- https://esm.run/
-
-
- https://esm.sh/
-
-
- https://code.jquery.com/
-
-
- https://cdn.jsdelivr.net/
-
-
- https://www.skypack.dev/
-
-
- https://unpkg.com/
-
-
- https://ajax.aspnetcdn.com/
-
-
- https://ajax.googleapis.com/
-
-
- https://polyfill-fastly.io/
-
-
- https://raw.githubusercontent.com/
+
+ 90
\ No newline at end of file
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs
index caf1f72..d972332 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ResourceServer.cs
@@ -17,6 +17,7 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
+using System.Xml.Serialization;
namespace WelsonJS.Launcher
{
@@ -29,26 +30,19 @@ namespace WelsonJS.Launcher
private string _prefix;
private string _resourceName;
private List _tools = new List();
- private readonly HttpClient _httpClient = new HttpClient();
+ private static readonly HttpClient _httpClient = new HttpClient();
private static readonly string _defaultMimeType = "application/octet-stream";
- private static readonly Regex _nodePackageRegex = new Regex(@"^[^/@]+@[^/]+/", RegexOptions.Compiled);
- private static readonly List CDN_PREFIXES = new List {
- new[] { "ajax/libs/" },
- new[] { "npm/", "gh/", "wp/" },
- new[] { "jquery/" },
- new[] { "polyfill/" },
- new[] { "ajax/" }, // https://learn.microsoft.com/en-us/aspnet/ajax/cdn/overview
- new[] { "raw/gh/"}
- };
- private enum CDN_TYPES: int
+ private static BlobConfig _blobConfig;
+
+ static ResourceServer()
{
- Cloudflare = 0,
- JsDeliver = 1,
- Jquery = 2,
- Polyfill = 3,
- Microsoft = 4,
- GitHub = 5
- };
+ // Set timeout
+ int timeout = int.TryParse(Program.GetAppConfig("HttpClientTimeout"), out timeout) ? timeout : 90;
+ _httpClient.Timeout = TimeSpan.FromSeconds(timeout);
+
+ // Fetch a blob config from Internet
+ FetchBlobConfig();
+ }
public ResourceServer(string prefix, string resourceName)
{
@@ -56,7 +50,6 @@ namespace WelsonJS.Launcher
_listener = new HttpListener();
_listener.Prefixes.Add(prefix);
_resourceName = resourceName;
- _httpClient.Timeout = TimeSpan.FromSeconds(30);
// Add resource tools
_tools.Add(new ResourceTools.Completion(this, _httpClient));
@@ -100,7 +93,7 @@ namespace WelsonJS.Launcher
{
return _isRunning;
}
-
+
private async Task ListenLoop(CancellationToken token)
{
while (!token.IsCancellationRequested && _isRunning)
@@ -207,8 +200,8 @@ namespace WelsonJS.Launcher
}
}
- // use CDN sources
- if (await TryServeFromCdn(context, path))
+ // use a blob source
+ if (await TryServeFromBlob(context, path))
{
return true;
}
@@ -217,48 +210,33 @@ namespace WelsonJS.Launcher
return false;
}
- private async Task TryServeFromCdn(HttpListenerContext context, string path)
+ private async Task TryServeFromBlob(HttpListenerContext context, string path)
{
- bool isNodePackageExpression = _nodePackageRegex.IsMatch(path);
- bool isPrefixMatched(CDN_TYPES type)
+ if (_blobConfig != null)
{
- if (CDN_PREFIXES[(int)type].Any(prefix => path.StartsWith(prefix)))
+ foreach (var route in _blobConfig.Routes)
{
- return true;
- }
-
- return false;
- }
-
- var sources = new (bool isMatch, string configKey, Func transform)[]
- {
- (isPrefixMatched(CDN_TYPES.Cloudflare), "CdnJsPrefix", p => p), // Libraries from Cloudflare
- (isPrefixMatched(CDN_TYPES.Cloudflare), "GoogleApisPrefix", p => p), // Libraries from Google
- (isNodePackageExpression, "UnpkgPrefix", p => p),
- (isNodePackageExpression, "SkypackPrefix", p => p),
- (isNodePackageExpression, "EsmShPrefix", p => p),
- (isNodePackageExpression, "EsmRunPrefix", p => p),
- (isPrefixMatched(CDN_TYPES.JsDeliver), "JsDeliverPrefix", p => p),
- (isPrefixMatched(CDN_TYPES.Jquery), "JqueryCdnPrefix", p => p.Substring("jquery/".Length)),
- (isPrefixMatched(CDN_TYPES.Polyfill), "CdnJsPrefix", p => p), // polyfill.js from Cloudflare
- (isPrefixMatched(CDN_TYPES.Polyfill), "PolyfillPrefix", p => p.Substring("polyfill/".Length)), // polyfill.js from Fastly
- (isPrefixMatched(CDN_TYPES.Microsoft), "AspNetCdnPrefix", p => p), // Libraries from Microsoft
- (isPrefixMatched(CDN_TYPES.GitHub), "RawGitHubPrefix", p => p.Substring("raw/gh/".Length)),
- (true, "BlobStoragePrefix", p => p) // fallback
- };
-
- foreach (var (isMatch, configKey, transform) in sources)
- {
- if (isMatch)
- {
- string prefix = Program.GetAppConfig(configKey);
- if (await ServeBlob(context, transform(path), prefix))
+ foreach (var (regex, index) in route.RegexConditions.Select((r, i) => (r, i)))
{
- return true;
+ if (!regex.Compiled.IsMatch(path)) continue;
+
+ var match = (index < route.Matches.Count) ? route.Matches[index] : route.Matches.First();
+ var _path = route.StripPrefix ? path.Substring(match.Length) : path;
+
+ foreach (var prefixUrl in route.PrefixUrls)
+ {
+ if (await ServeBlob(context, _path, prefixUrl))
+ return true;
+ }
}
}
}
+ // fallback
+ string prefix = Program.GetAppConfig("BlobStoragePrefix");
+ if (await ServeBlob(context, path, prefix))
+ return true;
+
return false;
}
@@ -360,7 +338,8 @@ namespace WelsonJS.Launcher
{
string xmlHeader = "";
- if (data == null) {
+ if (data == null)
+ {
data = Encoding.UTF8.GetBytes(xmlHeader + "\r\nCould not find the resource.");
mimeType = "application/xml";
statusCode = 404;
@@ -375,7 +354,7 @@ namespace WelsonJS.Launcher
}
}
- public void ServeResource(HttpListenerContext context, string data, string mimeType = "text/html", int statusCode = 200)
+ public void ServeResource(HttpListenerContext context, string data, string mimeType = "text/html", int statusCode = 200)
{
string xmlHeader = "";
@@ -442,5 +421,80 @@ namespace WelsonJS.Launcher
return memoryStream.ToArray();
}
}
+
+ private static async void FetchBlobConfig()
+ {
+ try
+ {
+ string url = Program.GetAppConfig("BlobConfigUrl");
+ var response = await _httpClient.GetStreamAsync(url);
+
+ var serializer = new XmlSerializer(typeof(BlobConfig));
+ using (var reader = new StreamReader(response))
+ {
+ _blobConfig = (BlobConfig)serializer.Deserialize(reader);
+ }
+
+ _blobConfig?.Compile();
+ }
+ catch (Exception ex)
+ {
+ Trace.TraceError($"Failed to fetch a blob config. Exception: {ex.Message}");
+ }
+ }
}
-}
+
+ [XmlRoot("blobConfig")]
+ public class BlobConfig
+ {
+ [XmlArray("routes")]
+ [XmlArrayItem("route")]
+ public List Routes { get; set; } = new List();
+
+ public void Compile()
+ {
+ foreach (var route in Routes)
+ {
+ if (route.Matches == null) continue;
+
+ route.RegexConditions = new List();
+ foreach (var match in route.Matches)
+ {
+ route.RegexConditions.Add(new RegexCondition
+ {
+ Pattern = match,
+ Compiled = new Regex(
+ match.StartsWith("^") ? match : "^" + Regex.Escape(match),
+ RegexOptions.Compiled)
+ });
+ }
+ }
+ }
+ }
+
+ public class BlobRoute
+ {
+ [XmlArray("matches")]
+ [XmlArrayItem("match")]
+ public List Matches { get; set; }
+
+ [XmlArray("prefixUrls")]
+ [XmlArrayItem("url")]
+ public List PrefixUrls { get; set; }
+
+ [XmlAttribute("stripPrefix")]
+ public bool StripPrefix { get; set; }
+
+ [XmlIgnore]
+ public List RegexConditions { get; set; }
+ }
+
+ public class RegexCondition
+ {
+ [XmlIgnore]
+ public string Pattern { get; set; }
+
+ [XmlIgnore]
+ public Regex Compiled { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config
index 4bd67b0..8c641cf 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config
@@ -13,16 +13,8 @@
-
-
-
-
-
-
-
-
-
-
+
+
diff --git a/data/blob.config.json b/data/blob.config.json
new file mode 100644
index 0000000..d3c2589
--- /dev/null
+++ b/data/blob.config.json
@@ -0,0 +1,74 @@
+{
+ "routes": [
+ {
+ "matches": [
+ "ajax/libs/"
+ ],
+ "prefixUrls": [
+ "https://cdnjs.cloudflare.com/",
+ "https://ajax.googleapis.com/"
+ ],
+ "stripPrefix": false
+ },
+ {
+ "matches": [
+ "npm/",
+ "gh/",
+ "wp/"
+ ],
+ "prefixUrls": [
+ "https://cdn.jsdelivr.net/"
+ ],
+ "stripPrefix": false
+ },
+ {
+ "matches": [
+ "^[^/@]+@[^/]+/"
+ ],
+ "prefixUrls": [
+ "https://unpkg.com/",
+ "https://www.skypack.dev/",
+ "https://esm.sh/",
+ "https://esm.run/"
+ ],
+ "stripPrefix": false
+ },
+ {
+ "matches": [
+ "jquery/"
+ ],
+ "prefixUrls": [
+ "https://code.jquery.com/"
+ ],
+ "stripPrefix": true
+ },
+ {
+ "matches": [
+ "polyfill/"
+ ],
+ "prefixUrls": [
+ "https://cdnjs.cloudflare.com/",
+ "https://polyfill-fastly.io/"
+ ],
+ "stripPrefix": true
+ },
+ {
+ "matches": [
+ "ajax/"
+ ],
+ "prefixUrls": [
+ "https://ajax.aspnetcdn.com/"
+ ],
+ "stripPrefix": true
+ },
+ {
+ "matches": [
+ "raw/gh/"
+ ],
+ "prefixUrls": [
+ "https://raw.githubusercontent.com/"
+ ],
+ "stripPrefix": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/data/blob.config.xml b/data/blob.config.xml
new file mode 100644
index 0000000..3166933
--- /dev/null
+++ b/data/blob.config.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+ ajax/libs/
+
+
+ https://cdnjs.cloudflare.com/
+ https://ajax.googleapis.com/
+
+
+
+
+ npm/
+ gh/
+ wp/
+
+
+ https://cdn.jsdelivr.net/
+
+
+
+
+ ^[^/@]+@[^/]+/
+
+
+ https://unpkg.com/
+ https://www.skypack.dev/
+ https://esm.sh/
+ https://esm.run/
+
+
+
+
+ jquery/
+
+
+ https://code.jquery.com/
+
+
+
+
+ polyfill/
+
+
+ https://cdnjs.cloudflare.com/
+ https://polyfill-fastly.io/
+
+
+
+
+ ajax/
+
+
+ https://ajax.aspnetcdn.com/
+
+
+
+
+ raw/gh/
+
+
+ https://raw.githubusercontent.com/
+
+
+
+