第二讲 Django创建应用webcoding,实现在线编程

一、准备工作

  1. 下载bootstrap,解压缩到statics目录下。
  2. 下载codemirror,解压缩到statics目录下。
  3. 在templates目录下创建webcoding,新建模板文件codit.html。
  4. ubuntu下apt命令安装docker、docker-compose。
  5. 下载JudgeServer,git clone https://github.com/QingdaoU/JudgeServer

二、简版在线编程网页前端

subprocess子进程执行代码subprocess子进程执行代码

在codit.html中利用<div>来布局,用<textarea>作为代码编辑窗口。在head部分,引入bootstrap4的css文件和js文件,同时引入jquery.js配合bootstrap使用的,通过样式来控制页面布局。div作为总的容器,内部包含若干行,每行分为12列,根据需要从左右分成几个部分,宽度由col-md-x中为个数字x(1<=x<=12)来分配。
表单放在第二行,左右各空1例,代码窗口占7列,语言选择、标准输入和提交按钮占3列,标准输出窗口在下方第三行。表单元素中定义了id和name,id是网页中js要使用的,name是提交表单到后端使用的。html代码如下:

<html>
    <head>
        <title> 在线程序编辑器  </title>
        <link rel="stylesheet" href="/static/bootstrap4/css/bootstrap.css">
    </head>
    <script type="text/javascript"  src="/static/bootstrap4/js/bootstrap.js"></script>
    <script type="text/javascript"  src="/static/bootstrap4/jquery.min.js"></script>
<body>
<br><br>
<div class='container'>
    <div class='row' ><!--第一行-->
        <div class='col-md-4'></div>
        <div class='col-md-5'> <font size='7' > 在线编程简化版 </font></div>
        <div class='col-md-3'></div>
    </div>
    <br>
     <form id='frm_coding' method='POST'>
     <div class='row'><!--第二行,表单-->
         <div class='col-md-1'></div>
         <div class='col-md-7'>
              <textarea id='src_coding' name='src_coding' style='width:600px;height:450px;'> </textarea>
          </div>
          <div class='col-md-3'>
             <select id='language' name='language'>
                 <option value='text/x-csrc'>c</option>
                    <option value='text/x-python' selected="selected">python</option>
                    <option value='text/x-c++src'>c++</option>
                    <option value='text/x-java'>java</option>
             </select><br>
             <font size='6' color='green'> input </font><br>
             <textarea id='cin_coding'  name='cin_coding' style='width:200px;height:300px;'></textarea>
             <br><br>
             <button class='btn btn-primary' id='sub_coding' type='button' onclick='up_data()'>调试运行</button>
           </div>
           <div class='col-md-1'></div>
      </div>
      </form>
      <div class='row'><!--第三行,输出运行结果-->
         <div class='col-md-1'></div>
         <div class='col-md-10'>
            <font size='6' color='green'> output </font><br><br>
            <textarea id='cout_coding'  name='cout_coding' style="border:1px solid gray;width:100%; height:150px;overflow-y:auto"></textarea>
         </div>
         <div class='col-md-1'></div>
      </div>
</div>
</body>
</html>

表单提交采用了ajax局部提交,提交代码如下:

<script type="text/javascript">
    function up_data(){
        udata = $('#frm_coding').serializeArray();
        $.ajax({
            type: 'POST',
            url: '/codej/',
            data:JSON.stringify(udata),
            contentType: 'application/json',
            dataType: 'json',
            success: function(data){
                $("#cout_coding").text(data);
                console.log(data);
            },
            error: function(data){
                $("#cout_coding").text(data);
            },
        });
    }
</script>

上述代码提交表单后,返回的信息更新到页面中最下面的cout_conding文本框中。有关ajax()详情参见https://www.cnblogs.com/tylerdonet/p/3520862.html 。

三、后端编译运行代码

在webcoding中的views.py中添加两个执行代码的视图:def codejudge(request):def codejudgeserver(request):,在almond中的urls.py中添加三个路由地址:

# webcoding的路由
path('codev/', codevelop, name='codev'),
path('codej/', codejudge, name='codej'),
path('codejs/', codejudgeserver, name='codejs'),

这里是把所有的路由放在主应用的urls.py文件中的,与大多数的django教程中不同 ------ 在各自的应用中有urls.py文件放置各自应用的路由,在主应用的urls.py文件中包含其他应用的urls.py文件。
下面实现视图codejudge,前后端是采用json格式来传递数据的。下面代码有详细解释

#简化版 后端 代码编译 运行
def codejudge(request):
    #首先获取表单数据udata,对数据整理一下放入字典x中
    udata = json.loads(request.body)
    print(udata)
    x = dict()
    for val in udata:
        x[str(val['name'])] = val['value']
    print(x)
    result={}
    #获取表单数据也可以用request.POST来,看情况定

    #根据选用的语言组织不同的编译命令和运行命令
    if x['language'] == 'text/x-csrc':
        fsource = open('test.c','w')
        fsource.write(x['src_coding'])
        fsource.close()

        fstdin = open('stdin.txt','w')
        fstdin.write(x['cin_coding'])
        fstdin.close()

        compile_cmd ='gcc  test.c -o  test.so'
        run_cmd='./test.so < stdin.txt'

    elif x['language'] == 'text/x-c++src':
        fsource = open('test.cpp','w')
        fsource.write(x['src_coding'])
        fsource.close()

        fstdin = open('stdin.txt','w')
        fstdin.write(x['cin_coding'])
        fstdin.close()

        compile_cmd ='g++  test.cpp -o  test.so'
        run_cmd='./test.so < stdin.txt'

    elif x['language'] == 'text/x-java':
        fsource = open('main.java','w')
        fsource.write(x['src_coding'])
        fsource.close()

        fstdin = open('stdin.txt','w')
        fstdin.write(x['cin_coding'])
        fstdin.close()

        compile_cmd ='javac  main.java '
        run_cmd='java main'

    elif x['language'] == 'text/x-python':
        fsource = open('test.py','w')
        fsource.write(x['src_coding'])
        fsource.close()

        fstdin = open('stdin.txt','w')
        fstdin.write(x['cin_coding'])
        fstdin.close()

        compile_cmd ='python -m py_compile test.py'
        run_cmd='python __pycache__/test.cpython-310.pyc < stdin.txt'

    #利用pyhon的子进程模块subprocess来编译和运行程序
    out = err = ""
    cominfo = subprocess.Popen(compile_cmd, shell=True,
                              stdin=subprocess.PIPE,stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE,universal_newlines=True)
    #等子进程编译程序结束,并获取信息
    out, err = cominfo.communicate()
    if err :
        #编译出错,返回出错信息
        print("compile err:",err)
        result = err  
    else:
        #编译通过,运行编译之后的字节码 或 可执行文件
        runinfo = subprocess.Popen(run_cmd, shell=True,
                               stdin=subprocess.PIPE,stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,universal_newlines=True)
        err = out = ""
        #等子进程运行结束,并获取运行结果信息
        out, err = runinfo.communicate()
        if err :
            print('run error',err)
            result = err
        else:
            result = out
    return JsonResponse(result,safe=False)

若编译有语法错误,就返回错误信息到前端;编译通过再运行程序,返回相应信息。信息采用json格式传递。
这里有一个重要问题是当提交的代码有死循环等有害代码时会对服务器的运行造成不利。接下来用到沙盒来隔离运行代码,并通过服务来向前端提供编译运行环境,避免并发造成服务器的阻塞。

四、使用OnlineJudgeServer运行代码

准备工作:onlinejudge是开源的编程在线测评系统,由四个模块组成。这里用到其中Judger和JudgeServer,git clone https://github.com/QingdaoU/JudgeServer 下载到本地,当前目录会有JudgeServer目录,进入其中,复制cp ./docker-compose.example.yml ./docker-compose.yml。这是基于docker来布署的,你的系统要安装docker、docker-compose。使用命令docker-compose up -d,根据docker-compose.yml文件的内容来布署,第一次运行会下载docker镜像并运行容器,以后会随系统一起启动。使用命令docker ps可以查询已经运行的docker容器。

wuxc@wubuntu:~$ docker ps -f id=b4b5 --format "{{.ID}}   {{.Image}}  {{.Names}} "
b4b53a76634c   wuxc/judgeserver:1.0.1  judgeserver_judge_server_1

这是我修改JudgeServer目录下的Dockerfile文件,docker build .来创建自己的JudgeServer镜像,再修改docker-compose.yml相应部分。这样可以使用编译软件的新版本及增加其他的编程语言。有关docker 的更多知识请查阅相关资料。

如何使用:准备语言配置和调用方法,放在文件judge.py中供视图webcoding.codejudgeserer视图调用,部分内容如下

#语言配置
py3_lang_config = {
    "compile": {
        "src_name": "solution.py",
        "exe_name": "__pycache__/solution.cpython-38.pyc",
        "max_cpu_time": 3000,
        "max_real_time": 5000,
        "max_memory": 128 * 1024 * 1024,
        "compile_command": "/usr/bin/python3 -m py_compile {src_path}",
    },
    "run": {
        "command": "/usr/bin/python3 {exe_path}",
        "seccomp_rule": "general",
        "env": ["PYTHONIOENCODING=UTF-8"] + default_env
    }
}

其他语言类似。定义一个调用服务的类,

class JudgeClient(object):
    def __init__(self, token, server_base_url):
        self.token = hashlib.sha256(token.encode("utf-8")).hexdigest()
        self.server_base_url = server_base_url.rstrip("/")

    def _request(self, url, data=None):
        kwargs = {"headers": {"X-Judge-Server-Token": self.token,
                              "Content-Type": "application/json"}}
        if data:
            kwargs["data"] = json.dumps(data)
        try:
            return requests.post(url, **kwargs).json()
        except Exception as e:
            raise JudgeError(str(e))

    def ping(self):
        return self._request(self.server_base_url + "/ping")

    def judge(self, src, language_config, max_cpu_time, max_memory, test_case_id=None, test_case=None, spj_version=None,
              spj_config=None,
              spj_compile_config=None, spj_src=None, output=False):
        if not (test_case or test_case_id) or (test_case and test_case_id):
            raise ValueError("invalid parameter")
   ......

这个类准备了一些参数,通过方法judge()来调用服务执行代码。judge.py根据JudgeServer目录下的client/Python下的几个文件整理而来的。 接下来看看webcoding下的视图函数codejudgeserver():

#沙盒代码编译执行代码
def codejudgeserver(request):
    #获取表单提交数据
    ........
    #根据表单数据选择 语言配置
    if x['language'] == "x-src-python":
        lang_config = py3_lang_config
        ......

    src = x['src_coding']
    stdinput = x['cin_coding']

    #调用JudgeServer,token值要与docker镜像中一致
    token = "YOUR_TOKEN_HERE"
    client = JudgeClient(token=token, server_base_url="http://127.0.0.1:12357")
    #测试与JudgeServer接连良好
    print("ping")
    print(client.ping(), "\n\n")

    #执行代码,返回结果,指定cpu memory最大值
    result = client.judge(src=src, language_config=lang_config,
                       max_cpu_time=1000, max_memory=1024 * 1024 * 128,
                       test_case=[{"input": stdinput, "output": ""}], output=True)

    if result['err'] != None:
        y = result
    else:
        rusd = dict(result['data'][0])
        print(rusd)
        y = result['data'][0]['output']+'\n'
        for k,v in rusd.items():
            if k in ['cpu_time','memory','real_time']:
                y += k+";"+str(v)+"    "
    print(y)

    return JsonResponse(y, safe=False)

沙盒判题系统执行代码斜体样式沙盒判题系统执行代码

通过设置max_cpu_time和max_memory的值终止有害代码的无限运行。test_case测试用例中仅用于stdinput输入,不用output对比。对输出的评判后续介绍,这里能利用JudgeServer来编译和运行程序就行了。

五、用codemirror作代码编辑器

CodeMirror是一款在线的功能齐全的代码编辑器,提供了很多流行语言的代码高亮、自动缩进功能和符号匹配等功能。在模板codit.html中作些调整,下面两个是使用 CodeMirror 必须引入的两主文件

<!-- 引入codemirror -->
<link rel="stylesheet" href="/static/codemirror/lib/codemirror.css">
<script type="text/javascript"  src="/static/codemirror/lib/codemirror.js"></script>

实现当前行背景高亮和括号匹配功能引入下列文件:

<script type="text/javascript"   src="/static/codemirror/addon/selection/active-line.js"></script>
<script type="text/javascript"   src="/static/codemirror/addon/edit/matchbrackets.js"></script>

实现某语言语法高亮显示功能引入下列文件:

<script type="text/javascript"   src="/static/codemirror/mode/clike/clike.js"></script>
<script type="text/javascript"   src="/static/codemirror/mode/python/python.js"></script>

clike.js 实现了c,c++,java三个语言的语法高亮显示功能。
实现编辑器的主题样式功能引入下列文件:

<link rel="stylesheet" href="/static/codemirror/theme/monokai.css">
<link rel="stylesheet" href="/static/codemirror/theme/twilight.css">

引入两个样式,可以切换使用。
实现快捷键功能引入下列文件:

<script type="text/javascript"   src="/static/codemirror/keymap/sublime.js"></script>

在模板文件templates/webcoding/codit.hml的head中添加以上各行代码,再于up_data()函数的第一行加入:$('#src_coding').val(editor.getValue()),意思是把codemirror的编辑器editor中的代码给表单元素id值为src_coding的文本框,以便提交。
在up_data()函数的之后,添加函数modechange(value),作用是在选择编程语言时切换相应的语法高亮。

function modechange(value){
    //alert(value);
    txt = editor.getValue();
    editor.setOption("mode", value);
    editor.setValue(txt);
        }

在模板文件codit.html最后加入定义设置editor的代码:

<script>
    // Script for the editor.
    var editor = CodeMirror.fromTextArea(document.getElementById("src_coding"), {
        styleActiveLine: true,
        lineNumbers: true,
        lineWrapping: true,
        mode: 'text/x-python',
        keyMap: "sublime",
        autoCloseBrackets: true,
        matchBrackets: true,
        showCursorWhenSelecting: true,
        theme: "monokai",
        tabSize: 4,
        indentUnit: 4,
        smartIndent: true,
      });

      //editor.setCursor(0);
      editor.setSize('auto', '480px');
</script>

大功告成,看看运行效果。

codemirror样式一codemirror样式一
codemirror样式二codemirror样式二

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值