Acme公司是关于数字形状的。形状与几何学相互关联,我们需要深入到数学符号的武库中,以更好地表达和理解形状。LaTeX是科学论文中最流行的数学符号表示方式。在写这篇文章时,Hugo并不支持渲染LaTeX。
一个名为MathJax(www.mathjax.org/)的JavaScrip…
- MathJax的体积相当大。虽然它的代码分割并试图获得最小的有效载荷来渲染所需的内容,但它仍然是一个额外的JavaScript,需要在每个客户的机器上下载和运行。
- MathJax并没有给我们的网站增加任何互动性。输出是一个静态图像,在浏览器中做这个没有任何优势。在服务器上渲染数学比添加额外的跳转来下载这个库,然后执行计算并更新图像更有性能和速度。
- 客户端上的MathJax对缓存并不友好。因为它需要按需完成所有的工作,所以我们不能预先为我们的数学符号生成图像并保存。
如果我们能将数学渲染转移到服务器上,并在页面加载甚至网站重建时对结果进行缓存,这将是一种卓越的用户体验。由于Hugo没有直接与MathJax对接,我们需要通过使用外部API来扩展Hugo,以完成这一任务。首先,我们创建一个云端函数并托管它。这个函数接受一个LaTeX表达式并返回相应的SVG图片。接下来,我们在网站编译过程中从Hugo中调用它,并将搜索结果放到我们的网站中。
子进程与API
理论上说,以子进程的形式运行与项目相关的额外代码是比较容易的,这也是各地与其他工具接口的首选机制。Hugo通过命令行调用AsciiDoc等外部辅助工具以及PostCSS等工具,但这一功能对于一般使用来说是不可用的。子进程有一个讨厌的习惯,那就是获得启动它们的父进程的全部凭证和控制权。这意味着任何恶意软件都可以很容易地隐藏在一个模块或一些样本源代码中,如果通过子进程运行,可以对系统造成巨大的破坏。
通过HTTP访问的API,由于运行在不同的机器上,所以被置于沙盒之中。这使得Hugo的生态系统更加安全。尽管你需要小心地运行npm install
,以确保你不会遇到恶意软件,但运行Hugo命令不太可能对你的机器造成任何损害。Hugo不能写到public
目录之外,这使得一切都很安全。作为奖励,创建API可以促进多个系统的重复使用,因为这些API形成了松散耦合的微服务,不仅可以在编译时使用,而且可以在运行时使用。
编写渲染LaTeX的代码
因为MathJax是用JavaScript写的,所以我们用node.js来与它对接。我们首先安装node.js版本的MathJax作为我们网站的一个依赖项。这可以通过在package.hugo.json
中添加mathjax
作为依赖项来完成。请注意,我们使用dependency
,而不是package.hugo.json
中的devDependency
属性,因为实时生产代码需要该依赖性。在本文中,我们使用3.1.2版本的MathJax。
// package.hugo.json
{
"dependencies": {
"mathjax": "3.1.2"
}
}
接下来我们需要通过hugo重新生成package.json,然后将其作为一个依赖项安装:
hugo mod npm pack
npm install
这样就把MathJax下载下来,作为我们网站的一个依赖项。接下来我们创建一个tex2svg文件夹,其中包含将LaTeX转换成SVG的代码。这包括初始化MathJax,将LaTeX字符串以及可用于执行SVG转换的参数作为输入,然后将输出作为字符串返回。我们将API代码保存在一个名为api
的文件夹中,该文件夹中有一个名为tex2svg的子文件夹,用于公开这个函数。在这个文件夹中,我们有一个叫做index.js的文件,将其作为一个云函数公开。
我们为这个方法使用AWS Lambda和Netlify函数所使用的exports.handler = async function(event, context){}
格式。这个函数需要两个参数,事件和上下文。event
是一个包含以下属性的对象:
- path。请求的路径(例如:/latex2svg)
- httpMethod。传入请求的方法名称(GET、POST、PUT等)
- headers。收到的请求标题(例如:{'Content-Type': 'application/json'} )
- queryStringParameters: 查询字符串参数(例如:{tex: '\frac{1}{2}'})。
- body:请求的有效载荷的JSON字符串。(在GET请求中为空)
- isBase64Encoded:一个布尔标志,表示适用的请求有效载荷是否以Base64格式进行编码。
这个脚本的代码在本文的资源(https://github.com/hugoinaction/ hugoinaction/tree/ch11-resources/2)中共享。
清单1.将LaTeX转换为SVG的云函数的源代码:
// api/latex2svg.js
const MathjaxModule = require("mathjax");
❶ 导入 MathJax 依赖关系
❷ 确保 tex 参数是可用的。
❸ 仅在需要时初始化 MathJax。它接受 LaTeX 的输入并输出 SVG
❹ 将查询字符串的所有参数传给 MathJax
❺将结果以JSON格式返回200。
我们特意输出JSON字符串,而不是原始的SVG,以便通过GetJSON在Hugo中获得这个JSON对象,并在需要时有能力对其进行后期处理。如果需要,我们可以在响应的JSON中向Hugo传递额外的信息。
这段代码现在可以部署到FAAS解决方案中,如AWS Lambda或Netlify Functions,以便从任何地方使用。对于PAAS解决方案,我们需要多做一点工作。
添加一个HTTP服务器来调用这个函数
虽然我们有将LaTeX转换为SVG的代码,但我们还没有在本地测试。FAAS供应商提供了Netlify Dev、AWS SAM或Firebase函数模拟器等工具来在本地运行云函数,以验证和单元测试。在这篇文章中,我们没有使用专门的工具,而是写了一些启动器代码来对接这个函数。这段代码还允许我们与平台服务解决方案(在我们的例子中是Heroku)对接,它不需要一个函数,而是一个完整的node.js程序来运行。
我们在项目的根部创建一个新的文件api.js(在API文件夹中创建它,Netlify会把它当作一个函数),它有一个简单的基于node.js的HTTP服务器,响应HTTP请求并调用这个方法。(https://github.com/hugoinaction/hugoinaction/tree/ch11-resources/3)。
清单2.创建一个基于node.js的HTTP服务器,可以将请求路由到正确的函数,以便在PAAS解决方案中处理
const http = require('http');
const querystring = require('querystring');
const latex2svg = require('./api/latex2svg');
const port = process.env.PORT || 3000;
❶ 从环境变量中询问端口或默认为 3000。
❷ 设置一个 HTTP 服务器。
❷ 创建一个与 AWS Lambda 兼容的请求对象。
❹ 创建一个默认的响应。
❺ 创建一个轻量级的路由器
❻ 优雅地处理异常
❼ 将响应发回给客户端
我们可以通过在项目的根部调用node api
来运行这段代码。我们可以导航到http://localhost:3000/latex2svg?tex=%5Cfrac%7Ba%7D%7Bb%7D,得到`\frac{a}{b}` LaTeX字符串的内联版本的JSON输出。我们可以追加&display=true
,以获得显示版本。
在这一点上,我们应该更新package.json的 "main "条目,使其指向api.js
,以便在JavaScript生态系统中作为一个有效项目运行。我们还添加了一个启动脚本,当我们编写npm start
,启动我们的API服务器。我们需要通过同样的途径来更新package.hugo.json
,并运行hugo mod npm pack
。
// package.hugo.json
{
"main": "api.js",
"scripts": {
"start": "node api.js"
}
}
图1.LaTeX到SVG转换API的JSON响应。
添加一些安全性以防止未经授权的访问
如果我们通过启动器脚本或直接发布我们的功能,我们就会通过打开一个未经认证的端点来增加风险,这个端点可以被整个互联网访问。如果它被其他人使用而不向我们付费,这可能会产生巨大的成本。虽然我们不能在不添加认证系统或防火墙的情况下阻止我们的端点,但我们可以使其对任何不拥有密码的人无用。一个轻量级的安全解决方案可以通过在构建系统和API供应商中加入一个内置的密码,并在这两个地方通过环境变量来暴露它。因为我们的密码没有进入客户端,所以它是安全的,只要我们使用一个好的密码,并且我们的服务提供商是安全的,普通的密码认证机制是有效的。
在latex2svg.js中,在检查tex
查询参数之前,我们应该检查password
查询参数,如果没有提供,则返回未授权。
清单3.在我们的API中添加一个密码,以防止未经授权的访问
// api/latex2svg.js
...
async handler(event, context) {
if (!event.queryStringParameters ||
!process.env.LATEX2SVG_PASSWORD ||
❶ 不允许空白的 LATEX2SVG_PASSWORD
❷ 如果密码是错误的,我们使用 HTTP 401。如果密码正确,而用户仍然没有访问权,HTTP 403 是正确的错误代码。
对于本地测试,我们可以通过我们系统上的环境变量暴露LATEX2SVG_PASSWORD,因为我们配置云提供商将此传递给Hugo以及我们的函数。
部署Netlify函数
因为我们已经用Netlify理解的方式构建了这个函数,所以将其部署到Netlify并不需要很多工作。我们需要做的第一步是告诉Netlify函数文件夹的位置。要为Netlify的函数设置一个文件夹,请进入网站设置>函数>部署设置,然后点击编辑设置。
图2.Netlify功能的设置用于指定网站源代码所在的文件夹位置。
接下来指定api
,然后点击保存。
图3.在部署设置中指定Netlify函数的目录
我们还需要在构建环境中添加LATEX2SVG_PASSWORD变量。步骤是进入网站设置>构建和部署>环境>环境变量。点击编辑变量>新建变量。为LATEX2SVG_PASSWORD添加一个复杂的密码,然后点击保存。这个密码只需要在部署后测试Netlify的功能。你不需要记住这个密码。
图4.存储密码以限制对我们的函数的未授权访问。环境变量是防止密码进入我们的代码库的一个好方法。
接下来我们可以把我们的代码推送到Netlify,以尝试Netlify的功能。代码上线后,我们可以调用https://<endpoint>/.netlify/functions/latex2svg?tex=%5Cfrac%7Ba%7D%7Bb%7D&password=<password>
,得到与之前在本地运行时一样的响应。
我们还可以在Netlify网站的函数标签中看到我们的函数,在那里我们可以获得调试日志,以弄清每次调用时发生了什么。任何被抛出的错误也会被报告。我们可以把console.log
语句放在我们的JavaScript代码中,并在这个摘要页中看到我们所做的所有日志。
图5.访问Netlify函数的日志。Netlify的函数标签提供了对Netlify函数的访问,它可以用来查看我们网站中所有活跃的函数,以及与之相关的错误调试。
图6.在Netlify中每个函数都有详细的日志。
代码检查点。活跃在ch11-1.hugoinaction.com。源代码在github.com/hugoinactio… |
请继续关注第二部分,我们将部署到Heroku。