从零部署一个在线 C 编译器:Gitee、Judge0 和 EdgeOne Pages
最近做了一个小工具:FanXing Online C。
它的目标很简单:打开网页,写一段 C 代码,点一下按钮,就能看到编译和运行结果。不需要本地装 GCC,也不需要配置 IDE,适合临时测试代码、写示例、给别人演示一段 C 程序。
项目本身不复杂,但里面有几个实际部署时很容易踩坑的点,比如浏览器跨域、公共编译 API 的调用格式、静态站点怎么接后端代理、Gitee 仓库怎么接 EdgeOne Pages。
现在已经部署到了:
1 | https://c.xingbox.me |
这篇文章就记录一下这个项目的实现过程、背后的原理,以及从 Gitee 部署到 EdgeOne Pages 的完整步骤。
先看成品
页面主要分成三块:
- 左侧是 C 代码编辑器
- 右上是运行输出
- 右下是标准输入 stdin
默认代码是最经典的:
1 |
|
点击「编译运行」之后,前端把代码提交到 Judge0,Judge0 编译运行后返回 stdout、stderr、compile_output、运行时间和内存占用。
如果代码有语法错误,就展示编译错误;如果运行成功,就展示程序输出。
线上地址:
1 | https://c.xingbox.me |
打开后可以直接运行默认的 Hello, World!,也可以自己写 C 代码,再配合 stdin 测试输入输出。
技术选型
这个项目刻意做得很轻:
| 部分 | 方案 |
|---|---|
| 页面 | 原生 HTML/CSS/JS |
| 编辑器 | CodeMirror 5 |
| 编译运行 | Judge0 CE |
| 代理 | EdgeOne Pages Functions |
| 部署 | Gitee + EdgeOne Pages |
没有用 Vue、React,也没有引入复杂构建流程。原因很直接:这个工具的交互不复杂,核心问题不是组件状态管理,而是如何稳定地把代码送到远端编译服务并拿回结果。
对于这种小工具,纯静态页面反而是最省心的。
整体原理
这个在线编译器的链路可以拆成四段:
1 | 用户浏览器 |
也就是说,浏览器本身并不编译 C 代码,真正的编译运行发生在 Judge0 的服务端。
前端要做的事情是:
- 提供代码编辑器
- 收集源代码和 stdin
- 把内容编码后提交给 Judge0
- 解析 Judge0 返回的结果
- 把编译错误、运行输出、资源占用展示出来
EdgeOne 要做的事情是:
- 托管静态页面
- 提供
/proxy代理函数 - 避免浏览器直接跨域请求 Judge0
- 把请求安全地转发给 Judge0
Judge0 要做的事情是:
- 接收代码
- 在沙箱里编译运行
- 返回 stdout、stderr、compile_output、状态码、时间和内存
这个分工很清晰,所以项目不需要自己维护一台编译服务器。
为什么不能直接请求 Judge0
Judge0 CE 提供了公开 API,理论上前端可以直接请求:
1 | https://ce.judge0.com/submissions |
但实际部署到浏览器环境时,会遇到跨域问题,或者某些请求头、预检请求不符合预期。
所以更稳的做法是加一层代理:
1 | 浏览器 |
前端只请求自己站点下的 /proxy,这样浏览器看到的是同源请求。真正访问 Judge0 的动作交给 EdgeOne 边缘函数完成。
部署到 c.xingbox.me 后,前端实际请求的是:
1 | https://c.xingbox.me/proxy?url=... |
浏览器认为它还在访问 c.xingbox.me,跨域问题就被代理层处理掉了。
前端配置
页面里放了一个全局配置:
1 | window.__APP_CONFIG__ = { |
这里的意思是:
judge0ApiUrl:真实的 Judge0 地址judge0ProxyUrl:本站代理地址useProxy:启用代理模式backendRunUrl:备用后端地址,目前留空
最终前端会拼出这样的请求:
1 | /proxy?url=https%3A%2F%2Fce.judge0.com%2Fsubmissions%3Fbase64_encoded%3Dtrue%26wait%3Dtrue |
也就是说,/proxy 只负责转发,真正的目标地址通过 url 参数传进去。
Judge0 的请求格式
Judge0 支持普通文本提交,也支持 base64 编码提交。
我这里选择了 base64:
1 | base64_encoded=true |
原因是 C 代码里经常会出现换行、中文、特殊字符。用 base64 可以减少编码问题,让传输更稳定。
提交时主要传这些字段:
1 | { |
其中 language_id: 50 对应 C 语言 GCC。
返回结果里,stdout、stderr、compile_output 也会是 base64,所以前端拿到后还要解码。
状态处理
Judge0 会返回一个 status.id,前端根据这个值判断运行状态。
常见状态包括:
| id | 含义 |
|---|---|
| 3 | 运行成功 |
| 5 | 超时 |
| 6 | 编译错误 |
| 7 | 运行时错误 |
| 13 | 内存不足 |
这个状态很重要,因为不能只看 stdout。
有些程序没有输出,但运行成功;有些程序 stdout 为空,是因为编译失败;还有些程序 stderr 有内容,但状态才是最终判断依据。
所以展示结果时,最好同时处理:
compile_outputstdoutstderrmessagetimememorystatus.id
这样用户看到的信息才完整。
EdgeOne 代理函数
EdgeOne Pages Functions 的核心逻辑很短:
1 | const url = new URL(request.url); |
不过代理函数一定要加域名限制。
如果不限制,任何人都可以拿你的 /proxy 去请求任意网站,这就变成了开放代理,风险很高。
项目里限制了目标域名:
1 | if (target.hostname !== 'ce.judge0.com' && !target.hostname.endsWith('.judge0.com')) { |
这样 /proxy 只允许转发到 Judge0。
部署前准备
部署前需要准备这些东西:
- 一个 Gitee 仓库
- 一个 EdgeOne 账号
- 一个要绑定的域名,这里用的是
c.xingbox.me - 项目代码里已经包含
index.html、app.js、style.css - 项目代码里已经包含 EdgeOne 函数文件
edge-functions/proxy.js
项目仓库是:
1 | https://gitee.com/knight3fax/c-online-compiler.git |
如果你用 SSH 管理代码,也可以使用:
1 | git@gitee.com:knight3fax/c-online-compiler.git |
第一步:把项目推到 Gitee
本地项目准备好后,确认仓库里至少有这些文件:
1 | index.html |
然后提交并推送:
1 | git add . |
EdgeOne Pages 后面会从 Gitee 拉取这份代码并自动部署。
第二步:创建 EdgeOne Pages 项目
这个项目是纯静态页面,所以部署很简单。
在 EdgeOne Pages 里新建项目,选择 Gitee 仓库:
1 | knight3fax/c-online-compiler |
如果控制台没有自动识别,也可以用 Git URL:
1 | https://gitee.com/knight3fax/c-online-compiler.git |
构建配置:
1 | 框架预设:Other / Static |
这里要注意:项目没有打包步骤,不需要 npm install,也不需要 npm run build。根目录本身就是静态站点的输出目录。
如果平台要求填输出目录,就填:
1 | / |
第三步:确认 EdgeOne 函数生效
项目里有这个文件:
1 | edge-functions/proxy.js |
它的作用是生成 /proxy 代理接口。
部署完成后,先不要急着测编译功能,先访问:
1 | https://c.xingbox.me/proxy |
如果看到:
1 | Missing url parameter |
说明 /proxy 函数已经生效。
如果这里是 404,说明 EdgeOne 没有识别到函数文件,优先检查函数目录、文件名和 Pages 构建日志。
第四步:绑定域名 c.xingbox.me
项目部署成功后,EdgeOne Pages 会先给一个默认访问地址。为了使用自己的域名,需要在 Pages 项目里绑定:
1 | c.xingbox.me |
大致流程是:
- 进入 EdgeOne Pages 项目设置
- 找到自定义域名
- 添加
c.xingbox.me - 根据 EdgeOne 提示配置 DNS
- 等待证书签发和 DNS 生效
DNS 通常会要求添加一条 CNAME 记录,比如:
1 | 主机记录:c |
生效后访问:
1 | https://c.xingbox.me |
如果页面能打开,说明静态站点部署成功。
再访问:
1 | https://c.xingbox.me/proxy |
如果返回 Missing url parameter,说明函数也成功了。
最后点「编译运行」,能看到 Hello, World!,整个链路才算真正跑通。
第五步:验证运行功能
可以用这段代码测试:
1 |
|
在 stdin 输入:
1 | 3 5 |
正常输出应该是:
1 | 8 |
这个测试比 Hello, World! 更完整,因为它同时验证了:
- 代码能提交
- stdin 能传到 Judge0
- 程序能读取输入
- stdout 能正确返回
- base64 编码和解码没有问题
常见问题排查
页面能打开,但点击运行失败
先访问:
1 | https://c.xingbox.me/proxy |
如果不是 Missing url parameter,说明代理函数没有正常工作。
如果代理正常,再打开浏览器开发者工具,看 Network 里的 /proxy?... 请求状态码。
/proxy 返回 403
说明代理函数认为目标域名不允许。
这个项目只允许转发到:
1 | ce.judge0.com |
如果你改了 Judge0 地址,需要同步修改代理函数里的域名白名单。
返回 429
这是 Judge0 公共服务限流。
解决办法有三个:
- 等一会儿再试
- 前端增加更长冷却时间
- 自己部署 Judge0 实例
页面样式或编辑器加载不出来
项目使用 cdnjs 加载 CodeMirror:
1 | https://cdnjs.cloudflare.com/ |
如果网络环境访问 cdnjs 不稳定,编辑器可能加载失败。更稳的方式是把 CodeMirror 资源下载到项目本地,由 EdgeOne 一起托管。
我觉得这个项目最关键的点
这个项目不是难在写页面,而是难在链路要闭合。
前端编辑器只是入口,真正要跑起来,需要同时满足:
- CodeMirror 正常加载
- 前端能正确 base64 编码代码
/proxy路由能被 EdgeOne 识别- 代理能带着 POST body 转发
- Judge0 返回的数据能被正确解码
- 错误状态能清楚展示给用户
任何一环断了,用户看到的都只是「运行失败」。
所以做这种小工具时,我更倾向于先把最短链路跑通:
1 | 默认代码 -> 点击运行 -> Judge0 返回 -> 页面显示 Hello, World! |
等这条链路稳定之后,再考虑美化、移动端适配、错误提示、请求冷却这些体验细节。
目前的限制
这个项目现在仍然是轻量版本,所以也有一些限制:
- 只支持 C 语言
- 使用 Judge0 CE 公共服务,可能会被限流
- 运行时间限制为 5 秒
- 代码长度限制为 32KB
- 依赖 cdnjs 加载 CodeMirror,网络差时可能加载慢
后续如果要继续扩展,可以考虑:
- 增加 C++、Python、Java 等语言
- 把 CodeMirror 资源本地化
- 给每次运行增加历史记录
- 支持保存代码片段
- 接入自己的 Judge0 实例,减少公共 API 限流影响
总结
这个在线 C 编译器本质上是一个很典型的「静态前端 + 边缘函数 + 第三方 API」项目。
它没有复杂架构,但很适合练习完整部署链路:前端页面、跨域代理、API 编码、错误处理、Gitee 仓库、EdgeOne Pages 自动部署。
对我来说,它最有价值的地方不是多高级,而是足够实用:打开就能写,点一下就能跑。
- 标题: 从零部署一个在线 C 编译器:Gitee、Judge0 和 EdgeOne Pages
- 作者: 番星
- 创建于 : 2026-05-24 22:00:00
- 更新于 : 2026-05-30 18:47:17
- 链接: https://xingbox.me/fanxing-online-c-edgeone-judge0/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。