3

Pythonを使用してHTTP経由で「tail-f」のようなものを実装しようとしています。現在、Tornadoを使用しようとしていますが、非同期リクエストを実行する場合でも、一度に1つの接続しか処理していません。

import socket
import subprocess

import tornado.gen as gen
import tornado.httpserver
import tornado.ioloop
import tornado.iostream
import tornado.options
import tornado.web

from tornado.options import define, options

define("port", default=8888, help="run on the given port", type=int)
define(
    "inputfile",
    default="test.txt",
    help="the path to the file which we will 'tail'",
    type=str)


class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @gen.engine
    def get(self):
        print "GOT REQUEST"
        inputfile = open(options.inputfile)
        p = subprocess.Popen(
            "./nettail.py",
            stdin=inputfile,
            stdout=subprocess.PIPE)
        port_number = int(p.stdout.readline().strip())

        self.write("<pre>")
        self.write("Hello, world\n")
        self.flush()

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
        stream = tornado.iostream.IOStream(s)
        yield gen.Task(stream.connect, ("127.0.0.1", port_number))
        while True:
            data = yield gen.Task(stream.read_until, "\n")
            self.write(data)
            self.flush()

def main():
    tornado.options.parse_command_line()
    application = tornado.web.Application([
        (r"/", MainHandler),
    ])
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()


if __name__ == "__main__":
    main()

私が始めているプロセスは、ソケットに出力する単純な「テール」です。

import random
import socket
import sys
import time

#create an INET, STREAMing socket
s = socket.socket(
    socket.AF_INET, socket.SOCK_STREAM)

# Open the connection.
try:
    for attempt_number in xrange(5):
        port_number = random.randint(9000, 65000)
        try:
            s.bind(("localhost", port_number))
        except socket.error:
            continue
        # We successfully bound!
        sys.stdout.write("{0}".format(port_number))
        sys.stdout.write("\n")
        sys.stdout.flush()
        break

    #become a server socket
    s.listen(5)

    # Accept a connection.
    try:
        (clientsocket, address) = s.accept()

        while True:
            line = sys.stdin.readline()
            if not line:
                time.sleep(1)
                continue
            clientsocket.sendall(line)
    finally:
        clientsocket.close()

finally:
    s.close()

./nettail.pyは期待どおりに機能しますが、TornadoHTTPサーバーは一度に1つのリクエストしか処理しません。

古いブラウザと互換性があるため、これを行うには、長時間実行される永続的なHTTP接続を使用したいと思います。私は、WebSocketsが最新のブラウザーでどのように行われるかを理解しています。

編集:私はこれをWindowsではなくLinuxとSolarisで実行しています。つまり、ソケットプログラムではなく、ファイルでtornado.iostreamを使用できます。それでも、それは文書化された機能ではないので、接続ごとにソケットプログラムを起動します。

4

3 に答える 3

4

さらにデバッグを行った後、結局、このテール サーバーはブロックされていないことがわかりました。

Firefox の 2 つのウィンドウを開いて同時接続をテストしようとしましたが、最初のウィンドウが手動で停止されるまで、Firefox は 2 番目のウィンドウのフェッチを開始しませんでした。Firefox は、同じリソースを取得するために 2 つの同時 HTTP 接続を使用することを好みません。

Firefox ウィンドウと Chromium ウィンドウを開くと、「テール」出力が両方のタブにプッシュされているのがわかります。

いつもお世話になっております。@abarnert のコメントは特に役に立ちました。

編集:

リリース予定の 2.4.2 バージョンの Tornado では、「パイプ」IOStream が実装されています。これと通常の「テール」を使用すると、コードが大幅に簡素化されます。

import subprocess

import tornado.httpserver
import tornado.ioloop
import tornado.iostream
import tornado.options
import tornado.web

from tornado.options import define, options

define("port", default=8888, help="run on the given port", type=int)
define(
    "inputfile",
    default="test.txt",
    help="the path to the file which we will 'tail'",
    type=str)


class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
        print "GOT REQUEST"
        self.p = subprocess.Popen(
            ["tail", "-f", options.inputfile, "-n+1"],
            stdout=subprocess.PIPE)

        self.write("<pre>")
        self.write("Hello, world\n")
        self.flush()

        self.stream = tornado.iostream.PipeIOStream(self.p.stdout.fileno())
        self.stream.read_until("\n", self.line_from_nettail)

    def on_connection_close(self, *args, **kwargs):
        """Clean up the nettail process when the connection is closed.
        """
        print "CONNECTION CLOSED!!!!"
        self.p.terminate()
        tornado.web.RequestHandler.on_connection_close(self, *args, **kwargs)

    def line_from_nettail(self, data):
        self.write(data)
        self.flush()
        self.stream.read_until("\n", self.line_from_nettail)

def main():
    tornado.options.parse_command_line()
    application = tornado.web.Application([
        (r"/", MainHandler),
    ])
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()


if __name__ == "__main__":
    main()
于 2013-01-11T16:00:48.863 に答える
1

最近、実験としてこれを作成しました。複数の接続で動作しますが、それは役に立ちますか?

class TailHandler(BaseHandler):
    @asynchronous
    def get(self):
        self.file = open('data/to_read.txt', 'r')
        self.pos = self.file.tell()

        def _read_file():
            line = self.file.read()
            last_pos = self.file.tell()
            if not line:
                self.file.close()
                self.file = open('data/to_read.txt', 'r')
                self.file.seek(last_pos)
                pass
            else:
                self.write(line)
                self.flush()

            IOLoop.instance().add_timeout(time.time() + 1, _read_file)
        _read_file()
于 2013-01-10T15:16:32.960 に答える
-1

ハンドラーでこのようなブロッキング呼び出しを行うべきではありません。

    port_number = int(p.stdout.readline().strip())

ブロッキング呼び出しを回避するには、 selectまたは同様のメカニズムを使用する必要があります。

編集:わかりました、私は行ってドキュメントをチェックしました。iostreamを使用して読み取る必要がありますp

于 2013-01-09T23:02:38.900 に答える