-->

tornado one handler blocks for another

2019-09-15 03:39发布

问题:

Using python/tornado I wanted to set up a little "trampoline" server that allows two devices to communicate with each other in a RESTish manner. There's probably vastly superior/simpler "off the shelf" ways to do this. I'd welcome those suggestions, but I still feel it would be educational to figure out how to do my own using tornado.

Basically, the idea was that I would have the device in the role of server doing a longpoll with a GET. The client device would POST to the server, at which point the POST body would be transferred as the response of the blocked GET. Before the POST responded, it would block. The server side then does a PUT with the response, which is transferred to the blocked POST and return to the device. I thought maybe I could do this with tornado.queues. But that appears to not have worked out. My code:

import tornado
import tornado.web
import tornado.httpserver
import tornado.queues

ToServerQueue = tornado.queues.Queue()
ToClientQueue = tornado.queues.Queue()

class Query(tornado.web.RequestHandler):
    def get(self):
        toServer = ToServerQueue.get()
        self.write(toServer)

    def post(self):
        toServer = self.request.body
        ToServerQueue.put(toServer)
        toClient = ToClientQueue.get()
        self.write(toClient)

    def put(self):
        ToClientQueue.put(self.request.body)
        self.write(bytes())

services = tornado.web.Application([(r'/query', Query)], debug=True)
services.listen(49009)
tornado.ioloop.IOLoop.instance().start()

Unfortunately, the ToServerQueue.get() does not actually block until the queue has an item, but rather returns a tornado.concurrent.Future. Which is not a legal value to pass to the self.write() call.

I guess my general question is twofold:

1) How can one HTTP verb invocation (e.g. get, put, post, etc) block and then be signaled by another HTTP verb invocation.

2) How can I share data from one invocation to another?

I've only really scratched the simple/straightforward use cases of making little REST servers with tornado. I wonder if the coroutine stuff is what I need, but haven't found a good tutorial/example of that to help me see the light, if that's indeed the way to go.

回答1:

1) How can one HTTP verb invocation (e.g. get, put, post,u ne etc) block and then be signaled by another HTTP verb invocation.

2) How can I share data from one invocation to another?

The new RequestHandler object is created for every request. So you need some coordinator e.g. queues or locks with state object (in your case it would be re-implementing queue).

tornado.queues are queues for coroutines. Queue.get, Queue.put, Queue.join return Future objects, that need to be "resolved" - scheduled task done either with success or exception. To wait until future is resolved you should yielded it (just like in the doc examples of tornado.queues). The verbs method also need to be decorated with tornado.gen.coroutine.

import tornado.gen

class Query(tornado.web.RequestHandler):

    @tornado.gen.coroutine
    def get(self):
        toServer = yield ToServerQueue.get()
        self.write(toServer)

    @tornado.gen.coroutine
    def post(self):
        toServer = self.request.body
        yield ToServerQueue.put(toServer)
        toClient = yield ToClientQueue.get()
        self.write(toClient)

    @tornado.gen.coroutine
    def put(self):
        yield ToClientQueue.put(self.request.body)
        self.write(bytes())

The GET request will last (wait in non-blocking manner) until something will be available on the queue (or timeout that can be defined as Queue.get arg).

tornado.queues.Queue provides also get_nowait (there is put_nowait as well) that don't have to be yielded - returns immediately item from queue or throws exception.