Tornado: get request arguments - python

I have following code:
application = tornado.web.Application([
(r"/get(.*)", GetHandler),
])
class GetHandler(tornado.web.RequestHandler):
def get(self, key):
response = {'key': key}
self.write(response)
when I go to localhost:port/get?key=python I receive empty key value {'key': ''}. What is wrong here?

(.*) in regex matches everything. So this — (r"/get(.*)", GetHandler) — will match anything followed by /get, example:
/get
/getsomething
/get/something
/get.asldfkj%5E&(*&fkasljf
Let's say a request comes in at localhost:port/get/something, then the value of key argument in GetHandler.get(self, key) will be /something (yes, including the slash because .* matches everything).
But if a request comes in at localhost:port/get?key=python, the value of key argument in GETHandler.get(self, key) will be an empty string. It happens because the part containing ?key=python is called a Query String. It's not part of the url path. Tornado (or almost every other web framework) doesn't pass this to the view as an argument.
There are two ways you can change your code:
If you want to access your view like this - localhost:port/get?key=python, you'll need to make changes to your url config and to your view:
application = tornado.web.Application([
(r"/get", GetHandler),
])
class GetHandler(tornado.web.RequestHandler):
def get(self):
key = self.get_argument('key', None)
response = {'key': key}
self.write(response)
If you don't want to change your app url config and your view, you'll need to make the request like this - localhost:port/get/python.
But still you'll need to make a small change to your url config. Add a slash - / - between get and (.*), because otherwise the value of key would be /python instead of python.
application = tornado.web.Application([
(r"/get/(.*)", GetHandler), # note the slash
])

I hope that you will figure what you did wrong by yourself - that's a task for you.
Your working code:
import tornado.ioloop
import tornado.web
class GetHandler(tornado.web.RequestHandler):
def get(self):
response = self.get_arguments("key")
if len(response) == 0:
# Handle me
self.set_status(400)
return self.finish("Invalid key")
self.write({"key":self.get_argument("key")})
def make_app():
return tornado.web.Application([
(r"/", GetHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()

Related

Why is reqparse not understanding the POST request?

From what I've seen online, I can use reqparse from flask_restful to add data to my GET and POST requests.
Here is what I have:
from flask import Flask, request
from flask_restful import Resource, Api, reqparse
import pandas as pd
import ast
app = Flask(__name__)
api = Api(app)
class User(Resource):
def get(self):
return {'data': 'get'}, 200
def post(self):
parser = reqparse.RequestParser(bundle_errors=True)
parser.add_argument('userId', type=str)
args = parser.parse_args()
print(args)
return {'data': 'post'}, 200
class Game(Resource):
pass
api.add_resource(User, '/user')
api.add_resource(Game, '/game')
if __name__ == '__main__':
app.run()
I'm trying to send this POST request (using Postman):
http://127.0.0.1:5000/user?userId=hello
But I always get this error back:
{
"message": "The browser (or proxy) sent a request that this server could not understand."
}
I truly don't know what I'm doing wrong...
I still have not figured out how to fix reqparse. However, I managed to get the result desired another way.
Basically you can change the api.add_resource() to include variables which you can pass into the class path. Like: api.add_resource(User, 'user/<userID>') allows you to send a request like http://127.0.0.1:5000/user/kayer and have the variable userID be kayer.
The other way (to go back to my original question on how to use this type of request: http://127.0.0.1:5000/user?userID=kayer is to implement reuest.args.get('userID') and assign the return of that function to a variable.
Full code:
from flask import Flask, request
from flask_restful import Resource, Api, reqparse
import pandas as pd
import ast
app = Flask(__name__)
api = Api(app)
class User(Resource):
def get(self, userId):
a = request.args.get('name')
return {'data': {'a': userId, 'b': a}}, 200
def post(self):
pass
class Game(Resource):
pass
api.add_resource(User, '/user/<userId>')
api.add_resource(Game, '/game')
if __name__ == '__main__':
app.run()
I think the problem is that reqparse (or flask_restful itself), by default, is not parsing the query string (?userId=hello), when it matches the request URL with the endpoint.
From https://flask-restful.readthedocs.io/en/latest/reqparse.html#argument-locations:
By default, the RequestParser tries to parse values from flask.Request.values, and flask.Request.json.
Use the location argument to add_argument() to specify alternate locations to pull the values from. Any variable on the flask.Request can be used.
As instructed and from the example on that documentation, you can explicitly specify a location keyword param as location="args", where args refers to flask.Request.args, which means the:
The parsed URL parameters (the part in the URL after the question mark).
...which is exactly where userId is set when you call the endpoint.
It seems to work after adding the location="args" parameter:
def post(self):
parser = reqparse.RequestParser(bundle_errors=True)
parser.add_argument("userId", type=str, location="args") # <--------
args = parser.parse_args()
print(args)
return {"data": args}, 200
Postman:
cURL:
$ curl -XPOST http://localhost:5000/user?userId=hello
{
"data": {
"userId": "hello"
}
}
Tested with:
Flask 2.1.2
Flask-RESTful 0.3.9

Sanic - path type of endpoint doesn't include query arguments

If I have defined my route as (only relevant part of code):
...
#app.get("brawlstats/endpoint:path>")
...
and I try to access route /brawlstats/rankings/global/brawlers/16000001?limit=200, it cuts off at the question mark. I want endpoint to include the entire url. How do I make it include the whole path ?
Everything after ? is considered as part of query string. Your endpoint represents path after /brawlstats.
Since endpoint and limit argument are split apart, you can retrieve both with this example:
from sanic import Sanic
from sanic.response import text
app = Sanic()
#app.get('/brawlstats/<endpoint:path>')
async def brawlstats(request, endpoint):
# endpoint is set to part after '/brawlstats'.
# limit query arg is converted to integer or 0 if it's not set.
limit = int(request.args.get('limit', 0))
if endpoint == 'rankings/global/brawlers/16000001' and limit > 0:
return text('Success', 200)
return text('Bad request', 400)
if __name__ == "__main__":
app.run(host="127.0.0.1", port=8000)

Access the response object in a bottlepy after_request hook

I have the following web app:
import bottle
app = bottle.Bottle()
#app.route('/ping')
def ping():
print 'pong'
return 'pong'
#app.hook('after_request')
def after():
print 'foo'
print bottle.response.body
if __name__ == "__main__":
app.run(host='0.0.0.0', port='9999', server='cherrypy')
Is there a way to access the response body before sending the response back?
If I start the app and I query /ping, I can see in the console that the ping() and the after() function run in the right sequence
$ python bottle_after_request.py
Bottle v0.11.6 server starting up (using CherryPyServer())...
Listening on http://0.0.0.0:9999/
Hit Ctrl-C to quit.
pong
foo
but when in after() I try to access response.body, I don't have anything.
In Flask the after_request decorated functions take in input the response object so it's easy to access it. How can I do the same in Bottle?
Is there something I'm missing?
Is there a way to access the response body before sending the response back?
You could write a simple plugin, which (depending on what you're actually trying to do with the response) might be all you need.
Here's an example from the Bottle plugin docs, which sets a request header. It could just as easily manipulate body.
from bottle import response, install
import time
def stopwatch(callback):
def wrapper(*args, **kwargs):
start = time.time()
body = callback(*args, **kwargs)
end = time.time()
response.headers['X-Exec-Time'] = str(end - start)
return body
return wrapper
install(stopwatch)
Hope that works for your purposes.
You can use plugin approach, this is what i did
from bottle import response
class BottlePlugin(object):
name = 'my_custom_plugin'
api = 2
def __init__(self, debug=False):
self.debug = debug
self.app = None
def setup(self, app):
"""Handle plugin install"""
self.app = app
def apply(self, callback):
"""Handle route callbacks"""
def wrapper(*a, **ka):
"""Encapsulate the result in the expected api structure"""
# Check if the client wants a different format
# output depends what you are returning from view
# in my case its dict with keys ("data")
output = callback(*a, **ka)
data = output["data"]
paging = output.get("paging", {})
response_data = {
data: data,
paging: paging
}
# in case if you want to update response
# e.g response code
response.status = 200
return response_data
return wrapper

Tornado Not Interpreting URL

I've been trying to create a web service using Tornado Framework. The system is supposed to handle URLs such as the following:
IP:Port/?user=1822&catid=48&skus=AB1,FS35S,98KSU1
First I've created this code to read urls:
#!/usr/bin/env python
from datetime import date
import tornado.httpserver
import tornado.escape
import tornado.ioloop
import tornado.web
class WService(tornado.web.RequestHandler):
def get(self, url):
self.write("value of url: %s" %(url))
application = tornado.web.Application([
(r"/([^/]+)", WService)])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(9000)
tornado.ioloop.IOLoop.instance().start()
and entering the url:
IP:Port/hello_world
resulted in:
value of url: hello_world
Any character used in the URL works, except for the "?". When changing the code such as:
application = tornado.web.Application([
(r"/?([^/]+)", WService)])
and sent the url with the "?" mark (IP:Port/?hello_world) the result is:
404: Not Found
Researching about Tornado to solve this problem I found the get_argument method and tried to apply it, such as:
class WService2(tornado.web.RequestHandler):
def get(self):
user = self.get_argument('user', None)
respose = { 'user': user }
self.write(response)
and
application = tornado.web.Application([
(r"/", WService2),
])
But sending the URL IP:Port/user=5 returned:
404: Not Found
I also tried:
application = tornado.web.Application([
(r"/(\w+)", WService2),
])
And also:
application = tornado.web.Application([
(r"/([^/]+)", WService2),
])
and nothing worked.
Am I doing something wrong to read the URLs with the "?" marks and is there a reason why Tornado is not reading URLs with parameters such as the user one? Is there something missing?
I've updated Tornado to the lastest version but it didn't work as well.
If you need more information or something is not clear please let me know.
Thanks in advance,
The reason that you aren't seeing the entire string is that Tornado has already parsed it and placed it into a request object. If you want to see the entire path including the query string, then take a look at request.uri.
In your example, if you were to go to http://localhost:9000/hello_world?x=10&y=33 then request.uri would be set to /hello_world?x=10&y=33.
However, as you state, you are better off using get_argument (or get_arguments) to examine the arguments. Here's your example, augmented to show how you can read them.
Try going to http://localhost:9000/distance?x1=0&y1=0&x2=100&y2=100 and see what you get back.
Similarly, try, http://localhost:9000/?user=1822&catid=48&skus=AB1,FS35S,98KSU1. This should now work as you expected.
#!/usr/bin/env python
import math
import tornado.httpserver
import tornado.escape
import tornado.ioloop
import tornado.web
class DistanceService(tornado.web.RequestHandler):
def get(self):
x1 = float(self.get_argument('x1'))
y1 = float(self.get_argument('y1'))
x2 = float(self.get_argument('x2'))
y2 = float(self.get_argument('y2'))
dx = x1 - x2
dy = y1 - y2
magnitude = math.sqrt(dx * dx + dy * dy)
self.write("the distance between the points is %3.2f" % magnitude)
class WService(tornado.web.RequestHandler):
def get(self):
self.write("value of request.uri: %s" % self.request.uri)
self.write("<br>")
self.write("value of request.path: %s" % self.request.path)
self.write("<br>")
self.write("value of request.query: %s" % self.request.query)
application = tornado.web.Application([
(r"/distance", DistanceService),
(r"/", WService),
])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(9000)
tornado.ioloop.IOLoop.instance().start()

How do I route specific paths using WSGIApplication()?

Currently I have foo.com/bar routing to a request handler Main. I also want foo.com/bar/id to route to that request handler (where "id" is an id of an object).
Here's what I tried but it's failing:
application = webapp.WSGIApplication(
[('/bar', MainHandler),
(r'/bar/(.*)', MainHandler)],
debug=True)
The error I get is:
TypeError: get() takes exactly 1 argument (2 given)
You need to change the signature of your MainHandler.get method, like so:
class MainHandler(webapp.RequestHandler):
def get(self, bar_id=None):
if bar_id is None:
# Handle /bar requests
else:
# Handle /bar/whatever requests

Categories

Resources