用代码怎么做网站,网页微信客户端下载,wordpress暂停网站,青岛注册公司的步骤和流程我最近使用 .NET Core 2.2 造了个名为Link Forwarder #xff08;链接转发器#xff09;的 URL 转发服务#xff0c;并已开源。目前预览版已部署到我的子域go.edi.wang。本文将分享我如何构建这个项目#xff0c;以及我学到的东西。为了帮助大家了… 我最近使用 .NET Core 2.2 造了个名为Link Forwarder 链接转发器的 URL 转发服务并已开源。目前预览版已部署到我的子域go.edi.wang。本文将分享我如何构建这个项目以及我学到的东西。为了帮助大家了解系统并浏览代码请查看我的 GitHub 存储库https://github.com/EdiWang/LinkForwarder面向的问题互联网上的资源有时会更改其 URL。例如当我 10 年前创建网站时一个典型的博客文章 URL 就像https://myolddomain.net/viewarticle.aspx?id123。我朋友在其他网站的帖子上引用了这个URL或讲它发给其他人。几年后我拥有了一个新域名并推出了一个新的博客系统完全改变了该文章的URL例如https://edi.wang/post/2009/1/1/an-old-article这使得任何旧的URL引用都失效。还好我的博客不盈利所以没太大关系。但是这个问题可能发生在企业的产品上。尤其是对于客户端系统和应用程序。比如将产品的支持链接写入安装在客户端的产品中结果有一天该链接更改了那么您就必须将所有客户端推送更新。为了解决这个问题我想以微软为榜样。微软创建了go.microsoft.com它使用不会更改的静态 ID以重定向到可能随时间变化的实际 URL。例如https://go.microsoft.com/fwlink/?linkid2049807 指向的是基于Chromium 的 Edge 浏览器的帮助文档该文档目前 URL 是 https://microsoftedgesupport.microsoft.com/hc/en-us 。如果文档的 URL 随时间而变化Edge 浏览器不必更改其内置帮助链接。微软只需要更新其数据库以更改链接 ID 2049807 的目标 URL。这种go.microsoft.com服务在微软产品中随处可见。这是链接转发器的基本思想。基本流程管理员为有效的 URL (例如https://www.some-website.com/1234/abcd/1.html) 创建Token URL(例如https://go.edi.wang/fw/e66fad1e)。然后用户可以使用生成的Token URL 重定向到原始 URL。每次成功重定向都将偷偷记录用户的浏览器 UA 和 IP 地址以便管理员可以查看报表并暗中观察一切得加个隐私协议。报表页面创建/编辑链接分享链接并非短链接服务链接转发器非常像但并不是短链接。关键差异在于短链接的目标是创建尽可能短的 URL通常部署到非常短的域名。链接转发器并不关心是否将其部署到长域名。大多数短链接服务不允许在创建链接后再修改。但是链接转发器的目标是面向更改。并不简单链接转发器不只是将Token映射到 URL。需要考虑以下问题。它需要足够快并能处理一定量的流量我当前的设计会缓存有效的 URL 重定向因此对于对同一令牌的请求系统不会每次都查询数据库。如何处理无效的令牌或有效但不存在的 URL?对于无效令牌停止请求。对于该有效的令牌但它指向不存在的 URL(数据库中没有记录)将用户重定向到预先设置的默认 URL。系统需要保护用户免受潜在有害链接的侵害例如链接转发器的数据库遭到破坏并且 URL 指向https://127.0.0.1/some-virus可以触发一个事先安装在本地的病毒。用户就可能会受到攻击。其他 URL (如/abc、123) 也被视为无效 URL不会执行重定向。对于可能包含恶意代码的互联网 URL目前不在设计范围中。但是也许将来我们可以集成第三方服务来识别链接。系统需要自我保护指向系统本身的链接可能会导致重定向死循环并把服务器爆上天。例如https://go.edi.wang/fw/a 指向 https://go.edi.wang/fw/b https://go.edi.wang/fw/b 又指向 https://go.edi.wang/fw/a 如果将链接转发器或其他类似的系统部署到另一个域也会发生类似的情况。甚至可以有多个节点参与在循环中尽管现代浏览器会停止这种重定向循环但攻击者可以通过不使用现代浏览器或根本不使用浏览器来绕过此限制。对于指向服务器域本身的链接我们可以轻松地识别和阻止它。但对于有多放参与的重定向环我找不到识别和阻止请求的可靠方法。因此我只能绕弯解决将特定时间段内同一 IP 地址的同一令牌的请求数做限制本文稍后将对此进行说明。重定向流程下图说明了URL重定向流程。手机上看不清可以稍后查看原文数据库设计我们只需要两张表就能进行重定向和跟踪用户事件。我选择的数据库引擎是用于开发的 LocalDB 和用于生产的 Microsoft Azure SQL Database。SQL脚本IF NOT EXISTS(SELECT TOP 1 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME NLink)CREATE TABLE [Link]([Id] [int] IDENTITY(1,1) PRIMARY KEY NOT NULL,[OriginUrl] [nvarchar](256) NULL,[FwToken] [varchar](32) NULL,[Note] [nvarchar](max) NULL,[IsEnabled] [bit] NOT NULL,[UpdateTimeUtc] [datetime] NOT NULL)IF NOT EXISTS(SELECT TOP 1 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME NLinkTracking)CREATE TABLE [LinkTracking]([Id] UNIQUEIDENTIFIER PRIMARY KEY NOT NULL,[LinkId] [int] NOT NULL,[UserAgent] [nvarchar](256) NULL,[IpAddress] [varchar](64) NULL,[RequestTimeUtc] [datetime] NOT NULL)IF NOT EXISTS(SELECT TOP 1 1 FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME NFK_LinkTracking_Link)ALTER TABLE [LinkTracking] WITH CHECK ADD CONSTRAINT [FK_LinkTracking_Link] FOREIGN KEY([LinkId])REFERENCES [Link] ([Id])ON UPDATE CASCADEON DELETE CASCADEALTER TABLE [LinkTracking] CHECK CONSTRAINT [FK_LinkTracking_Link]ASP.NET Core 应用程序设计为了避免篇幅又臭又长本文不列出代码的每处细节。完整参考请查看项目 GitHub 仓库https://github.com/EdiWang/LinkForwarderLinkForwarder.WebASP.NET Core MVC 应用程序作为入口点。它控制 URL 重定向、链接验证、本地帐户或 Azure AD 的身份验证、创建或编辑链接以及查看报告。LinkForwarder.Services定义对数据库的 CRUD 操作并通过 ILinkForwarderService 接口和实现 LinkForwarderService 获取报告数据。稍后解释的 ITokenGenerator 也在此项目中。LinkForwarder.Setup用于运行 SQL 脚本以为新服务器设置数据库。这仅在系统的第一次运行中使用。关键点Token生成/fw后面的参数是一个 Token。它用于在数据库中查找源 URL。我不使用 Link.Id 的原因是当执行数据库迁移或从多个服务器合并数据库时Id 可能会更改。但Token将保持不变。系统使用 ITokenGenerator 接口生成Token。public interface ITokenGenerator{ string GenerateToken(); bool TryParseToken(string input, out string token);}GenerateToken() 用于在提交新 URL 时创建新Token。TryParseToken() 用于验证客户端请求的Token格式。目前ITokenGenerator 接口的唯一实现是ShortGuidTokenGenerator。它将以 GUID 的前 8 个字符作为Token。public class ShortGuidTokenGenerator : ITokenGenerator{ private const int Length 8; public string GenerateToken() { return Guid.NewGuid().ToString().Substring(0, Length).ToLower(); } public bool TryParseToken(string input, out string token) { token null; if (input.Length ! Length) { return false; } token input; return true; }}注意在此示例中TryParseToken() 并不总是可靠的因为无法判断 8 个字符的字符串是否属于 GUID。您当然可以根据自己的规则创建另一个Token生成器这些规则可以进行准确的Token验证。创建新链接首先我们需要防止为已经存在的 URL 创建新Token。对于现有 URL我们可以查找旧记录并返回旧Token而不是生成新Token。在此之前我们还需要再次验证现有URL的Token以确保数据良好。例如黑客可以将数据库中的Token更改为某个恶意字符串我不希望它最终追加到 URL 上。所以TryParseToken() 必须比我目前的设计更可靠。其次我们需要防止生成已存在的令牌。完整 GUID 是可靠的但部分 GUID 不是。基于这两个因素创建新链接的代码将是const string sqlLinkExist SELECT TOP 1 FwToken FROM Link l WHERE l.OriginUrl originUrl;var tempToken await conn.ExecuteScalarAsyncstring(sqlLinkExist, new { originUrl });if (null ! tempToken){ if (_tokenGenerator.TryParseToken(tempToken, out var tk)) { _logger.LogInformation($Link already exists for token {tk}); return new SuccessResponsestring(tk); } string message $Invalid token {tempToken} found for existing url {originUrl}; _logger.LogError(message);}const string sqlTokenExist SELECT TOP 1 1 FROM Link l WHERE l.FwToken token;string token;do{ token _tokenGenerator.GenerateToken();} while (await conn.ExecuteScalarAsyncint(sqlTokenExist, new { token }) 1);_logger.LogInformation($Generated Token {token} for url {originUrl});var link new Link{ FwToken token, IsEnabled isEnabled, Note note, OriginUrl originUrl, UpdateTimeUtc DateTime.UtcNow};const string sqlInsertLk INSERT INTO Link (OriginUrl, FwToken, Note, IsEnabled, UpdateTimeUtc) VALUES (OriginUrl, FwToken, Note, IsEnabled, UpdateTimeUtc);await conn.ExecuteAsync(sqlInsertLk, link);return new SuccessResponsestring(link.FwToken);验证重定向 URL系统使用 ILinkVerifier 接口在将其发送到链接到客户端之前验证 URL。有 3 种无效状态无效格式: 例如865c8gyiB本地 URL: 例如/some-path自引用 URL: 例如https://go.edi.wang/some-pathpublic enum LinkVerifyResult{ Valid, InvalidFormat, InvalidLocal, InvalidSelfReference}public interface ILinkVerifier{ LinkVerifyResult Verify(string url, IUrlHelper urlHelper, HttpRequest currentRequest);}我们可以利用ASP.NET MVC 的 IUrlHelper 接口执行前两个无效情况的验证。public LinkVerifyResult Verify(string url, IUrlHelper urlHelper, HttpRequest currentRequest){ if (!url.IsValidUrl()) { return LinkVerifyResult.InvalidFormat; } if (urlHelper.IsLocalUrl(url)) { return LinkVerifyResult.InvalidLocal; } if (Uri.TryCreate(url, UriKind.Absolute, out var testUri)) { if (string.Compare(testUri.Authority, currentRequest.Host.ToString(), StringComparison.OrdinalIgnoreCase) 0 string.Compare(testUri.Scheme, currentRequest.Scheme, StringComparison.OrdinalIgnoreCase) 0 testUri.AbsolutePath ! /) { return LinkVerifyResult.InvalidSelfReference; } } return LinkVerifyResult.Valid;}要检查 URL 是否采用有效格式public enum UrlScheme{ Http, Https, All}public static bool IsValidUrl(this string url, UrlScheme urlScheme UrlScheme.All){ bool isValidUrl Uri.TryCreate(url, UriKind.Absolute, out var uriResult); if (!isValidUrl) { return false; } switch (urlScheme) { case UrlScheme.All: isValidUrl uriResult.Scheme Uri.UriSchemeHttps || uriResult.Scheme Uri.UriSchemeHttp; break; case UrlScheme.Https: isValidUrl uriResult.Scheme Uri.UriSchemeHttps; break; case UrlScheme.Http: isValidUrl uriResult.Scheme Uri.UriSchemeHttp; break; } return isValidUrl;}IP 请求速率限制对于单个 IP重定向入口 (/fw/{token} ) 在一分钟内最多包含 30 个请求。[Route(/fw/{token})]public async TaskIActionResult Forward(string token)appsettings.json中的配置控制 IP 限制规则IpRateLimiting: { EnableEndpointRateLimiting: true, StackBlockedRequests: false, RealIpHeader: X-Real-IP, ClientIdHeader: X-ClientId, HttpStatusCode: 429, GeneralRules: [ { Endpoint: *:/fw/*, Period: 1m, Limit: 30 } ]}有关如何进行 IP 速率限制的更完整介绍请查看我之前的博客文章《IP Rate Limit for ASP.NET Core》 https://edi.wang/post/2019/6/16/ip-rate-limit-for-aspnet-core从User Agent里暗中观察典型的 User Agent 字符串如下Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.12 Safari/537.36 Edg/76.0.182.6为了最方便地从中获取信息我使用一个名为 UAParser 的库。有了轮子就别自己造.NET程序员不需要福报var uaParser Parser.GetDefault();string GetClientTypeName(string userAgent){ ClientInfo c uaParser.Parse(userAgent); return ${c.OS.Family}-{c.UA.Family};}此代码允许我按 操作系统-浏览器 对数据进行分组。例如Windows 7 Chrome 60 的用户和 Windows 10 Chrome 62 的用户都将分组为 Windows-Chrome。因此最终的饼图不会显示太多碎片序列。var q from d in userAgentCounts group d by GetClientTypeName(d.UserAgent) into g select new ClientTypeCount { ClientTypeName g.Key, Count g.Sum(gp gp.RequestCount) };还没完事链接转发器项目处于早期阶段。我能想到很多改进和新功能。例如为第三方提供 REST API、为管理链接添加Tag、甚至在ASP.NET Core 3.0 发布后使用 Blazor。技术上也存在可以优化的地方比如是否需要引入HASH查找、LinkTracking表到底用不用GUID主键、索引怎么加等等类似这些需要经过一段时间的线上实践才能做决定。这是一个开源项目所以我欢迎大家一起帮它变得更牛逼