浏览代码

Merge pull request #63 from dotnetcore/ip-speed-test

Ip speed test
老九 3 年之前
父节点
当前提交
e8c5fd5216

+ 1 - 1
FastGithub.Configuration/DomainPattern.cs

@@ -7,7 +7,7 @@ namespace FastGithub.Configuration
     /// 表示域名表达式
     /// *表示除.之外任意0到多个字符
     /// </summary>
-    sealed class DomainPattern : IComparable<DomainPattern>
+    public class DomainPattern : IComparable<DomainPattern>
     {
         private readonly Regex regex;
         private readonly string domainPattern;

+ 2 - 2
FastGithub.Configuration/FastGithubConfig.cs

@@ -117,9 +117,9 @@ namespace FastGithub.Configuration
         /// 获取所有域名表达式
         /// </summary>
         /// <returns></returns>
-        public string[] GetDomainPatterns()
+        public DomainPattern[] GetDomainPatterns()
         {
-            return this.domainConfigs.Keys.Select(item => item.ToString()).ToArray();
+            return this.domainConfigs.Keys.ToArray();
         }
     }
 }

+ 64 - 5
FastGithub.DomainResolve/DnsClient.cs

@@ -2,13 +2,16 @@
 using DNS.Client.RequestResolver;
 using DNS.Protocol;
 using DNS.Protocol.ResourceRecords;
+using FastGithub.Configuration;
 using Microsoft.Extensions.Caching.Memory;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
 using System;
 using System.Collections.Concurrent;
+using System.Collections.Generic;
 using System.Linq;
 using System.Net;
+using System.Runtime.CompilerServices;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -21,22 +24,72 @@ namespace FastGithub.DomainResolve
     {
         private const int DNS_PORT = 53;
         private const string LOCALHOST = "localhost";
+
+        private readonly DnscryptProxy dnscryptProxy;
+        private readonly FastGithubConfig fastGithubConfig;
         private readonly ILogger<DnsClient> logger;
 
         private readonly ConcurrentDictionary<string, SemaphoreSlim> semaphoreSlims = new();
         private readonly IMemoryCache dnsCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
-        private readonly TimeSpan dnsExpiration = TimeSpan.FromMinutes(2d);
+        private readonly TimeSpan dnsExpiration = TimeSpan.FromMinutes(1d);
         private readonly int resolveTimeout = (int)TimeSpan.FromSeconds(2d).TotalMilliseconds;
 
         /// <summary>
         /// DNS客户端
-        /// </summary> 
+        /// </summary>
+        /// <param name="dnscryptProxy"></param>
+        /// <param name="fastGithubConfig"></param>
         /// <param name="logger"></param>
-        public DnsClient(ILogger<DnsClient> logger)
+        public DnsClient(
+            DnscryptProxy dnscryptProxy,
+            FastGithubConfig fastGithubConfig,
+            ILogger<DnsClient> logger)
         {
+            this.dnscryptProxy = dnscryptProxy;
+            this.fastGithubConfig = fastGithubConfig;
             this.logger = logger;
         }
 
+        /// <summary>
+        /// 解析域名
+        /// </summary>
+        /// <param name="domain">域名</param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async IAsyncEnumerable<IPAddress> ResolveAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken)
+        {
+            var hashSet = new HashSet<IPAddress>();
+            foreach (var dns in this.GetDnsServers())
+            {
+                foreach (var address in await this.LookupAsync(dns, domain, cancellationToken))
+                {
+                    if (hashSet.Add(address) == true)
+                    {
+                        yield return address;
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// 获取dns服务
+        /// </summary>
+        /// <returns></returns>
+        private IEnumerable<IPEndPoint> GetDnsServers()
+        {
+            var cryptDns = this.dnscryptProxy.LocalEndPoint;
+            if (cryptDns != null)
+            {
+                yield return cryptDns;
+                yield return cryptDns;
+            }
+
+            foreach (var fallbackDns in this.fastGithubConfig.FallbackDns)
+            {
+                yield return fallbackDns;
+            }
+        }
+
         /// <summary>
         /// 解析域名
         /// </summary>
@@ -44,7 +97,7 @@ namespace FastGithub.DomainResolve
         /// <param name="domain"></param>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
-        public async Task<IPAddress[]> LookupAsync(IPEndPoint dns, string domain, CancellationToken cancellationToken = default)
+        private async Task<IPAddress[]> LookupAsync(IPEndPoint dns, string domain, CancellationToken cancellationToken = default)
         {
             var key = $"{dns}:{domain}";
             var semaphore = this.semaphoreSlims.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
@@ -96,10 +149,16 @@ namespace FastGithub.DomainResolve
                 RecursionDesired = true,
                 OperationCode = OperationCode.Query
             };
+
             request.Questions.Add(new Question(new Domain(domain), RecordType.A));
             var clientRequest = new ClientRequest(resolver, request);
             var response = await clientRequest.Resolve(cancellationToken);
-            return response.AnswerRecords.OfType<IPAddressResourceRecord>().Select(item => item.IPAddress).ToArray();
+
+            return response.AnswerRecords
+                .OfType<IPAddressResourceRecord>()
+                .Where(item => IPAddress.IsLoopback(item.IPAddress) == false)
+                .Select(item => item.IPAddress)
+                .ToArray();
         }
     }
 }

+ 55 - 19
FastGithub.DomainResolve/DnscryptProxy.cs

@@ -1,4 +1,5 @@
 using FastGithub.Configuration;
+using Microsoft.Extensions.Logging;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics;
@@ -20,6 +21,8 @@ namespace FastGithub.DomainResolve
         private const string PATH = "dnscrypt-proxy";
         private const string NAME = "dnscrypt-proxy";
 
+        private readonly ILogger<DnscryptProxy> logger;
+
         /// <summary>
         /// 相关进程
         /// </summary>
@@ -30,12 +33,38 @@ namespace FastGithub.DomainResolve
         /// </summary>
         public IPEndPoint? LocalEndPoint { get; private set; }
 
+        /// <summary>
+        /// DnscryptProxy服务
+        /// </summary>
+        /// <param name="logger"></param>
+        public DnscryptProxy(ILogger<DnscryptProxy> logger)
+        {
+            this.logger = logger;
+        }
+
         /// <summary>
         /// 启动dnscrypt-proxy
         /// </summary>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
         public async Task StartAsync(CancellationToken cancellationToken)
+        {
+            try
+            {
+                await this.StartCoreAsync(cancellationToken);
+            }
+            catch (Exception ex)
+            {
+                this.logger.LogWarning($"{NAME}启动失败:{ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 启动dnscrypt-proxy
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        private async Task StartCoreAsync(CancellationToken cancellationToken)
         {
             var tomlPath = Path.Combine(PATH, $"{NAME}.toml");
             var port = GetAvailablePort(IPAddress.Loopback.AddressFamily);
@@ -70,7 +99,32 @@ namespace FastGithub.DomainResolve
             }
         }
 
-
+        /// <summary>
+        /// 停止服务
+        /// </summary>
+        public void Stop()
+        {
+            try
+            {
+                if (OperatingSystem.IsWindows())
+                {
+                    StartDnscryptProxy("-service stop")?.WaitForExit();
+                    StartDnscryptProxy("-service uninstall")?.WaitForExit();
+                }
+                if (this.process != null && this.process.HasExited == false)
+                {
+                    this.process.Kill();
+                }
+            }
+            catch (Exception ex)
+            {
+                this.logger.LogWarning($"{NAME}停止失败:{ex.Message }");
+            }
+            finally
+            {
+                this.LocalEndPoint = null;
+            }
+        }
 
         /// <summary>
         /// 获取可用的随机端口
@@ -113,24 +167,6 @@ namespace FastGithub.DomainResolve
             this.LocalEndPoint = null;
         }
 
-        /// <summary>
-        /// 停止dnscrypt-proxy
-        /// </summary>
-        public void Stop()
-        {
-            if (OperatingSystem.IsWindows())
-            {
-                StartDnscryptProxy("-service stop")?.WaitForExit();
-                StartDnscryptProxy("-service uninstall")?.WaitForExit();
-            }
-
-            if (this.process != null && this.process.HasExited == false)
-            {
-                this.process.Kill();
-            }
-            this.LocalEndPoint = null;
-        }
-
         /// <summary>
         /// 启动DnscryptProxy进程
         /// </summary>

+ 20 - 25
FastGithub.DomainResolve/DnscryptProxyHostedService.cs → FastGithub.DomainResolve/DomainResolveHostedService.cs

@@ -1,5 +1,4 @@
 using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Logging;
 using System;
 using System.Threading;
 using System.Threading.Tasks;
@@ -7,58 +6,54 @@ using System.Threading.Tasks;
 namespace FastGithub.DomainResolve
 {
     /// <summary>
-    /// DnscryptProxy后台服务
+    /// 域名解析后台服务
     /// </summary>
-    sealed class DnscryptProxyHostedService : BackgroundService
+    sealed class DomainResolveHostedService : BackgroundService
     {
-        private readonly ILogger<DnscryptProxyHostedService> logger;
         private readonly DnscryptProxy dnscryptProxy;
+        private readonly DomainSpeedTester speedTester;
+
+        private readonly TimeSpan speedTestDueTime = TimeSpan.FromSeconds(10d);
+        private readonly TimeSpan speedTestPeriod = TimeSpan.FromMinutes(2d);
 
         /// <summary>
-        /// DnscryptProxy后台服务
+        /// 域名解析后台服务
         /// </summary>
         /// <param name="dnscryptProxy"></param>
-        /// <param name="logger"></param>
-        public DnscryptProxyHostedService(
+        /// <param name="speedTester"></param> 
+        public DomainResolveHostedService(
             DnscryptProxy dnscryptProxy,
-            ILogger<DnscryptProxyHostedService> logger)
+            DomainSpeedTester speedTester)
         {
             this.dnscryptProxy = dnscryptProxy;
-            this.logger = logger;
+            this.speedTester = speedTester;
         }
 
         /// <summary>
-        /// 启动dnscrypt-proxy
+        /// 后台任务
         /// </summary>
         /// <param name="stoppingToken"></param>
         /// <returns></returns>
         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
         {
-            try
-            {
-                await this.dnscryptProxy.StartAsync(stoppingToken);
-            }
-            catch (Exception ex)
+            await this.dnscryptProxy.StartAsync(stoppingToken);
+            await Task.Delay(this.speedTestDueTime, stoppingToken);
+
+            while (stoppingToken.IsCancellationRequested == false)
             {
-                this.logger.LogWarning($"{this.dnscryptProxy}启动失败:{ex.Message}");
+                await this.speedTester.TestSpeedAsync(stoppingToken);
+                await Task.Delay(this.speedTestPeriod, stoppingToken);
             }
         }
 
         /// <summary>
-        /// 停止dnscrypt-proxy
+        /// 停止服务
         /// </summary>
         /// <param name="cancellationToken"></param>
         /// <returns></returns>
         public override Task StopAsync(CancellationToken cancellationToken)
         {
-            try
-            {
-                this.dnscryptProxy.Stop();
-            }
-            catch (Exception ex)
-            {
-                this.logger.LogWarning($"{this.dnscryptProxy}停止失败:{ex.Message}");
-            }
+            this.dnscryptProxy.Stop();
             return base.StopAsync(cancellationToken);
         }
     }

+ 15 - 33
FastGithub.DomainResolve/DomainResolver.cs

@@ -12,24 +12,20 @@ namespace FastGithub.DomainResolve
     /// </summary> 
     sealed class DomainResolver : IDomainResolver
     {
-        private readonly DnscryptProxy dnscryptProxy;
-        private readonly FastGithubConfig fastGithubConfig;
         private readonly DnsClient dnsClient;
+        private readonly DomainSpeedTester speedTester;
 
         /// <summary>
         /// 域名解析器
         /// </summary>
-        /// <param name="dnscryptProxy"></param>
-        /// <param name="fastGithubConfig"></param>
         /// <param name="dnsClient"></param>
+        /// <param name="speedTester"></param>
         public DomainResolver(
-            DnscryptProxy dnscryptProxy,
-            FastGithubConfig fastGithubConfig,
-            DnsClient dnsClient)
+            DnsClient dnsClient,
+            DomainSpeedTester speedTester)
         {
-            this.dnscryptProxy = dnscryptProxy;
-            this.fastGithubConfig = fastGithubConfig;
             this.dnsClient = dnsClient;
+            this.speedTester = speedTester;
         }
 
         /// <summary>
@@ -55,35 +51,21 @@ namespace FastGithub.DomainResolve
         /// <returns></returns>
         public async IAsyncEnumerable<IPAddress> ResolveAllAsync(string domain, [EnumeratorCancellation] CancellationToken cancellationToken)
         {
-            var hashSet = new HashSet<IPAddress>();
-            foreach (var dns in this.GetDnsServers())
+            var addresses = this.speedTester.GetIPAddresses(domain);
+            if (addresses.Length > 0)
             {
-                foreach (var address in await this.dnsClient.LookupAsync(dns, domain, cancellationToken))
+                foreach (var address in addresses)
                 {
-                    if (hashSet.Add(address) == true)
-                    {
-                        yield return address;
-                    }
+                    yield return address;
                 }
             }
-        }
-
-        /// <summary>
-        /// 获取dns服务
-        /// </summary>
-        /// <returns></returns>
-        private IEnumerable<IPEndPoint> GetDnsServers()
-        {
-            var cryptDns = this.dnscryptProxy.LocalEndPoint;
-            if (cryptDns != null)
+            else
             {
-                yield return cryptDns;
-                yield return cryptDns;
-            }
-
-            foreach (var fallbackDns in this.fastGithubConfig.FallbackDns)
-            {
-                yield return fallbackDns;
+                this.speedTester.Add(domain);
+                await foreach (var address in this.dnsClient.ResolveAsync(domain, cancellationToken))
+                {
+                    yield return address;
+                }
             }
         }
     }

+ 151 - 0
FastGithub.DomainResolve/DomainSpeedTester.cs

@@ -0,0 +1,151 @@
+using FastGithub.Configuration;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace FastGithub.DomainResolve
+{
+    /// <summary>
+    /// 域名的IP测速服务
+    /// </summary>
+    sealed class DomainSpeedTester
+    {
+        private const string DOMAINS_JSON_FILE = "domains.json";
+
+        private readonly DnsClient dnsClient;
+        private readonly ILogger<DomainSpeedTester> logger;
+
+        private readonly object syncRoot = new();
+        private readonly Dictionary<string, IPAddressItemHashSet> domainIPAddressHashSet = new();
+
+        /// <summary>
+        /// 域名的IP测速服务
+        /// </summary>
+        /// <param name="dnsClient"></param>
+        /// <param name="logger"></param>
+        public DomainSpeedTester(
+            DnsClient dnsClient,
+            ILogger<DomainSpeedTester> logger)
+        {
+            this.dnsClient = dnsClient;
+            this.logger = logger;
+
+            try
+            {
+                this.LoadDomains();
+            }
+            catch (Exception ex)
+            {
+                logger.LogWarning($"加载域名数据失败:{ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// 加载域名数据
+        /// </summary> 
+        private void LoadDomains()
+        {
+            if (File.Exists(DOMAINS_JSON_FILE) == false)
+            {
+                return;
+            }
+
+            var utf8Json = File.ReadAllBytes(DOMAINS_JSON_FILE);
+            var domains = JsonSerializer.Deserialize<string[]>(utf8Json);
+            if (domains == null)
+            {
+                return;
+            }
+
+            foreach (var domain in domains)
+            {
+                this.domainIPAddressHashSet.TryAdd(domain, new IPAddressItemHashSet());
+            }
+        }
+
+        /// <summary>
+        /// 添加要测速的域名
+        /// </summary>
+        /// <param name="domain"></param>
+        public void Add(string domain)
+        {
+            lock (this.syncRoot)
+            {
+                if (this.domainIPAddressHashSet.TryAdd(domain, new IPAddressItemHashSet()))
+                {
+                    try
+                    {
+                        this.SaveDomains();
+                    }
+                    catch (Exception ex)
+                    {
+                        logger.LogWarning($"保存域名数据失败:{ex.Message}");
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// 保存域名
+        /// </summary>
+        private void SaveDomains()
+        {
+            var domains = this.domainIPAddressHashSet.Keys
+                .Select(item => new DomainPattern(item))
+                .OrderBy(item => item)
+                .Select(item => item.ToString())
+                .ToArray();
+
+            var utf8Json = JsonSerializer.SerializeToUtf8Bytes(domains, new JsonSerializerOptions { WriteIndented = true });
+            File.WriteAllBytes(DOMAINS_JSON_FILE, utf8Json);
+        }
+
+        /// <summary>
+        /// 获取测试后排序的IP
+        /// </summary>
+        /// <param name="domain"></param>
+        /// <returns></returns>
+        public IPAddress[] GetIPAddresses(string domain)
+        {
+            lock (this.syncRoot)
+            {
+                if (this.domainIPAddressHashSet.TryGetValue(domain, out var hashSet) && hashSet.Count > 0)
+                {
+                    return hashSet.ToArray().OrderBy(item => item.PingElapsed).Select(item => item.Address).ToArray();
+                }
+                return Array.Empty<IPAddress>();
+            }
+        }
+
+        /// <summary>
+        /// 进行一轮IP测速
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task TestSpeedAsync(CancellationToken cancellationToken)
+        {
+            KeyValuePair<string, IPAddressItemHashSet>[] keyValues;
+            lock (this.syncRoot)
+            {
+                keyValues = this.domainIPAddressHashSet.ToArray();
+            }
+
+            foreach (var keyValue in keyValues)
+            {
+                var domain = keyValue.Key;
+                var hashSet = keyValue.Value;
+                await foreach (var address in this.dnsClient.ResolveAsync(domain, cancellationToken))
+                {
+                    hashSet.Add(new IPAddressItem(address));
+                }
+                await hashSet.PingAllAsync();
+            }
+        }
+    }
+}

+ 12 - 0
FastGithub.DomainResolve/FastGithub.DomainResolve.csproj

@@ -17,4 +17,16 @@
 		</None>
 	</ItemGroup>
 
+	<ItemGroup>
+	  <None Remove="domains.json" />
+	</ItemGroup>
+
+	<ItemGroup>
+	  <Content Include="domains.json">
+	    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+	    <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
+	    <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
+	  </Content>
+	</ItemGroup>
+
 </Project>

+ 70 - 0
FastGithub.DomainResolve/IPAddressItem.cs

@@ -0,0 +1,70 @@
+using System;
+using System.Diagnostics;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Threading.Tasks;
+
+namespace FastGithub.DomainResolve
+{
+    /// <summary>
+    /// IP地址项
+    /// </summary>
+    [DebuggerDisplay("Address = {Address}, PingElapsed = {PingElapsed}")]
+    sealed class IPAddressItem : IEquatable<IPAddressItem>
+    {
+        private readonly Ping ping = new();
+
+        /// <summary>
+        /// 地址
+        /// </summary>
+        public IPAddress Address { get; }
+
+        /// <summary>
+        /// Ping耗时
+        /// </summary>
+        public TimeSpan PingElapsed { get; private set; } = TimeSpan.MaxValue;
+
+        /// <summary>
+        /// IP地址项
+        /// </summary>
+        /// <param name="address"></param>
+        public IPAddressItem(IPAddress address)
+        {
+            this.Address = address;
+        }
+
+        /// <summary>
+        /// 发起ping请求
+        /// </summary>
+        /// <returns></returns>
+        public async Task PingAsync()
+        {
+            try
+            {
+                var reply = await this.ping.SendPingAsync(this.Address);
+                this.PingElapsed = reply.Status == IPStatus.Success
+                    ? TimeSpan.FromMilliseconds(reply.RoundtripTime)
+                    : TimeSpan.MaxValue;
+            }
+            catch (Exception)
+            {
+                this.PingElapsed = TimeSpan.MaxValue;
+            }
+        }
+
+        public bool Equals(IPAddressItem? other)
+        {
+            return other != null && other.Address.Equals(this.Address);
+        }
+
+        public override bool Equals(object? obj)
+        {
+            return obj is IPAddressItem other && this.Equals(other);
+        }
+
+        public override int GetHashCode()
+        {
+            return this.Address.GetHashCode();
+        }
+    }
+}

+ 64 - 0
FastGithub.DomainResolve/IPAddressItemHashSet.cs

@@ -0,0 +1,64 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace FastGithub.DomainResolve
+{
+    /// <summary>
+    /// IPAddressItem集合
+    /// </summary>
+    sealed class IPAddressItemHashSet
+    {
+        private readonly object syncRoot = new();
+        private readonly HashSet<IPAddressItem> hashSet = new();
+
+        /// <summary>
+        /// 获取元素数量
+        /// </summary>
+        public int Count => this.hashSet.Count;
+
+        /// <summary>
+        /// 添加元素
+        /// </summary>
+        /// <param name="item"></param>
+        /// <returns></returns>
+        public bool Add(IPAddressItem item)
+        {
+            lock (this.syncRoot)
+            {
+                return this.hashSet.Add(item);
+            }
+        }
+
+        /// <summary>
+        /// 转换为数组
+        /// </summary>
+        /// <returns></returns>
+        public IPAddressItem[] ToArray()
+        {
+            lock (this.syncRoot)
+            {
+                return this.hashSet.ToArray();
+            }
+        }
+
+        /// <summary>
+        /// Ping所有IP
+        /// </summary>
+        /// <returns></returns>
+        public Task PingAllAsync()
+        {
+            var items = this.ToArray();
+            if (items.Length == 0)
+            {
+                return Task.CompletedTask;
+            }
+            if (items.Length == 1)
+            {
+                return items[0].PingAsync();
+            }
+            var tasks = items.Select(item => item.PingAsync());
+            return Task.WhenAll(tasks);
+        }
+    }
+}

+ 3 - 1
FastGithub.DomainResolve/ServiceCollectionExtensions.cs

@@ -18,8 +18,10 @@ namespace FastGithub
         {
             services.TryAddSingleton<DnsClient>();
             services.TryAddSingleton<DnscryptProxy>();
+            services.TryAddSingleton<DomainSpeedTester>();
             services.TryAddSingleton<IDomainResolver, DomainResolver>();
-            return services.AddHostedService<DnscryptProxyHostedService>();
+            services.AddHostedService<DomainResolveHostedService>();
+            return services;
         }
     }
 }

+ 22 - 0
FastGithub.DomainResolve/domains.json

@@ -0,0 +1,22 @@
+[
+  "github.com",
+  "v2ex.com",
+  "alive.github.com",
+  "api.github.com",
+  "collector.githubapp.com",
+  "github.githubassets.com",
+  "avatars.githubusercontent.com",
+  "camo.githubusercontent.com",
+  "github-releases.githubusercontent.com",
+  "raw.githubusercontent.com",
+  "www.gravatar.com",
+  "onedrive.live.com",
+  "cdn.v2ex.com",
+  "microsoft.github.io",
+  "fonts.geekzu.org",
+  "gapis.geekzu.org",
+  "i.stack.imgur.com",
+  "skyapi.onedrive.live.com",
+  "codeproject.freetls.fastly.net",
+  "codeproject.global.ssl.fastly.net"
+]