前言
随着计算机科学与技术的发展,越来越多的人开始接触编程,也有越来越多的在线编程平台诞生。以Python语言的在线编程平台为例,关于Python语言的在线编程平台大致分为两类,一类是OJ类型的,即在线评测的编程平台,另一类则是学习、工具类的在线编程平台,例如Anycodes在线编程等网站。
但是,无论是那种类型,其背后的核心模块“代码执行器”/“判题机”等,都是值得被关注的。一方面,这类网站通常情况下都需要比要严格的“安全考虑”,例如程序会不会有恶意代码,出现死循环、破坏计算机系统等,程序是否需要隔离运行,运行时是否会获取到其他人提交的代码等;另一方面,这类平台通常情况下都会对资源消耗比较大,尤其是比赛来临时,更是需要突然间对相关机器进行扩容,必要时需要大规模集群来进行应对。同时这类网站通常情况下也都有一个比较大的特点,那就是触发式,即每个代码执行前后实际上并没有非常紧密的前后文关系等。
综合上面的问题和特点,结合Serverless架构,本文将会通过“Serverless角度”,实现简单的Python语言的在线编程能力,并对其进行进一步探索。
简单的在线代码执行器
基本流程
这一部分将会Python语言实现一个简单的在线代码执行工具,该工具拥有以下特点:
- 可以执行Python代码
- 支持标准输入
- 可以返回标准输出和标准错误
该简单工具的整个过程包括:
代码实现
其中执行代码部分,我们可以通过一些常见的Python依赖库,例如subprocess来进行实现:
child = subprocess.Popen("python %s" % (fileName),
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=True)
output = child.communicate(input=input_data.encode("utf-8"))
print(output)
在函数计算中(无论阿里云函数计算还是腾讯云云函数),通常情况下,再不进行硬盘挂载的前提下,只有/tmp/目录是有可写入权限的,所以函数计算部分,可以将代码写入临时目录/tmp/,并进行代码运行,获得结果:
# -*- coding: utf-8 -*-
import os
import json
import uuid
import random
import subprocess
# 随机字符串
randomStr = lambda num=5: "".join(random.sample('abcdefghijklmnopqrstuvwxyz', num))
# Response
class Response:
def __init__(self, start_response, response, errorCode=None):
self.start = start_response
responseBody = {
'Error': {"Code": errorCode, "Message": response},
} if errorCode else {
'Response': response
}
# 默认增加uuid,便于后期定位
responseBody['ResponseId'] = str(uuid.uuid1())
print("Response: ", json.dumps(responseBody))
self.response = json.dumps(responseBody)
def __iter__(self):
status = '200'
response_headers = [('Content-type', 'application/json; charset=UTF-8')]
self.start(status, response_headers)
yield self.response.encode("utf-8")
def WriteCode(code, fileName):
try:
with open(fileName, "w") as f:
f.write(code)
return True
except Exception as e:
print(e)
return False
def RunCode(fileName, input_data=""):
child = subprocess.Popen("python %s" % (fileName),
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=True)
output = child.communicate(input=input_data.encode("utf-8"))
print(output)
return output[0].decode("utf-8")
def handler(environ, start_response):
try:
request_body_size = int(environ.get('CONTENT_LENGTH', 0))
except (ValueError):
request_body_size = 0
requestBody = json.loads(environ['wsgi.input'].read(request_body_size).decode("utf-8"))
code = requestBody.get("code", None)
inputData = requestBody.get("input", "")
fileName = "/tmp/" + randomStr(5)
if code and WriteCode(code, fileName):
output = RunCode(fileName, inputData)
responseData = output
else:
responseData = "Error"
print(responseData)
return Response(start_response, {"result": responseData})
部署上线
当代码编写完成,可以直接通过Serverless-Devs工具进行项目部署,首先编写template.yaml文件:
ServerlessBookRunCodeDemo:
Component: fc
Provider: alibaba
Properties:
Region: cn-beijing
Service:
Name: ServerlessBook
Description: Serverless图书案例
Log:
Project: aliyun-fc-cn-beijing-9bd4a65b-c9d1-529d-9ce0-c91adfb823ab
LogStore: function-log
Function:
Name: serverless_run_code_ordinary
Description: 简单版代码在线执行器
CodeUri: ./
Handler: index.handler
MemorySize: 128
Runtime: python3
Timeout: 5
Triggers:
- Name: RunCodeOrdinary
Type: HTTP
Parameters:
AuthType: ANONYMOUS
Methods:
- GET
- POST
- PUT
Domains:
- Domain: Auto
完成编写之后,可以通过:
s deploy
进行项目部署:
完成项目部署之后,可以通过:
s logs -t
对日志系统进行监听,当函数被触发时,可以看到我们的一些日志内容。
接口测试
接下来,可以通过PostMan进行接口测试,以Python语言的输出语句为例:
print('HELLO WORLD')
测试结果:
可以看到,系统是可以正常输出我们的预期结果:HELLO WORLD,当我们将该部分代码进行破坏:
print('HELLO WORLD)
再次执行,可以看到错误结果:
当我们将带有输入内容的代码传入:
tempInput = input('please input: ')
print('Output: ', tempInput)
并输入“serverless devs”,可以看到:
至此我们完成了一个非常简单的在线代码执行的接口。针对这个接口,是有蛮大的优化空间的:
- 超时时间的处理
- 代码执行完成,可以进行清理
当然,通过这个接口也可以看到这样一个问题:那就是代码执行过程中是阻塞的,我们没办法进行持续性的输入,就算是想要输入内容也是需要将代码和输入内容一并发送到服务端。这种模式和目前市面上常见的OJ模式很类似。所以,针对上述的简单的在线代码执行器而言,是可以进行部分完善与OJ系统进行结合使用。