Commits

Pierre-Marie de Rodat committed c5310e5

prologin.web: add implementation and tests for Tornado applications

Comments (0)

Files changed (2)

python-lib/prologin/tests/web_test.py

 import multiprocessing
 import requests
 import time
+import tornado.ioloop
+import tornado.web
 import unittest
 import wsgiref.simple_server
 
 def test_ping_handler():
     """/__ping handler code returns pong"""
-    headers, text = prologin.web.ping_handler()
+    status_code, reason, headers, text = prologin.web.ping_handler()
     assert text == "pong"
 
 def test_threads_handler():
     """/__threads handler code returns threads info"""
-    headers, text = prologin.web.threads_handler()
+    status_code, reason, headers, text = prologin.web.threads_handler()
     assert ' threads found' in text
 
 class WebAppTest:
         def application(environ, start_response):
             start_response('200 OK', [])
             return [b'Normal output']
-        application = prologin.web.WsgiApp(application, 'test-app')
+        application = prologin.web.WsgiApp(application, 'test-wsgi-app')
         server = wsgiref.simple_server.make_server('127.0.0.1', 42543,
                                                    application)
         return server.serve_forever
     @classmethod
     def expected_normal_output(cls):
         return 'Normal output'
+
+class TornadoAppTest(unittest.TestCase, WebAppTest):
+    @classmethod
+    def setUpClass(cls):
+        WebAppTest.setup_web_server(cls)
+
+    @classmethod
+    def tearDownClass(cls):
+        WebAppTest.tear_down_web_server(cls)
+
+    @classmethod
+    def make_web_server(cls):
+        class TestHandler(tornado.web.RequestHandler):
+            def get(self):
+                self.set_status(200)
+                self.write(b'Normal output')
+        application = prologin.web.TornadoApp([
+            ('/', TestHandler)
+        ], 'test-tornado-app')
+        application.listen(42544)
+        return tornado.ioloop.IOLoop.instance().start
+
+    @classmethod
+    def get_server_url(cls):
+        return 'http://localhost:42544'
+
+    @classmethod
+    def expect_normal_output(cls):
+        return 'Normal output'

python-lib/prologin/web.py

     class MyRequestHandler(prologin.web.ProloginBaseHTTPRequestHandler):
         ...
 
-If your application is an RPC server, this is handled automatically.
-
 This initializes logging using prologin.log, and maps some special URLs to
 useful pages:
   * /__ping
 """
 
 import sys
+import tornado.web
 import traceback
 
+
+def exceptions_catched(func):
+    """Decorator for function handlers: return a HTTP 500 error when an
+    exception is raised.
+    """
+    def wrapper():
+        try:
+            return (200, 'OK') + func()
+        except Exception:
+            return (
+                500, 'Error',
+                {'Content-Type': 'text/html'},
+                '<h1>Onoes, internal server error</h1>'
+            )
+    return wrapper
+
+@exceptions_catched
 def ping_handler():
     return { 'Content-Type': 'text/plain' }, "pong"
 
+@exceptions_catched
 def threads_handler():
     frames = sys._current_frames()
     text = ['%d threads found\n\n' % len(frames)]
         return self.app(environ, start_response)
 
     def call_handler(self, environ, start_response, handler):
-        try:
-            headers, text = handler()
-            start_response('200 OK', list(headers.items()))
-            return [text.encode('utf-8')]
-        except Exception:
-            start_response('500 Error', [('Content-Type', 'text/html')])
-            return [b'<h1>Onoes, internal server error.</h1>']
+        status_code, reason, headers, content = handler()
+        start_response(
+            '{} {}'.format(status_code, reason),
+            list(headers.items())
+        )
+        return [content.encode('utf-8')]
+
+
+class TornadoApp(tornado.web.Application):
+    def __init__(self, handlers, app_name):
+        # Prepend special handlers, taking care of the Tornado interfacing.
+        handlers = (
+            tuple(
+                (path, self.get_special_handler(handler))
+                for path, handler in HANDLED_URLS.items()
+            )
+            + tuple(handlers)
+        )
+        super(TornadoApp, self).__init__(handlers)
+        self.app_name = app_name
+
+        # TODO(delroth): initialize logging
+
+    def get_special_handler(self, handler_func):
+        """Wrap a special handler into a Tornado-compatible handler class."""
+
+        class SpecialHandler(tornado.web.RequestHandler):
+            """Wrapper handler for special resources.
+            """
+            def get(self):
+                status_code, reason, headers, content = handler_func()
+                self.set_status(status_code, reason)
+                for name, value in headers.items():
+                    self.set_header(name, value)
+                self.write(content.encode('utf-8'))
+
+        return SpecialHandler