Tornado long connection - python

Can I realize "long connection(persistent connection, not long poll)" use tornado.web.RequestHandler? Because I need contain the connection, I need receive "heart message" which send by client, So, if tornado.web.RequestHandler can do it, How can I do? And have some demo like this?
Thanks!

RequestHandler isn't right for this, use WebSocketHandler instead. Here's an example app that receives a heartbeat from each client every second:
import tornado.ioloop
import tornado.web
import tornado.websocket
class HeartBeatReceiver(tornado.websocket.WebSocketHandler):
def open(self):
pass
def on_message(self, message):
print message
def on_close(self):
pass
class Main(tornado.web.RequestHandler):
def get(self):
# This could be a template, too.
self.write('''
<script>
function sendHeartBeat() {
ws.send("heartbeat");
setTimeout(sendHeartBeat, 1000);
}
ws = new WebSocket("ws://localhost:8888/websocket");
ws.onopen = sendHeartBeat;
</script>''')
application = tornado.web.Application([
(r"/", Main),
(r"/websocket", HeartBeatReceiver),
])
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()

Related

Sharing a variable between different classes in Tornado

I am trying to write a Tornado TCP + HTTP Server application.
My use case is a Tornado TCP + HTTP Server application which accepts data from a TCP client and pass the data to display it on a webpage hosted on the HTTP server.
Here is my tornado server code:
#!/usr/bin/env python
import os.path
import tornado.httpserver
import tornado.web
import logging
from tornado.ioloop import IOLoop
from tornado import gen
from tornado.iostream import StreamClosedError
from tornado.tcpserver import TCPServer
from tornado.options import options, define
define("port", default=6642, help="TCP port to listen on")
logger = logging.getLogger(__name__)
test = {}
class IndexHandler(tornado.web.RequestHandler):
def get(self):
global test
self.render('index.html', test=test)
class EchoServer(TCPServer):
#gen.coroutine
def handle_stream(self, stream, address):
global test
while True:
try:
test = yield stream.read_until("\n")
logger.info("Received bytes: %s", test)
except StreamClosedError:
logger.warning("Lost client at host %s", address[0])
break
except Exception as e:
print(e)
if __name__ == "__main__":
options.parse_command_line()
app = tornado.web.Application( handlers=[
(r'/', IndexHandler)],
static_path=os.path.join(os.path.dirname(__file__), "static"),
template_path=os.path.join(os.path.dirname(__file__), "templates"))
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
server = EchoServer()
server.listen(6641)
logger.info("Listening on TCP port %d",6641)
IOLoop.current().start()
Here is the python client code :
# echo_client.py
import socket
import time
counter = 0
host = '192.168.43.59'
port = 6641 # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
while True:
s.sendall("s\n")
counter = counter + 1
time.sleep(5)
I want to pass the data received from TCP client application into the variable "test" to the render template to display in index.html webpage but I am getting no data displayed.
I am using the global variable concept but no success since couldn't pass the updated "test" variable to index.html page.
If anyone could throw light on using common vaiables across different classes or handlers would help me.
Javascript file iam using is this :
/* test.js */
var test = ""
function set_test(val)
{
test=val
}
function show_test()
{
alert(test);
}
The HTML template used is this :
<!DOCTYPE html>
<html>
<meta http-equiv="refresh" content="30" />
<head>
<title>Test</title>
<script src="{{ static_url('scripts/test.js') }}"
type="application/javascript"></script>
</head>
<body>
<input type="button" onclick="show_test()" value="alert" />
<script type="application/javascript">
set_test("{{test}}");
</script>
</body>
</html>
Hi xyres,
Thank you for your spontaneous reply.I went through the link provided by you and after going through it i could understand that q.get() and q.put() can be used to store and retrieve data as you said.But i could not after modifying the tornado server code in the following manner I couldn't receive the data from TCP client , before this i could at least get data from TCP client .Can you let me know what is the mistake i have done in queue implementation
Here is my tornado server code:
#!/usr/bin/env python
import os.path
import tornado.httpserver
import tornado.web
import logging
from tornado.ioloop import IOLoop
from tornado import gen
from tornado.iostream import StreamClosedError
from tornado.tcpserver import TCPServer
from tornado.options import options, define
define("port", default=6642, help="TCP port to listen on")
logger = logging.getLogger(__name__)
#test = {}
q = Queue(maxsize=2)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
#global test
test = yield q.get
self.render('index.html', test=test)
class EchoServer(TCPServer):
#gen.coroutine
def handle_stream(self, stream, address):
#global test
yield q.put(test)
yield q.join()
while True:
try:
test = yield stream.read_until("\n")
logger.info("Received bytes: %s", test)
except StreamClosedError:
logger.warning("Lost client at host %s", address[0])
break
except Exception as e:
print(e)
if __name__ == "__main__":
options.parse_command_line()
app = tornado.web.Application( handlers=[
(r'/', IndexHandler)],
static_path=os.path.join(os.path.dirname(__file__), "static"),
template_path=os.path.join(os.path.dirname(__file__), "templates"))
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
server = EchoServer()
server.listen(6641)
logger.info("Listening on TCP port %d",6641)
IOLoop.current().start()
As per the tornado documentation it seems that queue can be applied to coroutines and here iam trying to replicate the same to two different classes.Is that a mistake ..Iam new to tornado so please bear my silly questions ..
You've a multiple options:
If you want to have a long-running connection, for example, if a client sends a request to IndexHandler and you want the client to wait until a message is in the queue, you can convert your handler to a coroutine.
If you want to return the response immediately, regardless of the availability of the data in the queue, you can use a queue's get_nowait() method.
Example for case #1:
from tornado.queues import Queue
q = Queue()
class IndexHandler(tornado.web.RequestHandler):
#gen.coroutine
def get(self):
self.data_future = q.get()
data = yield self.data_future
self.render('index.html', data=data)
def on_connection_close(self):
# set an empty result on the future
# if connection is closed so that
# the messages don't get removed from
# the queue unnecessariliy for
# closed connections
self.msg_future.set_result(None)
Example for case #2:
from tornado.queues import Queue, QueueEmpty
q = Queue()
def get(self):
try:
data = q.get_nowait()
except QueueEmpty:
data = None
self.render(...)

Tornado timeout block all services

I create a simple test app to check timeout in tornado
import tornado.ioloop
import tornado.web
class LoopHandler(tornado.web.RequestHandler):
def get(self):
while (True):
print ("in loop")
self.write("Loop, Handler")
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
(r"/loop", LoopHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Then I call http://localhost:8888/loop the endpoint never response because the infinite loop the problems is that http://localhost:8888/ not responding either. the question is why this happened and how can solve this?
EDIT
Update code that solve the problemn
import tornado.ioloop
import tornado.web
#unblock
class LoopHandler(tornado.web.RequestHandler):
def get(self):
while (True):
print ("in loop")
return "Loop, Handler"
#unblock
class MainHandler(tornado.web.RequestHandler):
def get(self):
return "Hello, world"
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
(r"/loop", LoopHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
#unblock.py
import tornado.web
import tornado.ioloop
from concurrent.futures import ThreadPoolExecutor
from functools import partial, wraps
EXECUTOR = ThreadPoolExecutor(max_workers=4)
def unblock(f):
#tornado.web.asynchronous
#wraps(f)
def wrapper(*args, **kwargs):
self = args[0]
def callback(future):
self.write(future.result())
self.finish()
EXECUTOR.submit(
partial(f, *args, **kwargs)
).add_done_callback(
lambda future: tornado.ioloop.IOLoop.instance().add_callback(
partial(callback, future)))
return wrapper
These are basics of async programming. To point you in the right direction take a look at the reactor pattern and especially at the event loop.
The reactor pattern is one implementation technique of event-driven
architecture. In simple terms, it uses a single threaded event loop
blocking on resource-emitting events and dispatches them to
corresponding handlers and callbacks.
Both functions LoopHandler and MainHandler are processed in the same event loop, therefore MainHandler gets queued but never executed since the event loop is busy executing LoopHandler.
One of the challenges (at least for me) in async programming is to be careful about blocking calls like database operations with for example SQLAlchemy, file operations, expensive calculations, etc. There are some interesting approaches using thread pools to solve this but you won't need them to get you started.
Ah, and in case you stumbling over the first sentence of the wiki article I have linked take a look here to understand the difference between parallel and concurrent. It helped me a lot.

python tornado - how to return real-time data

I am using the tornado library in python. I have a queue where data gets added in. I have to keep connection open so that when client requests I send out items from queue. Here is a simple implementation of mine. The problem I face is when I add new elements to queue, I don't see it being it returned. Infact, I don't see any code executed below IOLoop.current().start() line.
from tornado.ioloop import IOLoop
from tornado.web import RequestHandler, Application, url,asynchronous
from Queue import Queue
import json
q=Queue()
q.put("one")
q.put("two")
class HelloHandler(RequestHandler):
def get(self):
data=q.get()
self.write(data)
def make_app():
return Application([
url(r"/", HelloHandler),
])
def main():
app = make_app()
app.listen(8888)
IOLoop.current().start() # stops here
time.sleep(2)
q.put("three")
print q
if __name__=='__main__':
main()
first time on this :
http://localhost:8888/
returns "one"
second time on this:
http://localhost:8888/
return "two"
Third time on this"
http://localhost:8888/
blocking
The problem you have is that calling IOLoop.current().start() transfers control to Tornado. It loops until IOLoop.stop() is called.
If you need to do something after the IOLoop has started, then you can use one of the callbacks. For example, here is your code modified to use IOLoop.call_later(). You could also use IOLoop.add_timeout() if you are using an earlier (<4.0) version of Tornado.
from tornado.ioloop import IOLoop
from tornado.web import RequestHandler, Application, url,asynchronous
from Queue import Queue
import json
q=Queue()
q.put("one")
q.put("two")
class HelloHandler(RequestHandler):
def get(self):
data=q.get()
self.write(data)
def make_app():
return Application([
url(r"/", HelloHandler),
])
def main():
app = make_app()
app.listen(8888)
IOLoop.current().call_later(2, q.put, "three")
IOLoop.current().start()
if __name__=='__main__':
main()

why is tornado in python blocking my requests

I have this fairly simple code, taken from an example.
#!/usr/bin/python
import tornado.ioloop
import tornado.web
import tornado.gen
import time
class MainHandler(tornado.web.RequestHandler):
#tornado.web.asynchronous
#tornado.gen.engine
def get(self):
for i in range(1,10):
self.write("%d<br>" % i)
self.flush()
yield tornado.gen.Task(tornado.ioloop.IOLoop.instance().add_timeout, time.time() + 1)
self.finish()
application = tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
It isn't behaving how i'm expecting it to. If i open a browser window and point it to localhost:8888/, it will show 1 [pause 1 sec] 2 [pause 1 sec], etc. If i open a second tab doing the same request, it will block until the first request is finished. What am i missing?
Seems i should have used a different browser or an incognito window.
#Germano is right, it is the chrome shares the same connection for the same url.you can test with below code.
#coding:utf8
from tornado import ioloop
from tornado import web
from tornado import gen
class MainHandler(web.RequestHandler):
#gen.coroutine
def get(self):
client_address = self.request.connection.stream.socket.getpeername()
print repr(client_address), 'enter'
yield gen.sleep(10)
self.write("Hello, world")
print repr(client_address), 'leave'
self.finish()
if __name__ == "__main__":
application = web.Application([
(r"/", MainHandler),
])
application.listen(8888)
ioloop.IOLoop.current().start()

python Tornado websockets how to send message every X seconds?

I am trying to cobble together a test which allows websockets clients to connect to a Tornado server and I want the Tornado server to send out a message to all clients every X seconds.
The reason I am doing this is because wbesockets connections are being silently dropped somewhere and I am wondering of periodic "pings" sent by the websockets server will maintain the connection.
I'm afraid it's a pretty noobish question and the code below is rather a mess. I just don't have my head wrapped around Tornado and scope enough to make it work.
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
import tornado.gen
import time
from tornado import gen
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self):
print 'http://mailapp.crowdwave.com/girlthumb.jpg'
self.write_message("http://www.example.com/girlthumb.jpg")
def on_message(self, message):
print 'Incoming message:', message
self.write_message("http://www.example.com/girlthumb.jpg")
def on_close(self):
print 'Connection was closed...'
#gen.engine
def f():
yield gen.Task(tornado.ioloop.IOLoop.instance().add_timeout, time.time() + 8)
self.write_message("http://www.example.com/x.png")
print 'x'
#gen.engine
def g():
yield gen.Task(tornado.ioloop.IOLoop.instance().add_timeout, time.time() + 4)
self.write_message("http://www.example.com/y.jpg")
print 'y'
application = tornado.web.Application([
(r'/ws', WSHandler),
])
if __name__ == "__main__":
tornado.ioloop.IOLoop.instance().add_callback(f)
tornado.ioloop.IOLoop.instance().add_callback(g)
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
Why don't you try write a scheduler inside it? :)
def schedule_func():
#DO SOMETHING#
#milliseconds
interval_ms = 15
main_loop = tornado.ioloop.IOLoop.instance()
sched = tornado.ioloop.PeriodicCallback(schedule_func,interval_ms, io_loop = main_loop)
#start your period timer
sched.start()
#start your loop
main_loop.start()
Found that the accepted answer for this is almost exactly what I want:
How to run functions outside websocket loop in python (tornado)
With a slight modification, the accepted answer at the above link continually sends out ping messages. Here is the mod:
Change:
def test(self):
self.write_message("scheduled!")
to:
def test(self):
self.write_message("scheduled!")
tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=5), self.test)

Categories

Resources