如何通过Netlify Edge函数在网络字体中添加热链接保护和deno
#javascript #网络开发人员 #教程 #netlify

因为制作质量字体的人们应该得到报酬

在我重新设计的个人网站sidney.me工作时,我选择使用付费字体Klim Type Foundry s The Future Mono 。这是我第一次为项目使用付费网络字体,因此我阅读(掠过)web font licence agreement。奇怪的是,它包括此条款(强调我的):

3D。 Web字体文件保护

您同意使用合理的措施来确保仅用于网站上的造型文本的过程。至少,通过说明而不是限制,合理的措施包括a。)防止未经许可的第三方访问,即 hotlinking 和b。)不允许直接下载该字体与您网站的造型文本无关。

用更简单的话来说,您必须确保部署Web字体文件,以便没有人可以将URL粘贴到浏览器中以下载它,并且其他网站可以通过链接到该URL来使用Web字体。<<<<<<<<<<<<<<<<<< /p>

我多年来一直在使用Netlify来托管我的网站,因此我寻找了一种本地进行操作的方法。不幸的是,我遇到了这个Netlify support forum thread,我们还没有解决这个特定问题的解决方案,但它可能会在将来涵盖。敬请期待!2020年3月。

答复确实有一些建议,例如:

  • 使用netlify函数(此用例的过度杀伤和潜在慢)检查http请求中的koude0 header
  • 使用一个更具性能的Cloudflare工人到do that check(但我不想注册另一项服务)或
  • 从S3存储桶或单独的Apache服务器(打破无服务器堆栈的全部点)并使用.htaccess

自从该线程的最后一个答复以来,Netlify已发布Edge Functions。他们使用Deno运行JavaScript或打字稿代码,这是旨在成功节点的热门JS运行时,与典型的无服务器功能运行的情况相比,CDN服务器更接近用户。换句话说:快速,快速,快速。

代码

在NetLify项目上设置边缘功能非常简单。首先创建一个netlify/edge-functions目录并在其中制作JS或TS文件。然后,导出一个默认函数以使NetLify运行。让我们称其为hotlink-protection.ts,然后返回“你好世界”作为基本回应:

// netlify/edge-functions/hotlink-protection.ts

export default () => new Response("Hello world");

然后,我们需要指定该功能应在哪些路由下运行。在这种情况下,我们可以将其限制为字体文件。我仅以browser support is excellent为woff2格式使用字体。在项目的根部,创建一个名为netlify.toml的文件,然后将路径和函数名称设置为与您选择的文件名相同。

# netlify.toml

[[edge_functions]]
    path = "/*.woff2"
    function = "hotlink-protection"

当我们在这里时,让我们为字体添加一个缓存标头。它们是不变的,静态的资产,这些资产不会改变:我们需要向浏览器指定。

# netlify.toml

[[headers]]
    for = "/*.woff2"
    [headers.values]
        Cache-Control = "public, max-age=31536000, immutable"

现在让我们开始工作。我们有效地创建了修改和返回响应字体文件本身或403禁止HTTP错误的中间件。 Edge函数将获得两个参数,分别是requestcontext。我们需要使用contextawait响应(字体数据),因此,让我们将其更改为async函数。这是打字稿中的定义:

import type { Context } from "https://edge.netlify.com";

export default async (
    request: Request,
    context: Context
): Promise<Response> => {
    // ...
};

然后,让我们从request获取Referer标头,并使用正则表达式来测试它来自我们的网站。如果没有Referer或没有匹配,请返回403。

import type { Context } from "https://edge.netlify.com";

export default async (
    request: Request,
    context: Context
): Promise<Response> => {
    const referer = request.headers.get("referer");

    const regex = /^https?:\/\/(.*\.)?sidney\.me(\/.*)?$/;

    if (!referer || !regex.test(referer)) {
        return new Response("Forbidden", { status: 403 });
    }

    // ...
};

那条正则看起来很粗糙,但其大多数特殊字符都用于逃避特殊字符/.,它们也是URL的一部分。它与sidney.me和任何子域和目录相匹配,因此在复制此片段时,请确保更改这些字符(不包括\.)。有关说明正则的说明,请在regex101.com上查看。右上方面板突出了每个角色的作用。

然后,要返回字体文件,我们可以使用await context.next()获取下一个HTTP响应。这是最终代码

// netlify/edge-functions/hotlink-protection.ts

import type { Context } from "https://edge.netlify.com";

export default async (
    request: Request,
    context: Context
): Promise<Response> => {
    const referer = request.headers.get("referer");

    const regex = /^https?:\/\/(.*\.)?sidney\.me(\/.*)?$/;

    if (!referer || !regex.test(referer)) {
        return new Response("Forbidden", { status: 403 });
    }

    const response = await context.next();
    return response;
};

现在,您可以进行更改并部署项目以照常网络化。部署后,导航到字体URL应显示“禁止”。

注意:如果您希望您的字体在 Branch部署中工作,则需要单独检查NetLify域名。就我而言,它的sidney-me.netlify.app和分支名称和--的前缀是:branch-name--sidney-me.netlify.app。这是我的言论:

    const prodRegex = /^https?:\/\/(.*\.)?sidney\.me(\/.*)?$/;
    const devRegex = /^https?:\/\/(.*\--)?sidney-me\.netlify\.app(\/.*)?$/;

    if (
        !referer ||
        !(prodRegex.test(referer) || devRegex.test(referer))
    ) {
        return new Response("Forbidden", { status: 403 });
    }

将您的字体文件子设置为额外的保护和性能提升

当然,这种保护本身并非防弹。有人可以轻松地欺骗Referer标头或使用DevTools下载字体文件。字体数据必须发送给用户以供其浏览器显示字体。一旦在用户的计算机上,他们总是有一种方法来提取字体文件。

减轻这种情况的一种方法是将字体文件征用,仅包括您在网站上使用的字符,不包括其他语言或写作系统的字符。此外,字体通常具有像alternate characters这样的OpenType features,主要是出于风格目的,我们可以关注Google Fonts’ example从我们使用的文件中删除它们。

对于我的网站,我使用了Fonttools的pyftsubset进行子集。请关注these instructions安装(请注意,您也需要安装Python)。这是一个例子:

pyftsubset the-future-mono-regular.woff2 \
--output-file="the-future-mono-regular--subset.woff2" \
--flavor=woff2 \
--unicodes="U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD"

此命令子集与拉丁字符集和一些标点符号符号,共385个字符。如subset docs中所述,它还消除了可自由的Opentype特征。就我而言,这使字体文件的大小减少了一半,从30.1 kb到14.7 kb。这可以加起来并提高您的灯塔性能得分。

然后指定CSS中的Unicode范围,以帮助浏览器在必要时使用适当的后备字体。

@font-face {
    font-family: "The Future Mono";
    src: url("the-future-mono-regular--subset.woff2") format("woff2");
    font-display: swap;
    font-weight: 400;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
        U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
        U+FEFF, U+FFFD;
}

提供另一个项目的字体

如果您将网站的代码作为开源代码发布,请确保 not 将您的字体文件包括在该存储库中。您不想成为Github成为web’s largest font piracy site的问题的一部分。

  1. 使用*.woff2将字体文件添加到.gitignore文件中。
  2. 如果您已经公开发布了仓库,请从历史记录中永久删除文件(instructions)。
  3. 然后,将您的字体文件部署在新的私有存储库中作为单独的NetLify项目。
  4. 您可以将文件保留在本地存储库中进行测试,并使您的CSS保持不变,指向同一域名的文件。然后使用rewrite指向其他项目的域名:

    # netlify.toml
    
    [[redirects]]
      from = "/fonts/the-future-mono-*"
      to = "https://your-project.netlify.app/the-future-mono/the-future-mono-:splat"
      status = 200
      force = true
    

重要:您必须在两个项目的匹配URL上具有热链接保护边缘功能。如果有人去您的主要URL获取字体,则私人项目的边缘功能将获得正确的Referer标题,并让任何人下载或热链接。

您可以将相同的功能复制到主存储库中,并将其设置为netlify.toml中的适当路径:

# netlify.toml

[[edge_functions]]
  path = "/fonts/the-future-mono-*"
  function = "hotlink-protection"

现在,您拥有两全其美的最好:该字体可用于部署后的本地开发。

其他资源

  • 这是一些针对Web字体的其他保护措施的suggestions from TypeKit
  • 另外,您可以根据网站上使用的字符使用glyphhanger来子集Web字体。
  • 您可以看到哪些字符在Unicode范围内,并使用Unicode Range Interchange在范围内进行数学。
  • 您可以使用Wakamai Fondue中的字体文件中包含哪些OpenType功能和所有字符。
  • 如果您对Web字体性能感到好奇,Zach Leatherman在CSS-Tricks上的实施中写了一个excellent guide

当然,请查看我的个人网站sidney.me,让我知道您对Mastodon的想法。

Hi, I’m Sidney.