从屏幕上的 PDF 倒着追回去:Overleaf 的 3 个服务如何跑通一条 LaTeX 编译流水线
【免费下载链接】overleafA web-based collaborative LaTeX editor项目地址: https://gitcode.com/GitHub_Trending/ov/overleaf
打开 Overleaf,点击编译,几秒后左侧预览栏就躺着一份 PDF。这条"LaTeX 编译 → PDF 预览"的链路横跨三个服务,今天我们不按"请求发出去之后发生了什么"的常规顺序讲,而是反着来:从你屏幕上的那份 PDF 出发,一路往回追到编译内核,看看每个环节到底把什么交给了下一个环节。
第一站:PDF 预览层,浏览器里的"最后一公里"
先看终点。前端预览组件在 services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.tsx,它基于 pdfjs 渲染,并且用usePersistedState把每个项目的缩放比例存在本地——你上次放大到多少,下次进来还是多少。
这里有个容易被忽略的细节:浏览器并不是去"请求一个接口拿 PDF",而是直接从一个独立的下载域名拉取文件。该域名由后端配置pdfDownloadDomain下发(对应环境变量COMPILES_USER_CONTENT_DOMAIN),定义在 services/web/config/settings.defaults.js。静态产物走独立域名,编辑请求走 API,这条分流在架构上很典型。
那么预览层拿到的 PDF,是谁生成的?
第二站:CLSI 的输出端口,编译结果的"出货口"
往下追,就要认识这条链路的发动机:CLSI(Common LaTeX Service Interface),一个把命令行 LaTeX 工具包装成 REST API 的服务。它默认监听 3 个端口:
| 端口 | 用途 |
|---|---|
| TCP/3013 | RESTful 接口,编译请求与输出文件都从这里进出 |
| TCP/3048 | 向负载均衡器上报负载信息 |
| TCP/3049 | 服务控制接口 |
端口定义见 services/clsi/config/settings.defaults.cjs,完整说明见 services/clsi/README.md。
编译完成后,CLSI 返回的响应长这样(节选):
{ "compile": { "status": "success", "outputFiles": [ { "type": "pdf", "url": "http://localhost:3013/project/<id>/output/output.pdf" }, { "type": "log", "url": "http://localhost:3013/project/<id>/output/output.log" } ] } }也就是说,预览层最终加载的 URL,正是 CLSI 的output目录。再追问一层:这个output.pdf是怎么产生的?
第三站:编译内核,沙箱容器里的 TeX Live
答案在请求解析器 services/clsi/app/js/RequestParser.js 里。CLSI 接受四种引擎:pdflatex、latex、xelatex、lualatex,缺省为pdflatex;请求体里还带draft(草稿模式,跳过部分环节换速度)、stopOnFirstError等开关。
真正执行编译的环节依赖一个前置概念:TeX Live 是一套完整的 LaTeX 发行版。CLSI 自己并不内置它——当环境变量SANDBOXED_COMPILES=true时,CLSI 会为每次编译拉起一个"兄弟容器",用TEXLIVE_IMAGE指定的镜像运行引擎,编译完即弃。这种沙箱编译把任意 LaTeX 宏包带来的进程风险关进了容器里。
几个直接影响编译行为的关键参数:
| 参数 | 作用 | 默认值 |
|---|---|---|
COMPILE_SIZE_LIMIT | 请求体(即源文件包)大小上限 | 7mb |
PROCESS_LIFE_SPAN_LIMIT_MS | CLSI 进程生命周期上限 | 2 天(到期自动换进程,防资源泄漏) |
timeout(请求内) | 单次编译超时,秒 | 600(超过会被钳制到 600) |
TEXLIVE_IMAGE | 沙箱容器使用的 TeX Live 镜像 | quay.io/sharelatex/texlive-full:2017.1 |
FILESTORE_HOST | 源文件下载来源 | 127.0.0.1:3009 |
这里顺带回答两个高频痛点:编译超时不是改"60 秒"那么简单——单次编译上限 600 秒是在RequestParser里硬钳制的,真正的长期资源安全阀是processLifespanLimitMs;想换引擎输出样式,就在请求的options.compiler里写xelatex或lualatex,请求示例:
{ "compile": { "options": { "compiler": "xelatex", "timeout": 600, "draft": false }, "rootResourcePath": "main.tex", "resources": [ { "path": "main.tex", "url": "http://filestore:3009/<blob>" } ] } }注意resources里的文件只带 URL 不带内容——源码本身存在另一个服务里。
终点站:Filestore,源码与产物的"仓库管理员"
链条的最上游是 services/filestore/:一个只管文件进出的极简服务(默认端口 3009)。它不编译任何东西,只负责把项目里每个.tex和素材存好、按需吐出 URL。CLSI 收到编译请求后,按resources里的 URL 把文件拉进沙箱容器,编译完再把 PDF 挂回 3013 端口的输出目录——Filestore 管"原料进",CLSI 管"成品出",前端只负责把成品渲染出来。
一句话复盘:Overleaf 的 PDF 处理就是一条三级流水线——Filestore 供料、CLSI 在沙箱 TeX Live 容器里编译、pdfjs 前端渲染,三者各守一个端口、各干一段活。
延伸阅读:服务级细节可对照 services/clsi/README.md 与 README.md。
如果你正自部署 Overleaf,欢迎在评论区聊聊你调TEXLIVE_IMAGE时踩过的坑。
【免费下载链接】overleafA web-based collaborative LaTeX editor项目地址: https://gitcode.com/GitHub_Trending/ov/overleaf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考