我给工具站加了一个在线代码运行器,结果先被 Docker 沙盒教育了一遍
最近给自己的工具站加了一个在线代码运行功能。地址 https://web.fushengtool.com/dev/code_runner最开始只是想补一个小能力用户在页面里输入一段代码选择语言点击运行然后页面返回执行结果。我原本以为这只是一个很简单的后端接口。真正做起来以后才发现这个功能和普通的文本处理工具不太一样。像 JSON 格式化、时间戳转换、Base64 编解码这类工具大部分逻辑都可以在浏览器里完成。用户输入文本前端处理后直接展示结果即可。但在线代码运行器不一样。它需要准备运行环境还要处理不同语言的执行方式、运行超时、错误输出、结果截断等问题。这篇文章记录一下我这次实现 Code Runner 的过程。一、为什么想做这个功能我平时写代码时经常会遇到一些很小的验证场景。比如临时写几行 Python验证一个字符串处理结果看一段 JavaScript 示例想快速跑一下输出写正则或时间处理逻辑想确认边界情况写一个小算法不想专门打开 IDE看别人给的示例代码想马上试一下。这些需求都不大但出现频率不低。如果每次都打开编辑器、新建文件、配置环境会有点麻烦。所以我就想在工具站里加一个轻量的在线代码运行功能。第一版先支持 Python后面再逐步扩展到 JavaScript、Go、Java、C、C 等语言。二、整体设计整体流程大概分成三层。前端页面 ↓ 后端接口 ↓ 代码运行模块前端页面负责选择编程语言编辑代码点击运行展示运行结果展示错误信息和耗时。后端接口负责校验参数判断语言是否支持调用对应语言的运行逻辑返回统一格式的结果。代码运行模块负责准备代码文件执行对应命令收集标准输出收集错误输出控制运行时间处理异常情况。拆成这几层以后前端和后端都比较清楚。前端不用关心每种语言怎么运行后端也可以把不同语言的差异收敛到配置里。三、先从 Python 开始Python 是最适合做第一版的语言。它不需要编译写入文件后直接执行即可。比如用户输入print(hello world)后端只需要把代码保存成临时文件然后执行python main.py再把输出结果返回给前端。第一版做完 Python 后整个流程基本就跑通了输入代码 → 提交接口 → 执行代码 → 获取输出 → 返回页面这个阶段主要解决的是接口结构和返回格式。我最后返回的数据大概包括是否成功标准输出错误输出退出码是否超时运行耗时。这样前端展示时会比较灵活。四、扩展到 JavaScriptJavaScript 的处理方式和 Python 类似。用户代码保存成文件后使用 Node.js 执行nodemain.js这一类解释型语言的接入成本相对较低。主要要处理的是运行环境是否存在错误输出如何展示执行时间如何限制输出太长时如何截断。做到这里时我发现如果后面还要继续加语言不能每种语言都写一大段独立逻辑。否则代码会越来越散。所以我开始把语言配置抽象出来。五、把语言差异整理成配置不同语言真正不同的地方主要是这些代码文件名是否需要编译编译命令运行命令默认超时时间输出长度限制。所以可以把每种语言整理成类似这样的配置语言Python 文件名main.py 运行命令python main.py 是否需要编译否语言JavaScript 文件名main.js 运行命令node main.js 是否需要编译否语言C 文件名main.c 编译命令gcc main.c -o main 运行命令./main 是否需要编译是这样后面新增语言时思路会清楚很多。不是在业务逻辑里不断加判断而是新增一份语言配置。六、编译型语言的处理支持 C、C、Java、Go 之后流程会比 Python 和 JavaScript 多一步。比如 C 语言gcc main.c-omain ./mainJavajavac Main.javajavaMainGogo run main.go这些语言需要注意几个问题。第一编译错误和运行错误要分开。编译阶段失败就不应该继续执行运行命令。比如 C 语言少了分号编译阶段就会报错这时候直接把编译错误返回给用户即可。第二不同语言的错误格式不一样。Python 的 Traceback、Java 的 Exception、GCC 的编译错误、Node.js 的异常提示看起来都不一样。前端展示时不能强行格式化只要把原始错误清楚展示出来就可以。第三运行时间要有限制。用户可能写出死循环也可能代码本身执行比较慢。接口不能一直等待所以需要设置超时时间。第四输出长度要有限制。如果用户代码输出大量内容接口和页面都会受到影响。所以需要对 stdout 和 stderr 做最大长度限制。七、统一返回格式为了让前端处理简单我把所有语言的结果都统一成一套格式。比如{success:true,stdout:hello world,stderr:,exit_code:0,timeout:false,duration_ms:120}如果运行失败也使用同样的结构{success:false,stdout:,stderr:SyntaxError: ...,exit_code:1,timeout:false,duration_ms:80}这样前端不用针对每种语言写一套展示逻辑。成功就展示输出失败就展示错误。超时就给出超时提示。八、前端体验前端页面我主要关注几个点。第一打开页面要能直接试。每种语言最好有默认示例代码。用户不用自己先想写什么打开就能点运行。第二输出区域要清楚。标准输出和错误输出最好分开展示。否则用户分不清到底是代码正常输出还是运行错误。第三按钮状态要明确。运行中要禁用按钮避免用户连续点击提交多个请求。第四错误提示不要太笼统。如果是代码报错就展示代码错误。如果是接口异常就展示接口异常。不要所有情况都只提示“运行失败”。九、这次做完后的感受做这个功能之前我以为重点是“怎么把代码跑起来”。做完以后我觉得重点其实是“怎么把结果稳定地返回给用户”。因为代码运行本身只是其中一步前后还要处理很多细节参数校验临时文件管理多语言配置编译和运行流程超时控制输出截断错误展示前端状态管理。这些细节都不算特别难但少一个就会影响体验。十、后续计划后面我准备继续优化几个方向。增加更多示例给每种语言准备一些常见示例比如字符串处理、数组遍历、时间格式化等。优化错误提示常见错误可以做一些简单说明让用户更容易看懂。支持运行历史登录用户可以保存最近运行过的代码片段下次继续编辑。优化移动端体验手机上写代码不一定方便但查看和运行简单示例还是有需求。逐步增加语言先把常用语言跑稳定再考虑更多语言不急着一次性加太多。十一、总结在线代码运行器看起来是一个小功能但实现时会遇到不少细节。我的经验是先从 Python 这类简单语言开始返回格式一开始就统一编译错误和运行错误分开处理一定要设置超时时间输出内容要做长度限制前端要把 stdout 和 stderr 展示清楚多语言最好用配置方式扩展。如果你也想做类似功能建议不要一上来就支持很多语言。先把一两个语言的完整链路跑通再逐步扩展。这样后面遇到问题时排查会轻松很多。