You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Django的在线Python编译器:Subprocess捕获STDIN输入问题

解决Django在线Python编译器中input提示流式传输与用户输入交互问题

原来的流式HTTP响应只能单向传输数据,没法接收用户输入,所以要实现input语句的完整交互,必须改用WebSocket双向通信。以下是具体实现步骤:

后端实现(基于Django Channels)

  1. 先安装Django Channels并配置ASGI服务(确保项目支持异步WebSocket)
  2. 编写WebSocket Consumer处理子进程的IO与客户端通信:
import subprocess
import json
from channels.generic.websocket import AsyncWebsocketConsumer
import asyncio

class CompilerConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        await self.accept()
        # 从路由参数获取待执行的Python代码
        code = self.scope['url_route']['kwargs']['code']
        # 启动异步子进程,开启管道用于stdin/stdout/stderr交互
        self.process = await asyncio.create_subprocess_exec(
            'python', '-c', code,
            stdin=asyncio.subprocess.PIPE,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
            text=True  # 以文本模式处理流,避免字节流转换麻烦
        )
        # 启动两个异步任务,分别读取标准输出和错误输出
        asyncio.create_task(self._read_stream(self.process.stdout, 'stdout'))
        asyncio.create_task(self._read_stream(self.process.stderr, 'stderr'))

    async def _read_stream(self, stream, stream_type):
        """实时读取子进程输出并发送给客户端"""
        while True:
            line = await stream.readline()
            if not line:  # 流结束,退出循环
                break
            # 将输出内容发送给客户端,带上类型标记
            await self.send(text_data=json.dumps({
                'type': stream_type,
                'content': line.rstrip('\n')  # 去掉换行符,前端自行处理显示
            }))
        # 子进程结束后发送退出信号
        await self.send(text_data=json.dumps({'type': 'exit'}))

    async def receive(self, text_data):
        """接收客户端发送的用户输入,写入子进程stdin"""
        data = json.loads(text_data)
        if data.get('type') == 'input':
            # 输入内容末尾加换行符,模拟用户回车操作
            self.process.stdin.write(f"{data['content']}\n")
            await self.process.stdin.drain()  # 确保数据写入管道

    async def disconnect(self, close_code):
        """客户端断开连接时终止子进程"""
        if hasattr(self, 'process'):
            self.process.terminate()
            await self.process.wait()

前端实现

前端需要建立WebSocket连接,根据后端发送的消息类型处理输出,在收到input提示时显示输入框,收集用户输入后发送回后端:

<div id="terminal" style="padding: 10px; background: #f5f5f5; font-family: monospace;"></div>
<input type="text" id="input-box" style="display: none; margin-top: 10px; padding: 5px; width: 100%;">

<script>
const terminal = document.getElementById('terminal');
const inputBox = document.getElementById('input-box');
// 替换成实际的WebSocket路由,这里假设code_id是代码的唯一标识
const ws = new WebSocket(`ws://${window.location.host}/ws/compiler/your-code-id/`);

ws.onmessage = function(event) {
    const data = JSON.parse(event.data);
    const outputLine = document.createElement('div');

    if (data.type === 'stdout') {
        outputLine.textContent = data.content;
        terminal.appendChild(outputLine);
        
        // 检测是否是input提示(这里简单通过包含": "判断,可根据实际场景调整逻辑)
        if (data.content.includes(': ')) {
            inputBox.style.display = 'block';
            inputBox.focus();
            // 监听回车事件,发送输入内容
            inputBox.onkeydown = function(e) {
                if (e.key === 'Enter') {
                    ws.send(JSON.stringify({
                        'type': 'input',
                        'content': inputBox.value
                    }));
                    inputBox.value = '';
                    inputBox.style.display = 'none';
                    inputBox.onkeydown = null;
                }
            };
        }
    } else if (data.type === 'stderr') {
        // 错误输出用红色显示
        outputLine.style.color = '#dc3545';
        outputLine.textContent = data.content;
        terminal.appendChild(outputLine);
    } else if (data.type === 'exit') {
        const exitMsg = document.createElement('div');
        exitMsg.textContent = '代码执行结束';
        exitMsg.style.marginTop = '10px';
        exitMsg.style.color = '#6c757d';
        terminal.appendChild(exitMsg);
        ws.close();
    }
};

ws.onclose = function() {
    const closeMsg = document.createElement('div');
    closeMsg.textContent = '连接已断开';
    closeMsg.style.marginTop = '10px';
    closeMsg.style.color = '#6c757d';
    terminal.appendChild(closeMsg);
};
</script>

关键说明

  • 为什么不用原来的流式HTTP?因为HTTP是单向协议,服务器无法主动接收客户端的输入,必须用WebSocket这种双向通信协议才能完成"服务器发提示→客户端传输入→服务器写stdin"的闭环。
  • 子进程必须用异步方式启动并读取流,避免阻塞Django服务的其他请求。
  • 前端判断input提示的逻辑可以根据实际情况优化,比如后端在发送提示时额外标记类型,前端直接识别标记更可靠。

内容的提问来源于stack exchange,提问作者Winmari Manzano

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 23:45:12