Can a Tornado HTTP server also be a Tornado websocket client?

1,660 views
Skip to first unread message

JohnMudd

unread,
Apr 12, 2017, 10:04:23 AM4/12/17
to Tornado Web Server
I already have a Tornado HTTP server that is also a websocket server. That works well. 

I have another Tornado HTTP server that uses HTTP to talk to the first server. I'd like to replace the HTTP communication with websocket as a client of the first server. Is that possible without getting "current IOLoop already exists"?

John



JohnMudd

unread,
Apr 15, 2017, 10:38:35 AM4/15/17
to Tornado Web Server
Here's a clue from Ben at http://stackoverflow.com/a/30362810/487992

You can run multiple HTTPServers (or other servers) on the same IOLoop.
 
It is possible to create multiple IOLoops and give each one its own thread, but...

Here's at attempt. The websocket client connects to the websocket but ioloop.start() in HttpServer produces "RuntimeError: IOLoop is already running".

Switching both calls to IOLoop.instance() to IOLoop() runs w/o error, the websocket client connects to the websocket but the HTTP server does not respond.

import threading
import time

import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.websocket

class HttpServer(threading.Thread):

    def __init__(self):
        super(self.__class__, self).__init__()
        self.start()

    def run(self):
        class HelloHandler(tornado.web.RequestHandler):
            def get(self):
                self.write('Hello!')

        tornado_handlers = [
            ('/hello', HelloHandler),
        ]

        tornado_app = tornado.web.Application(tornado_handlers)
        http_server = tornado.httpserver.HTTPServer(tornado_app, no_keep_alive=True)
        host = 'localhost'
        port = 8040
        http_server.bind(port, address=host)
        tornado_process_count = 1
        http_server.start(tornado_process_count)
        #app.listen(port)
        ioloop = tornado.ioloop.IOLoop.instance()
        print  'Serving HTTP on %s:%d' % (host, port)
        print dir(ioloop)
        ioloop.start()

class WebsocketClient(threading.Thread):
    def __init__(self):
        super(self.__class__, self).__init__()
        self.start()

    @tornado.gen.coroutine
    def _connect_to_server(self):
        client = tornado.httpclient.HTTPRequest('ws://localhost:8030/websocket')
        self.conn = yield tornado.websocket.websocket_connect(client, on_message_callback=self._on_message)

    def run(self):
        ioloop = tornado.ioloop.IOLoop.instance()
        ioloop.run_sync(self._connect_to_server)
        print 'Connected to websocket.'
        time.sleep(100000)

    def _on_message(self, msg):
        self.conn.write_message('Echo: ' + msg)


http_server_thread = HttpServer()
ws_server_thread = WebsocketClient()

JohnMudd

unread,
Apr 15, 2017, 10:44:35 AM4/15/17
to Tornado Web Server
Here's the websocket server I used for reference.

import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.websocket

class EchoWebSocket(tornado.websocket.WebSocketHandler):
    def open(self):
        print("WebSocket opened")

    def on_message(self, message):
        print 'Recv: ' + message
        self.write_message(u"You said: " + message)

    def on_close(self):
        print("WebSocket closed")

tornado_handlers = [
    ('/websocket', EchoWebSocket),
]

tornado_app = tornado.web.Application(tornado_handlers)
http_server = tornado.httpserver.HTTPServer(tornado_app, no_keep_alive=True)
http_server.bind(8030, address='localhost', reuse_port=True)  
tornado_process_count = 1
http_server.start(tornado_process_count)
ioloop = tornado.ioloop.IOLoop.instance()
ioloop.start()

Pierce Lopez

unread,
Apr 15, 2017, 1:29:00 PM4/15/17
to Tornado Web Server
On Saturday, April 15, 2017 at 10:38:35 AM UTC-4, JohnMudd wrote:
Here's a clue from Ben at http://stackoverflow.com/a/30362810/487992

You can run multiple HTTPServers (or other servers) on the same IOLoop.
 
It is possible to create multiple IOLoops and give each one its own thread, but...

Here's at attempt. The websocket client connects to the websocket but ioloop.start() in HttpServer produces "RuntimeError: IOLoop is already running".

Switching both calls to IOLoop.instance() to IOLoop() runs w/o error, the websocket client connects to the websocket but the HTTP server does not respond.

 IOLoop.instance() returns a single global IOLoop. It will only run in a single thread. You should only start it once in one thread.

time.sleep() is never appropriate to run on a thread which is running an IOLoop, as you attempt to do in your websocket client. The IOLoop cannot run during time.sleep(), callbacks cannot be triggered, etc.

There are interesting things you can do with threads, but you need to learn the basics first. Stick to a single thread, single IOLoop. In a main() function or section, create your HTTPServer and WebSocket instances. This main() is not a coroutine, it can't await or yield, but it can create things that listen on ports, and it can schedule callbacks for later. Finally, start() the singleton IOLoop at the end of your main() - everything will run all together on the IOLoop.

If you really want to sleep in a coroutine, use tornado.gen.sleep()

Ben Darnell

unread,
Apr 15, 2017, 1:30:39 PM4/15/17
to python-...@googlegroups.com
On Sat, Apr 15, 2017 at 10:38 AM JohnMudd <john...@gmail.com> wrote:
Here's a clue from Ben at http://stackoverflow.com/a/30362810/487992

You can run multiple HTTPServers (or other servers) on the same IOLoop.
 
It is possible to create multiple IOLoops and give each one its own thread, but...


Read the rest of that sentence: "but this is rarely useful". Don't use threads, just do everything on the shared `IOLoop.current()` IOLoop. Call `IOLoop.current().start()` at the end of your `__name__ == '__main__'` block and nowhere else. Don't use `run_sync` or `time.sleep`; you have to do everything asynchronously. Translating from the code you posted:

    class WebsocketClient(object):
        # include _connect_to_server and _on_message as before

        @tornado.gen.coroutine
         def run(self):
             yield self._connect_to_server()
             print('connected to websocket')
             # This is not necessary now that we're not using run_sync,
             # but in case you do need to sleep here's how you do it.
             # If you mean "wait forever", you could also do
             # `yield tornado.locks.Event().wait()` to wait on an event that will
             # never be set.
             yield tornado.gen.sleep(100000)

    # in __main__:
        # set up http server, then
        IOLoop.current().spawn_callback(WebsocketClient().run)
        IOLoop.current().start()

-Ben
 
--
You received this message because you are subscribed to the Google Groups "Tornado Web Server" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python-tornad...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

JohnMudd

unread,
Apr 15, 2017, 5:56:03 PM4/15/17
to Tornado Web Server, b...@bendarnell.com
Thanks! I followed your example and I have a working HTTP server / Websocket client with no threads and one ioloop. I'll test further and then post it here.

JohnMudd

unread,
Apr 16, 2017, 10:03:53 PM4/16/17
to Tornado Web Server, b...@bendarnell.com
Here's my new version. This combines a HTTP Server and Websocket client.

I also added code to retry websocket connection as needed.

import time
import errno
import socket

import tornado
import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.websocket
 
class HelloHandler(tornado.web.RequestHandler):
    def get(self):
        try:
            ws_client.conn.write_message(('test'))
        except:
            print 'Tried to send websocket msg but failed.'
        self.write('Hello!')

tornado_handlers = [
    ('/hello', HelloHandler),
]

tornado_app = tornado.web.Application(tornado_handlers)
http_server = tornado.httpserver.HTTPServer(tornado_app, no_keep_alive=True)
host = 'localhost'
port = 8040
http_server.bind(port, address=host, reuse_port=True)  # TODO: reuse_port might require a modern kernel?
tornado_process_count = 1
http_server.start(tornado_process_count)
print  'Serving HTTP on %s:%d' % (host, port)


class WebsocketClient(object):
    def __init__(self, conn_pause=1):
        self.conn_pause = conn_pause
        self.client = tornado.httpclient.HTTPRequest('ws://localhost:8030/websocket')

    @tornado.gen.coroutine
    def connect_to_server(self):
        self.conn = None
        while self.conn is None:
            try:
                t1 = time.time()
                self.conn = yield tornado.websocket.websocket_connect(self.client, on_message_callback=self.on_message)
                elapsed = (time.time() - t1) * 1000
                print 'Connection elapsed: %.1f msec' % elapsed
            except socket.error as serr:  # https://goo.gl/xchRcA
                if serr.errno == errno.ECONNREFUSED:
                    print 'Websocket connection refused. Sleeping...'
                    yield tornado.gen.sleep(self.conn_pause)  # Returns immediately?
                else:
                    raise

    def on_message(self, msg):
        if msg is None:
            # Reconnect.
            self.connect_to_server()
        else:
            print 'Received: %s' % `msg`


ioloop = tornado.ioloop.IOLoop.current()

ws_client = WebsocketClient()
ioloop.spawn_callback(ws_client.connect_to_server)  # https://goo.gl/kFcF7X

ioloop.start()



Here's the Websocket server I use for testing.

import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.websocket

class EchoWebSocket(tornado.websocket.WebSocketHandler):
    def open(self):
        #self.set_nodelay(True)  # "Usually doesn't help". https://goo.gl/NzveYq
        print("WebSocket opened")

    def on_message(self, message):
        #print 'Recv: ' + message
        self.write_message(u"You said: " + message)

    def on_close(self):
        print("WebSocket closed")

tornado_handlers = [
    ('/websocket', EchoWebSocket),
]

tornado_app = tornado.web.Application(tornado_handlers)
http_server = tornado.httpserver.HTTPServer(tornado_app, no_keep_alive=True)
http_server.bind(8030, address='localhost', reuse_port=True)  # TODO: reuse_port might require a modern kernel?

sandy

unread,
Feb 7, 2023, 6:51:51 AM2/7/23
to Tornado Web Server
Hello 

I am trying to implement something similar to what JohnMudd has done (see last message), but I need to send the data(unique data) which the websocket server sends to the client, as a response to the GET request , Is there anyway to implement this logic?

New to tornado! Any help will be very much appreciated.

Japhy Bartlett

unread,
Feb 9, 2023, 1:02:41 PM2/9/23
to python-...@googlegroups.com
Hey Sandy -

The thread you found is pretty dated, you'd probably have the best luck starting over at tornadoweb.org with something like https://www.tornadoweb.org/en/stable/websocket.html

"""

Here is an example WebSocket handler that echos back all received messages back to the client:

class EchoWebSocket(tornado.websocket.WebSocketHandler):
    def open(self):
        print("WebSocket opened")

    def on_message(self, message):
self.write_message(u"You said: " + message)

    def on_close(self):
        print("WebSocket closed")
"""

- Japhy

--
You received this message because you are subscribed to the Google Groups "Tornado Web Server" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python-tornad...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages