So I'm learning the Tornado web framework right now, and following a few examples from a book I managed to get authentication with OAuth2/Google almost working. It redirects to Google to prompt a user to sign in, but after signing in it throws the following error at me:
Traceback (most recent call last):
File "C:\python27\lib\site-packages\tornado\web.py", line 1288, in _stack_context_handle_exception
raise_exc_info((type, value, traceback))
File "C:\python27\lib\site-packages\tornado\web.py", line 1475, in wrapper
result = method(self, *args, **kwargs)
File "C:\Users\enricojr\Downloads\Github\LearningTornado\grumble_login.py", line 8, in get
self.get_authenticated_user(callback=self.async_callback(self._on_auth))
AttributeError: 'LoginHandler' object has no attribute 'async_callback'
Upon checking the Tornado source code on Github, it does appear indeed that the async_callback() method is gone. Any idea on how to handle this? Every example of authentication using Tornado's auth module I've seen thus far calls for it to be used, and since I'm new to the whole 'asynchronous web programming' thing I'm not quite sure at this point how else I can do it.
My code is below, Python 2.7 and Tornado 4.0.2:
############################
# grumble_login.py
############################
from tornado.auth import GoogleMixin
from tornado.web import RequestHandler, asynchronous
class LoginHandler(RequestHandler, GoogleMixin):
#asynchronous
def get(self):
if self.get_argument("openid.mode", None):
self.get_authenticated_user(callback=self.async_callback(self._on_auth))
return
else:
self.authenticate_redirect()
def _on_auth(self, user):
if not user:
self.clear_all_cookies()
raise tornado.web.HTTPError(500, "Google auth failed.")
else:
# the user id serves as the basis for
# all the data we store.
self.set_secure_cookie('user_id', user['id'])
self.redirect("/")
class LogoutHandler(RequestHandler):
def get(self):
self.clear_all_cookies()
self.render("logout.html")
############################
# main.py
############################
import os
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application
from tornado.options import define, options
from grumble_handlers import *
from grumble_user_handlers import *
from grumble_login import *
# /user/profile/<user> - leads to profile page
# /user/posts - leads to posts made by user
# /post/<id> - leads to a specific post
# /post/add - add post
# /post/edit - edit post
# /post/delete - delete post
# D$oP5lz3D$oP5lz3
TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), "templates")
STATIC_PATH = os.path.join(os.path.dirname(__file__), "static")
if __name__ == "__main__":
define("port", default=8000, help="run on the given port", type=int)
settings_dict = {
'cookie_secret': "GENERATE NEW KEY HERE",
'template_path': TEMPLATE_PATH,
'static_path': STATIC_PATH,
'debug': True,
'login_url': "/login",
}
general_handlers = [
(r"/", IndexHandler),
(r"/login", LoginHandler),
(r"/logout", LogoutHandler)
]
post_handler_list =[
(r"/post/(\d+)", PostFetchHandler), # leads to a single post page
(r"/post/add", PostAddHandler),
(r"/post/edit", PostEditHandler),
(r"/post/delete", PostDeleteHandler),
]
user_handler_list = [
(r"/user/(\d+)/posts", UserPostsHandler), # lists out all the posts by a given user
(r"/user/(\d+)/profile", UserProfileHandler),
]
master_handler_list = general_handlers + post_handler_list + user_handler_list
app = Application(
handlers=master_handler_list,
**settings_dict
)
http_server = HTTPServer(app)
http_server.listen(options.port)
# GO TEAM GO
IOLoop.instance().start()
############################
# grumble_handlers.py
############################
...
class IndexHandler(NotImplementedHandler):
#authenticated
def get(self):
pass
The book is, unfortunately, long outdated. async_callback was removed in Tornado 4 since it is no longer required. You can simply do:
self.get_authenticated_user(callback=self._on_auth)
Once you have that working, I recommend following the coroutine documents and learn how to use "yield":
user = yield self.get_authenticated_user()
Related
This is a Python code for a Tornado web application. The code defines two classes, BaseHandler and MainHandlerIndex, both of which inherit from the Tornado web library's RequestHandler. The BaseHandler class has a method, "get_username_id_get_data", which returns the username ID obtained from a "get_data" module. The MainHandlerIndex class has a "get" method decorated with the "#decoradores.authenticated" and "#gen.coroutine" decorators, which handles incoming GET requests. This method retrieves the username cookie, obtains the username ID using the "get_username_id_get_data" method, and then renders an HTML template "index/index.html" with the title, items, username, and version as variables.
import tornado.web
from tornado import gen
from functools import wraps
import modulos.decoradores as decoradores
import modulos.get_data as get_data
import logging
logging.basicConfig(level=logging.DEBUG)
import tracemalloc; tracemalloc.start()
class BaseHandler(tornado.web.RequestHandler):
async def get_username_id_get_data(self):
print("soy basehandler y estoy siendo ejecutado....")
username_id = await get_data.primero_redis_segundo_query("user", "('username', '=', 'test')", 'x')
print("get_username_id_get_data: ", username_id)
return username_id
class MainHandlerIndex(BaseHandler):
#decoradores.authenticated
#gen.coroutine
async def get(self):
print("Iniciando MainHandlerIndex metodo: GET")
username_cookie_pre = self.get_secure_cookie("user")
username_cookie = username_cookie_pre.decode()
#username_cookie = 'test'
username_id = await self.get_username_id_get_data()
version = self.application.settings['valores_entorno']['version']
print("###########################################################################")
print("00_username_cookie------------>",username_cookie )
print("11_username_id------------>",username_id )
print("22_version------------>",version )
print("###########################################################################")
items_test = ["Item 1_index", "Item 2_index", "Item 3_index"]
#items_test = []
self.render("index/index.html", title="test", items=items_test, username=str(username_cookie).strip("\""), version=version)
This is the contents of the file index.py:
import asyncio
import os
import tornado.web
#----------------------------#
# Definir entorno #
#----------------------------#
from config import Settings, PreProductionSettings, ProductionSettings
import tornado.options
tornado.options.parse_config_file('config.py')
valores_entorno = PreProductionSettings()
#print(tornado.options.options.version)
#----------------------------#
# Handlers from modules #
#----------------------------#
from blueprints.test.handlers import MainHandlerTest #TEST <-- Borrar....from blueprints.index.handlers import MainHandlerIndex #Index
from blueprints.rid_propios.ri_handlers import MainHandlerRid_Propio_Index #Remote_id_propios
from blueprints.login.login_handlers import MainHandlerLogin, LogoutHandler, RegisterHandler, ConfirmeHandler
from blueprints.index.handlers import MainHandlerIndex
#----------------------------#
# Módulos del proyecto #
#----------------------------#
import modulos.gestiondb as gestiondb
import modulos.redis_stack as redisstack
import modulos.tablas as Tablas
from aiopg.sa import create_engine
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
print("BASE_DIR: ", BASE_DIR)
engine = create_engine(
user=tornado.options.options.user,
database=tornado.options.options.database,
host=tornado.options.options.host,
password=tornado.options.options.password,
)
def make_app():
settings = {
"template_path": os.path.join(os.path.dirname(__file__), "templates"), #/home/developer/xrig_torado/web/web/blueprints/base/templates
"blueprints_path": os.path.join(os.path.dirname(__file__), "web/blueprints"), #/home/developer/xrig_torado/web/web/blueprints
"static_path": os.path.join(os.path.dirname(__file__), "static"), #/home/developer/xrig_torado/web/static
"cookie_secret": tornado.options.options.cookie_secret,
"login_url": "/login",
"xsrf_cookies": True,
"valores_entorno": tornado.options.options
}
handlers = (
(r'/', MainHandlerIndex),
(r'/login', MainHandlerLogin),
(r'/logout', LogoutHandler),
(r'/register', RegisterHandler),
(r'/confirmar', ConfirmeHandler),
(r'/test', MainHandlerTest),
(r'/rid_propios_index', MainHandlerRid_Propio_Index)
)
return tornado.web.Application(
handlers,
debug = True,
autoreload = True,
**settings,
#db = db
db_engine = engine,
)
async def main():
app = make_app()
app.listen(tornado.options.options.puerto_xrig)
#await asyncio.Event().wait()
await asyncio.Event().wait()
print(f'🌐 Server is listening on localhost on port {tornado.options.options.puerto_xrig}')
#Comprobando que existen la tablas del proyecto:
db_dsn = tornado.options.options.dsn
#lista_tablas = [gestiondb.tbl_benchmark, gestiondb.alpha, gestiondb.user, ]
lista_tablas = [Tablas.tbl_benchmark, Tablas.alpha, Tablas.user, Tablas.rid, Tablas.rid_propio ]
await gestiondb.rutina_alta_db_proyecto(db_dsn, lista_tablas)
#Comprobaciones Redis Stack.
await redisstack.lista_tablas_para_volcar_en_redis()
shutdown_event = asyncio.Event()
await shutdown_event.wait()
if __name__ == "__main__":
asyncio.run(main())
The extension starts correctly, does the login process and once the authenticated user is when I make a redirect to the root path / (this is where I can not operate a code that can call asynchronous functions). I have tried to follow several examples and there is no way the error is always:
http://http://192.168.196.49:8888/ (depués del proceso login)
[I 230205 19:48:57 web:2271] 200 GET / (192.168.196.4) 14.50ms
/usr/lib/python3.9/asyncio/events.py:80: RuntimeWarning: coroutine 'MainHandlerIndex.get' was never awaited
self._context.run(self._callback, *self._args)
Object allocated at (most recent call last):
File "/home/developer/xrig_torado/lib/python3.9/site-packages/tornado/gen.py", lineno 216
result = ctx_run(func, *args, **kwargs)
[W 230205 19:48:57 web:2271] 404 GET /favicon.ico (192.168.196.4) 7.46ms
I do NOT find enough documentation that explains an architecture to do this kind of thing. The intention is in the requestHandler in the GET or POST method to develop code and be able to call asynchronous functions.
I am currently trying to use tornado to display my twitter streams. Below is my code:
#!/usr/bin/env python
import time
import logging
from tornado.auth import TwitterMixin
from tornado.escape import json_decode, json_encode
from tornado.ioloop import IOLoop
from tornado import gen
from tornado.options import define, options, parse_command_line, parse_config_file
from tornado.web import Application, RequestHandler, authenticated, HTTPError
define('port', default=8080, help="port to listen on")
define('config_file', default='secrets.cfg',
help='filename for additional configuration')
define('debug', default=True, group='application',
help="run in debug mode (with automatic reloading)")
# The following settings should probably be defined in secrets.cfg
define('twitter_consumer_key', type=str, group='application')
define('twitter_consumer_secret', type=str, group='application')
define('cookie_secret', type=str, group='application',
default='this is a string',
help="signing key for secure cookies")
class BaseHandler(RequestHandler):
COOKIE_NAME = "uuser"
def get_current_user(self):
user_json = self.get_secure_cookie(self.COOKIE_NAME)
if not user_json:
print(" No user_json")
return None
print(" Yes user_json")
return json_decode(user_json)
class MainHandler(BaseHandler, TwitterMixin):
#authenticated
#gen.coroutine
def get(self):
timeline = yield self.twitter_request(
'/statuses/home_timeline',
access_token = self.current_user['access_token'])
self.render('home.html', timeline=timeline)
class LoginHandler(BaseHandler, TwitterMixin):
#gen.coroutine
def get(self):
if self.get_argument('oauth_token', None):
user = yield self.get_authenticated_user()
print(' user:', type(user))
del user["description"]
self.set_secure_cookie(self.COOKIE_NAME, json_encode(user))
print(' get_secure_cookie:', self.get_secure_cookie(self.COOKIE_NAME) )
self.redirect(self.get_argument('next', '/'))
else:
print(" Authorize_redirecting...")
yield self.authorize_redirect(callback_uri=self.request.full_url())
class LogoutHandler(BaseHandler):
def get(self):
self.clear_cookie("user")
def main():
parse_command_line(final=False)
parse_config_file(options.config_file)
app = Application(
[
(r'/', MainHandler),
(r'/login', LoginHandler),
(r'/logout', LogoutHandler),
],
login_url='/login',
**options.group_dict('application'))
app.listen(options.port)
logging.info('Listening on http://localhost:%d' % options.port)
IOLoop.current().start()
if __name__ == '__main__':
main()
So my understanding of the flow is as follows:
1.) Visit '/' - MainHandler, the #authenticated will redirect to login_url if the user is not logged in.
2.) Visit '/login' - LoginHandler, self.authorize_redirect(callback_uri=self.request.full_url()) will append oauth_token argument at the end of url, and re-visit '/login'
3.) Visit'/login' - LoginHandler, obtain user from self.get_authenticated_user(), and set_secure_cookie(self.COOKIE_NAME, json_encode(user))
And here is the problem I think, I can't seem to set the cookie. When I try to access it immediately by self.get_secure_cookie(self.COOKIE_NAME), it returns None, and hence it keeps on re-visiting '/login'
Can anybody offer some help to my problem? Maybe it is something very obvious I am not seeing. Thanks
I have also set http://127.0.0.1:8080/ as the callback url on my twitter app setting, not sure if this has any contribution to the problem.
Final solution!!
#!/usr/bin/env python
import time
import uuid
import logging
from tornado.auth import TwitterMixin
from tornado.escape import json_decode, json_encode, url_escape, url_unescape
from tornado.ioloop import IOLoop
from tornado import gen
from tornado.options import define, options, parse_command_line, parse_config_file
from tornado.web import Application, RequestHandler, authenticated, HTTPError
from urllib.parse import quote
import re
define('port', default=8080, help="port to listen on")
define('config_file', default='secrets.cfg',
help='filename for additional configuration')
define('debug', default=True, group='application',
help="run in debug mode (with automatic reloading)")
# The following settings should probably be defined in secrets.cfg
define('twitter_consumer_key', type=str, group='application')
define('twitter_consumer_secret', type=str, group='application')
# define('cookie_secret', type=str, group='application',
# default='thisisastring',
# help="signing key for secure cookies")
class BaseHandler(RequestHandler):
COOKIE_NAME = "user"
def get_current_user(self):
user_json = self.get_cookie(self.COOKIE_NAME)
if not user_json:
print("\n - Cannot obtain cookie from client browser")
return None
print("\n - Cookie obtained from client browser")
return json_decode(user_json)
class MainHandler(BaseHandler, TwitterMixin):
#authenticated
#gen.coroutine
def get(self):
print("\n - Obtaining timeline from twitter")
timeline = yield self.twitter_request(
'/statuses/home_timeline',
access_token = self.current_user)
self.render('home.html', timeline=timeline)
class LoginHandler(BaseHandler, TwitterMixin):
#gen.coroutine
def get(self):
if self.get_argument('oauth_token', None):
print("\n - Authenticating with oauth_token...")
user = yield self.get_authenticated_user()
encoded_token = json_encode(user['access_token'])
# remove certain ascii symbols which are rejected
# by self.set_cookie() function...
encoded_token = re.sub(r"[\x00-\x20]", '', encoded_token)
# save encoded token as cookie
self.set_cookie(name=self.COOKIE_NAME, value=encoded_token)
self.redirect(self.get_argument('next', '/'))
else:
print("\n - Authorize_redirecting...")
yield self.authorize_redirect(callback_uri=self.request.full_url())
class LogoutHandler(BaseHandler):
def get(self):
self.clear_cookie(self.COOKIE_NAME)
def main():
parse_command_line(final=False)
parse_config_file(options.config_file)
app = Application(
[
(r'/', MainHandler),
(r'/login', LoginHandler),
(r'/logout', LogoutHandler),
],
login_url='/login',
cookie_secret=str(uuid.uuid4().bytes),
**options.group_dict('application'))
app.listen(options.port)
logging.info('Listening on http://localhost:%d' % options.port)
IOLoop.current().start()
if __name__ == '__main__':
main()
I found this examples to upload files to a server using Tornado Python Web framework but the thing is that none of the examples have error handling developed. If I submit the form with no file attached, it returns a 500 error. The idea is to set up the upload file field as optional not mandatory.
https://github.com/vamsiikrishna/tornado-upload
http://technobeans.wordpress.com/2012/09/17/tornado-file-uploads/
Could you please give me a hand?
I guess this may have an easy solution but I am quite a newbie.
Thanks in advance!
Googleing I found this solution:
import tornado.ioloop
import tornado.web
UPLOAD_FILE_PATH = '/path/to/files/'
class MainHandler(tornado.web.RequestHandler):
def get(self):
args = dict(username = 'visitor')
self.render('home.html', **args)
class UploadHandler(tornado.web.RequestHandler):
def post(self):
username = self.get_argument('username', 'anonymous')
if self.request.files.get('uploadfile', None):
uploadFile = self.request.files['uploadfile'][0]
filename = uploadFile['filename']
fileObj = open(UPLOAD_FILE_PATH+username+filename, 'wb')
fileObj.write(uploadFile['body'])
self.redirect('/')
application=tornado.web.Application([(r'/',MainHandler),('/upload', UploadHandler) ],
template_path = 'templates',
debug = True
)
if __name__=='__main__':
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
The think is the following condition placed before file request: if self.request.files.get('uploadfile', None):
I'm trying to set it up so that my Pyramid app logs a message whenever it does a redirect - e.g. When one of my views raises HTTPFound.
Creating a custom subclass of HTTPFound worked but it sucks to have to make sure that class is used everywhere in the app.
I then had the idea of creating a custom exception view with context=HTTPFound but that view doesn't seem to get called.
Is there a standard way to set up special handling for a redirect that is global to the app?
To log out redirects you would just have a tween that checks the return status, something like:
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.httpexceptions import HTTPFound
def hello1(request):
raise HTTPFound(request.route_url('hello2'))
def hello2(request):
return {'boom': 'shaka'}
def redirect_logger(handler, registry):
def redirect_handler(request):
response = handler(request)
if response.status_int == 302:
print("OMGZ ITS NOT GOING WHERE YOU THINK")
return response
return redirect_handler
def main():
config = Configurator()
config.add_route('hello1', '/', view=hello1)
config.add_route('hello2', '/hi', view=hello2, renderer='json')
config.add_tween('__main__.redirect_logger')
app = config.make_wsgi_app()
return app
if __name__ == '__main__':
app = main()
server = make_server('0.0.0.0', 8080, app)
print("http://0.0.0.0:8080")
server.serve_forever()
I get this ugly "Internal Server Error" when I should be getting a neat 'page not found' page when an internal Pylons:python error occurs.
I have simulated a Python error elsewhere & the correct pagenotfound template is generated. In my config (development.ini): debug = false, full_stack = true ... so as to render this page when an exception occurs.
This is how the middleware.py looks currently:
"""Pylons middleware initialization"""
from beaker.middleware import CacheMiddleware, SessionMiddleware
from paste.cascade import Cascade
from paste.registry import RegistryManager
from paste.urlparser import StaticURLParser
from paste.deploy.converters import asbool
from pylons import config
from pylons.middleware import ErrorHandler, StatusCodeRedirect
from observer import Observer
from pylons.wsgiapp import PylonsApp
from routes.middleware import RoutesMiddleware
from r4l.lib.components.middleware.striptrailingslash import StripTrailingSlash
from r4l.config.environment import load_environment
def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
"""
Create a Pylons WSGI application and return it
``global_conf``
The inherited configuration for this application. Normally from
the [DEFAULT] section of the Paste ini file.
``full_stack``
Whether this application provides a full WSGI stack (by default,
meaning it handles its own exceptions and errors). Disable
full_stack when this application is "managed" by another WSGI
middleware.
``static_files``
Whether this application serves its own static files; disable
when another web server is responsible for serving them.
``app_conf``
The application's local configuration. Normally specified in
the [app:<name>] section of the Paste ini file (where <name>
defaults to main).
"""
# Configure the Pylons environment
load_environment(global_conf, app_conf)
# The Pylons WSGI app
app = PylonsApp()
# Routing/Session/Cache Middleware
app = RoutesMiddleware(app, config['routes.map'])
app = SessionMiddleware(app, config)
app = CacheMiddleware(app, config)
# CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
if asbool(full_stack):
# Handle Python exceptions
app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
app = Observer(app)
# Display error documents for 401, 403, 404 status codes (and
# 500 when debug is disabled)
if asbool(config['debug']):
app = StatusCodeRedirect(app, [400, 401, 403, 404], '/content/pagenotfound')
else:
app = StatusCodeRedirect(app, [400, 401, 403, 404, 500], '/content/pagenotfound')
# Establish the Registry for this application
app = RegistryManager(app)
if asbool(static_files):
# Serve static files
static_app = StaticURLParser(config['pylons.paths']['static_files'])
app = Cascade([static_app, app])
app = StripTrailingSlash(app)
return app
Also Observer.py class
from pylons.util import call_wsgi_application
from webob import Request, Response
from weberror import formatter, collector
class Observer(object):
""" Observer object to monitor and extract the traceback for 'pagenotfound' emails """
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
status, headers, app_iter, exc_info = call_wsgi_application(
self.app, environ, catch_exc_info=True)
if exc_info is not None:
exc_data = collector.collect_exception(*exc_info)
err_report = formatter.format_text(exc_data, show_hidden_frames=True)[0]
environ['err_report'] = err_report
start_response(status, headers, exc_info)
return app_iter
this is the Error.py
import cgi
from paste.urlparser import PkgResourcesParser
from pylons import request
from pylons.controllers.util import forward
from pylons.middleware import error_document_template
from webhelpers.html.builder import literal
from r4l.lib.base import BaseController
from r4l.lib import helpers as h
class ErrorController(BaseController):
"""Generates error documents as and when they are required.
The ErrorDocuments middleware forwards to ErrorController when error
related status codes are returned from the application.
This behaviour can be altered by changing the parameters to the
ErrorDocuments middleware in your config/middleware.py file.
"""
def document(self):
"""Render the error document"""
resp = request.environ.get('pylons.original_response')
if resp.status_int == 404:
h.redirect_to('/content/pagenotfound')
content = literal(resp.body) or cgi.escape(request.GET.get('message', ''))
page = error_document_template % \
dict(prefix=request.environ.get('SCRIPT_NAME', ''),
code=cgi.escape(request.GET.get('code', str(resp.status_int))),
message=content)
return page
def img(self, id):
"""Serve Pylons' stock images"""
return self._serve_file('/'.join(['media/img', id]))
def style(self, id):
"""Serve Pylons' stock stylesheets"""
return self._serve_file('/'.join(['media/style', id]))
def _serve_file(self, path):
"""Call Paste's FileApp (a WSGI application) to serve the file
at the specified path
"""
request.environ['PATH_INFO'] = '/%s' % path
return forward(PkgResourcesParser('pylons', 'pylons'))
And finally the controller for pagenotfound func
#h.template(u'content/404')
def pagenotfound(self):
"""
Default pagenotfound page
"""
c.err_report = str(request.environ.get('pylons.original_response','')) + '\n\n'
if 'err_report' in request.environ:
c.err_report += request.environ.get('err_report','')
log.info("###### c.err_report: %s" % c.err_report)
c.page_title = u'Page Not Found'
c.previous_url = request.environ.get('HTTP_REFERER', h.url_for('/'))
pagenotfound = True
session['pagenotfound'] = pagenotfound
session.save()
if c.login:
user = Session.query(User).get(session[u'username'])
if user:
c.login.email = user.contactemail.value
This is the exception that is not being caught:
File '/home/chilton/work/cj2/CJ-7519/r4l/templates/resume/preview.html', line 441 in
${h.cj_month_year(employment.startdate)} - Current${employment.enddate.strftime('%m/%Y')}
ValueError: year=200 is before 1900; the datetime strftime() methods require year >= 1900
I've tried looking at different ways of solving this one, but without success.