直播stdout和stdin与websocket

时间:2017-01-02 18:46:13

标签: python python-2.7 websocket

online compiler这是我的网站,用户可以在其中运行控制台程序。

目前,用户必须在运行程序之前输入程序输入。我正在尝试为程序构建实时用户输入(希望提供与在笔记本电脑上运行程序相同的体验)。

在实现这一目标的研究中,我遇到了 stream stdout和stdin websocket 的解决方案。

我的实施

# coding: utf-8
import subprocess
import thread

from tornado.websocket import WebSocketHandler

from nbstreamreader import NonBlockingStreamReader as NBSR


class WSHandler(WebSocketHandler):
    def open(self):
        self.write_message("connected")
        self.app = subprocess.Popen(['sh', 'app/shell.sh'], stdout=subprocess.PIPE, stdin=subprocess.PIPE,
                                    shell=False)
        self.nbsr = NBSR(self.app.stdout)
        thread.start_new_thread(self.soutput, ())

    def on_message(self, incoming):
        self.app.stdin.write(incoming)

    def on_close(self):
        self.write_message("disconnected")

    def soutput(self):
        while True:
            output = self.nbsr.readline(0.1)
            # 0.1 secs to let the shell output the result
            if not output:
                print 'No more data'
                break
            self.write_message(output)

nbstreamreader.py

from threading import Thread
from Queue import Queue, Empty


class NonBlockingStreamReader:
    def __init__(self, stream):
        '''
        stream: the stream to read from.
                Usually a process' stdout or stderr.
        '''

        self._s = stream
        self._q = Queue()

        def _populateQueue(stream, queue):
            '''
            Collect lines from 'stream' and put them in 'quque'.
            '''

            while True:
                line = stream.readline()
                if line:
                    queue.put(line)
                else:
                    raise UnexpectedEndOfStream

        self._t = Thread(target=_populateQueue,
                         args=(self._s, self._q))
        self._t.daemon = True
        self._t.start()  # start collecting lines from the stream

    def readline(self, timeout=None):
        try:
            return self._q.get(block=timeout is not None,
                               timeout=timeout)
        except Empty:
            return None


class UnexpectedEndOfStream(Exception): pass

shell.sh

#!/usr/bin/env bash
echo "hello world"
echo "hello world"
read -p "Your first name: " fname
read -p "Your last name: " lname
echo "Hello $fname $lname ! I am learning how to create shell scripts"

此代码流stdout un-till shell.sh代码到达read语句。

请指导我做错了什么。为什么它不等待标准输入并达到打印状态?没有更多数据'在完成程序执行之前?

测试它的源代码https://github.com/mryogesh/streamconsole.git

2 个答案:

答案 0 :(得分:2)

您的readline()方法超时,除非您在100毫秒内发送输入,然后打破循环。您没有看到read -p提示的原因是缓冲(因为readline和管道缓冲)。最后,您的示例javascript不会发送尾随换行符,因此read将不会返回。

如果你增加超时,包括换行符和find a way to work around buffering issues,你的示例应该基本上有效。

我还使用tornado.process和协同程序而不是子进程和线程:

from tornado import gen
from tornado.process import Subprocess
from tornado.ioloop import IOLoop
from tornado.iostream import StreamClosedError
from tornado.websocket import WebSocketHandler


class WSHandler(WebSocketHandler):
    def open(self):
        self.app = Subprocess(['script', '-q', 'sh', 'app/shell.sh'], stdout=Subprocess.STREAM, stdin=Subprocess.STREAM)
        IOLoop.current().spawn_callback(self.stream_output)

    def on_message(self, incoming):
        self.app.stdin.write(incoming.encode('utf-8'))

    @gen.coroutine
    def stream_output(self):
        try:
            while True:
                line = yield self.app.stdout.read_bytes(1000, partial=True)
                self.write_message(line.decode('utf-8'))
        except StreamClosedError:
            pass

答案 1 :(得分:-2)

也许你应该看看websocketd(homepage) 我觉得它会简化你想要做的事情。
它将充当websocket服务器并启动程序,您可以在每次客户端连接时将其作为参数提供。来自websocket连接的所有内容都将转发给该程序的STDIN。程序输出到STDOUT的所有内容都将通过websocket发送。