Async middleware in tornado - python

My target is to validate the access token before each websocket request initialize. To do that I need to make a call to my oauth server. So attempted to add a middleware which will check the access token. I found how to add middleware in this link https://github.com/tornadoweb/tornado/issues/49 which works fine. But the problem is when I am making the call to my oauth server I am doing it asynchronous and it seems like middleware can't be a async. Here is my sample code
app = Application()
async def middleware(request):
userToken = request.headers.get('Authorization')
active = await check_accesstoken(userToken)
if not active:
return error
app(request)
async def check_accesstoken(userToken):
http_client = httpclient.AsyncHTTPClient()
post_data = {'token': userToken, 'scope': 'email phone'}
api_endpoint = 'https://192.168.0.4:1445/oauth2/introspect'
json_data = json.dumps(post_data)
response = await http_client.fetch(api_endpoint,
raise_error=False,
method='POST',
body=json_data
# headers=headers
)
return response.body.active:
def main():
http_server = tornado.httpserver.HTTPServer(middleware)
http_server.listen(PORT, HOST)
tornado.ioloop.IOLoop.current().start()
if __name__ == '__main__':
main()
Getting following error.
RuntimeWarning: coroutine 'middleware' was never awaited
self.request_callback(self.request)
Questions
How to add a aync middleware?
Or should I make a synchronous call to oauth server?
Or is there any other place where I should check the access token?

Yeah, it's more trouble than it's worth to override HTTPServer.
A saner solution that comes to mind is that you can subclass RequestHandler and create a prepare() method on that subclass where you can check the access token.
Then create all your handlers from that subclass. Here's an example:
class BaseHandler(web.RequestHandler):
async def prepare(self):
active = await check_accesstoken(userToken)
if not active:
self.write("Error")
self.finish()
class SomeHandler(BaseHandler):
...
And if you need to create a prepare() method on your handlers as well, just call the BaseHandler's prepare using super().

Related

Tornado 6.1 non-blocking request

Using Tornado, I have a POST request that takes a long time as it makes many requests to another API service and processes the data. This can take minutes to fully complete. I don't want this to block the entire web server from responding to other requests, which it currently does.
I looked at multiple threads here on SO, but they are often 8 years old and the code does not work anylonger as tornado removed the "engine" component from tornado.gen.
Is there an easy way to kick off this long get call and not have it block the entire web server in the process? Is there anything I can put in the code to say.. "submit the POST response and work on this one function without blocking any concurrent server requests from getting an immediate response"?
Example:
main.py
def make_app():
return tornado.web.Application([
(r"/v1", MainHandler),
(r"/v1/addfile", AddHandler, dict(folderpaths = folderpaths)),
(r"/v1/getfiles", GetHandler, dict(folderpaths = folderpaths)),
(r"/v1/getfile", GetFileHandler, dict(folderpaths = folderpaths)),
])
if __name__ == "__main__":
app = make_app()
sockets = tornado.netutil.bind_sockets(8888)
tornado.process.fork_processes(0)
tornado.process.task_id()
server = tornado.httpserver.HTTPServer(app)
server.add_sockets(sockets)
tornado.ioloop.IOLoop.current().start()
addHandler.py
class AddHandler(tornado.web.RequestHandler):
def initialize(self, folderpaths):
self.folderpaths = folderpaths
def blockingFunction(self):
time.sleep(320)
post("AWAKE")
def post(self):
user = self.get_argument('user')
folderpath = self.get_argument('inpath')
outpath = self.get_argument('outpath')
workflow_value = self.get_argument('workflow')
status_code, status_text = validateInFolder(folderpath)
if (status_code == 200):
logging.info("Status Code 200")
result = self.folderpaths.add_file(user, folderpath, outpath, workflow_value)
self.write(result)
self.finish()
#At this point the path is validated.
#POST response should be send out. Internal process should continue, new
#requests should not be blocked
self.blockingFunction()
Idea is that if input-parameters are validated the POST response should be sent out.
Then internal process (blockingFunction()) should be started, that should not block the Tornado Server from processing another API POST request.
I tried defining the (blockingFunction()) as async, which allows me to process multiple concurrent user requests - however there was a warning about missing "await" with async method.
Any help welcome. Thank you
class AddHandler(tornado.web.RequestHandler):
def initialize(self, folderpaths):
self.folderpaths = folderpaths
def blockingFunction(self):
time.sleep(320)
post("AWAKE")
async def post(self):
user = self.get_argument('user')
folderpath = self.get_argument('inpath')
outpath = self.get_argument('outpath')
workflow_value = self.get_argument('workflow')
status_code, status_text = validateInFolder(folderpath)
if (status_code == 200):
logging.info("Status Code 200")
result = self.folderpaths.add_file(user, folderpath, outpath, workflow_value)
self.write(result)
self.finish()
#At this point the path is validated.
#POST response should be send out. Internal process should continue, new
#requests should not be blocked
await loop.run_in_executor(None, self.blockingFunction)
#if this had multiple parameters it would be
#await loop.run_in_executor(None, self.blockingFunction, param1, param2)
Thank you #xyres
Further read: https://www.tornadoweb.org/en/stable/faq.html

Python with Tornado: How to get fetch response in async functions?

I'm trying to create a Python script which running Tornado Async http client with fetch and trying to get the response and print the response.body to the screen.
my class code is:
class MyAsyncHTTPClient(httpclient.AsyncHTTPClient):
#gen.coroutine
def _fetch(self, url):
print('send Asynchronous request ...['+url+"] ")
http_response = yield gen.Task(self.fetch, url)
print(http_response.body)
print('got Asynchronous response !!'+str(http_response))
raise gen.Return(http_response.body)
and I'm calling it this way:
async_http_client = MyAsyncHTTPClient()
res_body = async_http_client._fetch(url)
The issue is that I'm not sure how to deal with this code to get the returned value once it's ready.
can you please help?
Thanks!
Editing
I have also tried implementing this function like:
class MyAsyncHTTPClient(httpclient.AsyncHTTPClient):
#gen.coroutine
def _fetch(self, url):
print('send Asynchronous request ...['+url+"] "+str(self))
http_response = yield self.fetch(url)
print('got Asynchronous response !!')
return http_response.body
But I'm having the same results :(
Editing again
I have succeeded running the async class...but without the inherited object self.
I did it like that:
#gen.coroutine
def _fetch_async(self, url):
print('send Asynchronous request ...[' + url + "] ")
http_response = yield httpclient.AsyncHTTPClient().fetch(url)
#http_response = yield self.fetch(url)
print('got Asynchronous response !!')
return http_response.body
and it worked fine.
The issue is that I need to use the inherited object self, and I'm not sure what am I missing here when defining the class.
When debugging I can see that self is very "thin" with its content..
Please let me know what am I doing wrong here.
Thanks!
Asynchronous functions can only be called from other asynchronous functions. You must call _fetch like this:
#gen.coroutine
def f():
async_http_client = MyAsyncHTTPClient()
res_body = yield async_http_client._fetch(url)
If you're not doing this in the context of a tornado server, the best way to call a coroutine from your __main__ block is like this:
tornado.ioloop.IOLoop.current().run_sync(f)

Aiohttp authentication middleware

I want to attach a middleware to specific handler and if client is not authorized then want to return an error response. However with the following code :
async def middleware_factory(app, handler):
async def auth_handler(request):
if request.headers.get('Authorization') == 'Basic test1234':
return await handler(request)
return web.Response(text='Forbidden', status='403')
return auth_handler
I am getting an exception that :
AssertionError: Handler <function AbstractRoute.__init__.
<locals>.handler_wrapper at 0x10da56bf8> should return response
instance, got <class 'NoneType'> [middlewares [<function
middleware_factory at 0x1104cb268>]]
Documentation states that I should return a response object which I am doing. Still this error. Where am I going wrong?
You can look to an example from official documentation.
But the main concern that if you want to have Middleware Factory - needs to be a function not a coroutine. Also, recommend to use #web.middleware decorator for that.
from aiohttp import web
def middleware_factory(text):
#web.middleware
async def sample_middleware(request, handler):
resp = await handler(request)
resp.text = resp.text + text
return resp
return sample_middleware

how does python aiohttp.web middleware work

according to the documentation
The handler passed in to a middleware factory is the handler returned by the next middleware factory. The last middleware factory always receives the request handler selected by the router itself (by UrlDispatcher.resolve()).
I thought UrlDispatcher.resolve() will return the registered handler that I assigned so I wrote this code. Based on my understanding when the page 127.0.0.1:9000 is accessed the index handler will be used as handler for m1
import logging;
import asyncio
from aiohttp import web
logging.basicConfig(level=logging.INFO)
#asyncio.coroutine
def m1(app,handler):
def log(request):
r = yield from handler(request)
logging.info(str(r))
#asyncio.coroutine
def index(request):
return web.Response(body=b'<h1>Aswesome</h1>')
#asyncio.coroutine
def names(request):
return web.Response(text='<h1>%s</h1>' % request.match_info['name'])
#asyncio.coroutine
def init(loop):
app = web.Application(loop=loop, middlewares=[
m1
])
app.router.add_route('GET','/',index)
app.router.add_route('GET','/{name:\w+}',names)
srv = yield from loop.create_server(app.make_handler(),'127.0.0.1',9000)
logging.info('server started at http://127.0.0.1:9000')
return srv
loop = asyncio.get_event_loop()
loop.run_until_complete(init(loop))
loop.run_forever()
When I ran the code and accessed the server at 127.0.0.1:9000 I got
File "/home/os/Documents/Python/web/lib/python3.5/site-packages/aiohttp/web.py", line 90, in handle_request
resp = yield from handler(request)
TypeError: 'NoneType' object is not callable
Which seems to me like a NoneType was passed into the m1 middleware as handler
Your middleware returns nothing, but middleware handler log should be returned. I added a line to your code, which returns log.
#asyncio.coroutine
def m1(app,handler):
def log(request):
r = yield from handler(request)
logging.info(str(r))
return log # changed
See documentation regarding middlewares for more details.
Also, it worth to look into Python 3.5, which provides async/await syntax for work with awaitable objects aka coroutines. Usage of the syntax is suggested by the contributors of aiohttp.
See official PEP-492 or slides from the talk from Igor Davydenko with the introduction into the new syntax.

GAE: How can a handler return a webapp2.Response when using sessions and overriding `dispatch`?

I adapted this sample code in order to get webapp2 sessions to work on Google App Engine.
What do I need to do to be able to return webapp2.Response objects from a handler that's inheriting from a BaseHandler that overrides the dispatch method?
Here's a demonstration of the kind of handler I want to write:
import webapp2
import logging
from webapp2_extras import sessions
class BaseHandler(webapp2.RequestHandler):
def dispatch(self):
# Get a session store for this request.
self.session_store = sessions.get_store(request=self.request)
try:
# Dispatch the request.
webapp2.RequestHandler.dispatch(self)
finally:
# Save all sessions.
self.session_store.save_sessions(self.response)
class HomeHandler(BaseHandler):
def get(self):
logging.debug('In homehandler')
response = webapp2.Response()
response.write('Foo')
return response
config = {}
config['webapp2_extras.sessions'] = {
'secret_key': 'some-secret-key',
}
app = webapp2.WSGIApplication([
('/test', HomeHandler),
], debug=True, config=config)
This code is obviously not working, since BaseHandler always calls dispatch with self. I've looked through the code of webapp2.RequestHandler, but it seriously eludes me how to modify my BaseHandler (or perhaps set a custom dispatcher) such that I can simply return response objects from inheriting handlers.
Curiously, the shortcut of assigning self.response = copy.deepcopy(response) does not work either.
You're mixing the two responses in one method. Use either
return webapp2.Response('Foo')
or
self.response.write('Foo')
...not both.
I took a look at webapp2.RequestHandler and noticed that returned values are just passed up the stack.
A solution which works for me is to use the returned Response when one is returned from the handler, or self.response when nothing is returned.
class BaseHandler(webapp2.RequestHandler):
def dispatch(self):
# Get a session store for this request.
self.session_store = sessions.get_store(request=self.request)
response = None
try:
# Dispatch the request.
response = webapp2.RequestHandler.dispatch(self)
return response
finally:
# Save all sessions.
if response is None:
response = self.response
self.session_store.save_sessions(response)
While I was playing I noticed that my session stored as a secure cookie was not getting updated when exceptions were raised in the handler.

Categories

Resources