4

I used Tornado to create a login page which works on synchronous method.Now I want to make it asynchronous. So what are the changes I should do to the following code:

import tornado.ioloop
import tornado.web
import http
import time

class BaseHandler(tornado.web.RequestHandler):
    def get_current_user(self):
        return self.get_secure_cookie("user")

class MainHandler(BaseHandler):
    def get(self):
        if not self.current_user:
            self.redirect("/login")
            return
        name = tornado.escape.xhtml_escape(self.current_user)
        self.write('<html>'
    '<head> '
    '<title>WELCOME  </title>'
    '</head>'
    '<body style="background:orange;"'

    '<div align="center">'
    '<div style="margin-top:200px;margin-bottom:10px;">'
    '<span style="width:500px;color:black;font-size:30px;font-weight:bold;">WELCOME </span>'
    '</div>'
    '<div style="margin-bottom:5px;">'"Hello, " + name)

class LoginHandler(BaseHandler):
    def get(self):
        self.write('<html><body><form action="/login" method="post">'
                   '<div><span style="width:100px;;height: 500px;color:black;font-size:60;font-weight:bold;">'
                   'LOGIN'
                   '<div><span style="width:100px;;height: 500px;color:purple;font-size:30;font-weight:bold;">'
                   'NAME <input type="text" name="name">'
                   '<div><span style="width:100px;height: 500px;color:purple;font-size:30;font-weight:bold;">PASSWORD</span><input style="width:150px;" type="password" name="password" id="password" value="">'
                   '</div>'                   
                   '<input type="submit" value="Sign in">'
                   '</form></body></html>')

    def post(self):
        self.set_secure_cookie("user", self.get_argument("name"))
        time.sleep(10)
        self.redirect("/")

application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/login", LoginHandler),
], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")

application.listen(5000)
tornado.ioloop.IOLoop.current().start()

In my code I have three classes BaseHandler, MainHandler and LoginHandler. Any help will be appreciated.

Akshay Kandul
  • 592
  • 4
  • 10
Erina
  • 81
  • 1
  • 2
  • 13

3 Answers3

2

Don't call "sleep" in a Tornado method:

http://www.tornadoweb.org/en/stable/faq.html

If you want to pause the method for a while to prove to yourself that it's non-blocking, add from tornado import gen and try:

async def post(self):
    self.set_secure_cookie("user", self.get_argument("name"))
    yield gen.sleep(10)
    self.redirect("/")

Or in Python 2:

@gen.coroutine
def post(self):
    self.set_secure_cookie("user", self.get_argument("name"))
    yield gen.sleep(10)
    self.redirect("/")
A. Jesse Jiryu Davis
  • 23,641
  • 4
  • 57
  • 70
  • I get the following error , when i try yield gen.sleep: raise BadYieldError("yielded unknown object %r" % (yielded,)) tornado.gen.BadYieldError: yielded unknown object ERROR:tornado.access:500 POST /login (::1) 8.00ms – Erina Jul 31 '17 at 12:36
  • Is there a way to make this code async. by changing the func. and by using '@return_future def future_func(arg1, arg2, callback): # Do stuff (possibly asynchronous) callback(result) @gen.engine def caller(callback): yield future_func(arg1, arg2) callback() – Erina Aug 07 '17 at 11:01
1

A simple example

Python 3.5 introduced the async and await keywords (functions using these keywords are also called “native coroutines”). See the answer for more details.

Every class-based view we construct for our Tornado app must inherit from the RequestHandler object found in tornado.web.

If we override the prepare method, we can set some logic to run that'll do whenever a request is received.

def tornado.web.RequestHandler.get_current_user(self) - Override to determine the current user from, e.g., a cookie.

A subclass may override get_current_user(), which will be called automatically the first time self.current_user is accessed. get_current_user() will only be called once per request, and is cached for future access.

class BaseHandler(tornado.web.RequestHandler):
    def prepare(self):
        if self.get_argument("btn1", None) is not None:
            print("detected click on btn Profile")
            self.redirect("/profile")
            return

        if self.get_argument("btn2", None) is not None:
            print("detected click on btn Sources")
            self.redirect("/sources")
            return

        if self.get_argument("logout", None) is not None:
            self.clear_cookie("username")
            self.redirect("/")
            return

        if self.get_argument("btnSignIn", None) is not None:
            print("detected click on btnSignIn")
            self.redirect("/signin")
            return

    def get_current_user(self):
        a = self.get_secure_cookie("username")
        if a:
            return a.decode("utf-8")
        return None
class LoginHandler(BaseHandler):
    def get(self):
        self.render('login.html')

    def prepare(self):
        super().prepare()

    async def post(self):
        username = self.get_argument("username")
        password = self.get_argument("password")

        my_db = self.settings['db']
        collection = my_db.test

        val = await do_check_one(collection, username, password)

        if val is not None:
            self.set_secure_cookie("username", username)
            self.redirect("/profile")
        else:
            self.write('<h1>Wrong credentials</h1>')

For a better understanding see: Simplest async/await example.

async def do_check_one(my_collection, value1, value2):
    document = await my_collection.find_one({"name": value1, "password": value2})
    return document

and main:

Options must be defined with tornado.options.define before use, generally at the top level of a module. The options are then accessible as attributes of tornado.options.options.

The main() method of your application does not need to be aware of all of the options used throughout your program; they are all automatically loaded when the modules are loaded. However, all modules that define options must have been imported before the command line is parsed.


define("port", default=8000, help="run on the given port", type=int)


if __name__ == '__main__':
    tornado.options.parse_command_line()

    client = motor.motor_tornado.MotorClient('localhost', 27017)
    db = client.test
    collection = db.test

    settings = {
        "template_path": os.path.join(os.path.dirname(__file__), "templates"),
        "static_path": os.path.join(os.path.dirname(__file__), "static"),
        "cookie_secret": "_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
        "login_url": "/login",
        "db": db,
        "debug": True,
        "xsrf_cookies": True
    }

    app = tornado.web.Application(
        handlers=[(r'/', MainHandler),
                  (r'/sources', SourceHandler),
                  (r'/login', LoginHandler),
                  (r'/signin', SigninHandler),
                  (r'/profile', ProfileHandler),
                  (r'/favicon.ico', tornado.web.StaticFileHandler, dict(path=settings['static_path'])),
                  ],
        **settings
    )
    http_server = tornado.httpserver.HTTPServer(app)
    http_server.listen(options.port)
    try:
        tornado.ioloop.IOLoop.instance().start()
        print('Server started...')
    except KeyboardInterrupt:
        print('Server has shut down.')

Your main() method can parse the command line or parse a config file with either parse_command_line or parse_config_file:

tornado.options.parse_command_line()
# or
tornado.options.parse_config_file("/etc/server.conf")
Milovan Tomašević
  • 6,823
  • 1
  • 50
  • 42
0

Using async/await notation here, possibly adapt to fit your Python version:

class BaseHandler(tornado.web.RequestHandler):
    async def get_current_user(self):
        return await some_async_operation()

    async def prepare(self):
        self.current_user = await self.get_current_user()

Tornado won't call get_current_user asynchronously out of the box, but it will honour asynchronous prepare methods, so you can populate self.current_user there.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • Thank you for helping.But I get an error :500 when I execute the code. – Erina Jul 31 '17 at 09:14
  • You'll have to check your error logs/activate debugging here, 500 could be a lot of things. – deceze Jul 31 '17 at 09:15
  • oh ok, So the above mentioned changes should ideally make the code work asynchronously.Is that right? – Erina Jul 31 '17 at 09:26
  • Yes. In your code within `def get` etc. you just use `self.current_user`; the only place asynchronicity is relevant is how `self.current_user` is being populated, and that simply happens in `prepare` (overriding the default Tornado implementation of how `self.current_user` is populated). There *may* of course be some error being triggered here, but you need to investigate that in detail. – deceze Jul 31 '17 at 09:29
  • Isn't it necessary to add the keyword async before every def function? – Erina Jul 31 '17 at 09:37
  • *If* you're doing any `await` within it, yes. – deceze Jul 31 '17 at 09:39
  • Then I'm not sure what your question is, or it has been answered…? – deceze Jul 31 '17 at 09:44
  • My question is that my code works synchronously, but to make it asynchronous ,what changes should be done in the code ? – Erina Jul 31 '17 at 09:46
  • What I'm showing above is how you can make the resolution of `current_user` asynchronous; I was under the impression that was the stumbling block. If that's not all you'll need to clarify what exactly it is you want to make asynchronous. – deceze Jul 31 '17 at 09:52
  • That is exactly what I want;to make the current_user asynchronous.Thank you for your help.Let me now figure out the reason for Error:500 – Erina Jul 31 '17 at 09:56
  • Run Tornado in debug mode to get a stack trace. http://www.tornadoweb.org/en/stable/guide/running.html#debug-mode-and-automatic-reloading – deceze Jul 31 '17 at 09:56
  • Well, obviously you're supposed to substitute *your* code there to determine the current user. – deceze Jul 31 '17 at 11:14