从零部署一个在线 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
2
3
4
5
6
#include <stdio.h>

int main() {
printf("Hello, World!\n");
return 0;
}

点击「编译运行」之后,前端把代码提交到 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
2
3
4
5
6
7
8
9
用户浏览器
↓ 写代码、点运行
前端页面
↓ POST /proxy
EdgeOne Pages Function
↓ 转发到 Judge0
Judge0 CE
↓ 返回编译运行结果
前端页面展示 stdout / stderr / compile_output

也就是说,浏览器本身并不编译 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
2
3
4
5
浏览器

EdgeOne /proxy

Judge0 CE

前端只请求自己站点下的 /proxy,这样浏览器看到的是同源请求。真正访问 Judge0 的动作交给 EdgeOne 边缘函数完成。

部署到 c.xingbox.me 后,前端实际请求的是:

1
https://c.xingbox.me/proxy?url=...

浏览器认为它还在访问 c.xingbox.me,跨域问题就被代理层处理掉了。


前端配置

页面里放了一个全局配置:

1
2
3
4
5
6
7
window.__APP_CONFIG__ = {
version: 'v2026.05.24-edgeone-proxy.1',
judge0ApiUrl: 'https://ce.judge0.com',
judge0ProxyUrl: '/proxy',
useProxy: true,
backendRunUrl: ''
};

这里的意思是:

  • 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
2
3
4
5
6
7
{
language_id: 50,
source_code: "base64 后的 C 代码",
stdin: "base64 后的标准输入",
cpu_time_limit: 5,
memory_limit: 128000
}

其中 language_id: 50 对应 C 语言 GCC。

返回结果里,stdoutstderrcompile_output 也会是 base64,所以前端拿到后还要解码。


状态处理

Judge0 会返回一个 status.id,前端根据这个值判断运行状态。

常见状态包括:

id含义
3运行成功
5超时
6编译错误
7运行时错误
13内存不足

这个状态很重要,因为不能只看 stdout。

有些程序没有输出,但运行成功;有些程序 stdout 为空,是因为编译失败;还有些程序 stderr 有内容,但状态才是最终判断依据。

所以展示结果时,最好同时处理:

  • compile_output
  • stdout
  • stderr
  • message
  • time
  • memory
  • status.id

这样用户看到的信息才完整。


EdgeOne 代理函数

EdgeOne Pages Functions 的核心逻辑很短:

1
2
3
4
5
6
7
8
9
10
11
const url = new URL(request.url);
const targetUrl = url.searchParams.get('url');
const target = new URL(targetUrl);

const response = await fetch(target.toString(), {
method: request.method,
headers: {
'Content-Type': 'application/json',
},
body: request.method === 'POST' ? await request.text() : undefined,
});

不过代理函数一定要加域名限制。

如果不限制,任何人都可以拿你的 /proxy 去请求任意网站,这就变成了开放代理,风险很高。

项目里限制了目标域名:

1
2
3
if (target.hostname !== 'ce.judge0.com' && !target.hostname.endsWith('.judge0.com')) {
return new Response('Forbidden domain', { status: 403 });
}

这样 /proxy 只允许转发到 Judge0。


部署前准备

部署前需要准备这些东西:

  • 一个 Gitee 仓库
  • 一个 EdgeOne 账号
  • 一个要绑定的域名,这里用的是 c.xingbox.me
  • 项目代码里已经包含 index.htmlapp.jsstyle.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
2
3
4
5
6
index.html
app.js
style.css
edge-functions/proxy.js
_routes.json
README.md

然后提交并推送:

1
2
3
git add .
git commit -m "deploy c online compiler"
git push

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
2
3
框架预设: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

大致流程是:

  1. 进入 EdgeOne Pages 项目设置
  2. 找到自定义域名
  3. 添加 c.xingbox.me
  4. 根据 EdgeOne 提示配置 DNS
  5. 等待证书签发和 DNS 生效

DNS 通常会要求添加一条 CNAME 记录,比如:

1
2
3
主机记录:c
记录类型:CNAME
记录值:EdgeOne 提供的目标地址

生效后访问:

1
https://c.xingbox.me

如果页面能打开,说明静态站点部署成功。

再访问:

1
https://c.xingbox.me/proxy

如果返回 Missing url parameter,说明函数也成功了。

最后点「编译运行」,能看到 Hello, World!,整个链路才算真正跑通。


第五步:验证运行功能

可以用这段代码测试:

1
2
3
4
5
6
7
8
#include <stdio.h>

int main() {
int a, b;
scanf("%d %d", &a, &b);
printf("%d\n", a + b);
return 0;
}

在 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
2
ce.judge0.com
*.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 进行许可。