Program gets stuck with tornado - python

I'm trying to make a Raspberry Pi send plain text to my phone over my local network, from where I plan to pick it up.
I tried the following "hello world"-like program from their official website, but I cannot get it to proceed after a point.
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Ugh, the world? Well.. hello, I guess")
application = tornado.web.Application([
(r"/", MainHandler),
])
application.listen(8881)
tornado.ioloop.IOLoop.instance().start()
# I cannot get this line to execute!!
print("Hi!!")
Experience: basics of Python, intermediate with Arduino C++, none in networking/web

You're trying to print to STDOUT after starting the event loop, so that print statement never sees the light of the day. Basically, you're creating a HTTP server at port 8881 that is constantly listening for requests. Whatever logic you wish the server to do needs to be in a callback, like MainHandler
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Ugh, the world? Well.. hello, I guess")
# All your SMS logic potentially goes here
self.write("Sent SMS to xyz...")
application = tornado.web.Application(
[
(r"/", MainHandler),
]
)
application.listen(8881)
tornado.ioloop.IOLoop.instance().start()
Then trigger the endpoint by making an HTTP call
curl <IP ADDRESS OF YOUR PI>:8881

This is because Tornado's IOLoop.start() method is a "blocking" call, which means that it doesn't return until some condition is met. This is why your code "gets stuck" on that line. The documentation for this method states:
Starts the I/O loop.
The loop will run until one of the callbacks calls stop(), which will
make the loop stop after the current event iteration completes.
Typically, a call to IOLoop.start() will be the last thing in your program. The exception would be if you want to stop your Tornado application and then proceed to do something else.
Here are two possible solutions to your problem, depending on what you want to accomplish:
call self.stop() from the handler. This will stop the Tornado application, IOLoop.start() will return, and then your print will execute.
call print("Hi!!") from your handler.

Related

How to write a Tornado listener correctly?

I wanted to write a server background listener to listen to dozens of clients sending commands to this background listener on TCP port 8888. Command format is "http://myip/?c="ClientCommand=A5"
Those clients could be sending commands at the same time or different time. Also, these clients will stay connected and sending few different commands until disconnect by client themselves. All the commands received by the background listener will be written to a MySQL DB.
After few days of study, I found Python + Tornado framework could be a good tool to do this. However, I have tried few days with examples from the web and I still don't know how to program this background listener.
Here is an example I found on a blog and it worked...How do I modify the code below to do the background listener? I know I need to use GET to get parameters from the URL. I have tried get(self.get), but I got error of invalid syntax...
Thanks for your help.
import tornado.ioloop
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, Tornado!")
if __name__ == "__main__":
application = tornado.web.Application([
(r"/", IndexHandler)
])
application.listen(8888)
tornado.ioloop.IOLoop.current().start()

Python tornado AsyncHttpClient does not send any request

Below is a snippet from the tornado documentation.
def handle_response(response):
if response.error:
print("Error: %s" % response.error)
else:
print(response.body)
http_client = AsyncHTTPClient()
http_client.fetch("http://www.google.com/", handle_response)
But this does not print anything to the console. I tried adding a time.sleep at the end but even then nothing prints.
Also, it does not send any request to my server when I change the url above to point to my server.
tornado.httpclient.HTTPClient works fine though.
I am on Macbook with Python 3.6.1.
Tornado is an asynchronous framework where all tasks are scheduled by a single event loop called the IOLoop. At the end of your program, put:
import tornado.ioloop
tornado.ioloop.IOLoop.current().start()
That will start the loop running and allow the AsyncHTTPClient to fetch the URL.
The IOLoop runs forever, so you need to implement some logic that determines when to call IOLoop.stop(). In your example program, call IOLoop.stop() at the bottom of handle_response. In a real HTTP client program, the loop should run until all work is complete and the program is ready to exit.

How to run the Tornado event loop alongside a Kivy GUI?

My client application uses a Kivy GUI (Kivy has its own event loop) and connects to the server using the WebSocket protocol with Tornado (Tornado also has an event loop). That's why the connection part is asynchronous.
I want the user to interact with the UI while a Tornado client is running an infinite asynchronous loop of listening for server messages.
Here's some example code:
client_test.py
from tornado.ioloop import IOLoop
from tornado.websocket import websocket_connect
class RequestSender:
url = 'server url here (no scheme)'
async def _connect(self):
self.conn = await websocket_connect('wss://' + self.url, io_loop=self.ioloop)
self.ioloop.add_callback(self._listen)
async def _listen(self):
while True:
print(await self.conn.read_message())
def __init__(self):
self.ioloop = IOLoop.current()
self.ioloop.add_callback(self._connect)
def run(self):
self.ioloop.start()
GUI
from kivy.app import App
from kivy.uix.label import Label
from client_test import RequestSender
class TestApp(App):
def build(self):
RequestSender().run()
return Label(text = "hello")
TestApp().run()
Apparently, since the Tornado's event loop has started earlier, it has taken over the program flow and now no GUI window appears.
I execute the GUI file and the execution hangs after the RequestSender().run(), so build never returns.
Searching on this case provided little to no information, except for this Google Groups post. Kivy's documentation only mentions Twisted.
I tried putting the Kivy event loop into slave mode and running GUI updates from Tornado's event loop, but that didn't work because apparently a call EventLoop.idle() of Kivy's event loop isn't enough to keep the GUI application running.
What else could be done here?
I found this question trying to do the same thing, and opted for two separate processes instead; one Kivy GUI and one Tornado (Server, in my case). I have the two communicate using multiprocessing.connection as explained well in this SO answer
If you have large and complex data to pass between the two, perhaps this is less than ideal, but for simple messages it works well. You also have the advantage of running without the UI, and running the UI on a separate machine.

Handling stdin with tornado

How to listen for events that happen on stdin in Tornado loop?
In particular, in a tornado-system, I want to read from stdin, react on it, and terminate if stdin closes. At the same time, the Tornado web service is running on the same process.
While looking for this, the most similar I could find was handling streams of an externally spawned process. However, this is not what I want: I want to handle i/o stream of the current process, i.e. the one that has the web server.
Structurally, my server is pretty much hello-world tornado, so we can base the example off that. I just need to add an stdin handler.
You can use the add_handler method on the IOLoop instance to watch for events on stdin.
Here's a minimal working example:
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler
import sys
class MainHandler(RequestHandler):
def get(self):
self.finish("foo")
application = Application([
(r"/", MainHandler),
])
def on_stdin(fd, events):
content = fd.readline()
print "received: %s" % content
if __name__ == "__main__":
application.listen(8888)
IOLoop.instance().add_handler(sys.stdin, on_stdin, IOLoop.READ)
IOLoop.instance().start()

Stopping a tornado application

Let's take the hello world application in the Tornado home page:
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
Is there a way, after the IOloop has been started and without stopping it, to essentially stop the application and start another one (on the same port or on another)?
I saw that I can add new application (listening on different ports) at runtime, but I do not know how I could stop existing ones.
Application.listen() method actually creates a HTTPServer and calls its listen() medthod. HTTPServer objects has stop() method which is probably what you need. But in order to do it you have to explicitly create HTTPServer object in your script.
server = HTTPServer(application)
server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
#somewhere in your code
server.stop()
Here is a gist about how to gracefully and safely shutdown the tornado ioloop.
https://gist.github.com/nicky-zs/6304878
However, you can refer to this implementation to achieve your goal.
To add to #Alex Shkop's answer a few years later, as of Tornado 4.3 .listen() returns a reference to its HTTPServer!
https://www.tornadoweb.org/en/stable/web.html#tornado.web.Application.listen
server = app.listen()
... # later
server.stop()
Further, if you're working in a Jupyter notebook and for some reason need a Tornado server, you can try to close the HTTPServer before you recreate it to avoid OSError: [Errno 98] Address already in use on re-running the cell
# some Jupyter cell
#
import tornado.web
try:
server.stop() # NameError on first cell run
except Exception as ex:
print(f"server not started to stop: {repr(ex)}")
else: # did not raise NameError: server was running
print(f"successfully stopped server: {server}")
app = tornado.web.Application(...)
server = app.listen(9006) # arbitrary listening port

Categories

Resources