How to handle unexpected url parameter in Flask - python

I'm new to Flask and web development. I have a question about url parameters.
For example, I have an endpoint '/categories' which expect no url arguments. I experimented adding some random url parameter in curl like
curl localhost:5000/categories?page=1
It works like normal, I'm wondering if that is the expected behavior or should I handle this with some error or warning?
Also, if I expect an url parameter called 'id' and the request contains no url parameter or wrong url parameter. How should I handle this situation?
What status code should I abort in the above situations?
Thank you for any help.

You will need to inspect your query string (the values after ? in the URL) to see if the parameter exists and the value is valid. There are libraries to do this with a decorator, but you can do it manually as well. You will need to import the request object at the top of your module.
from flask import request
#app.route('/categories')
def categories():
id = request.args.get('id') # will return None if key does not exist
if id:
# id exists, validate value here
pass
else:
# no id, return error or something
pass
Related question:
How do you get a query string on Flask?

Related

Issue with url_for mapping variables to URL if multiple routes are used

I've hit an issue with url_for, where it won't automatically remap the variable straight into the URL because there are two routes.
My use case is an API, where creating an object will return the same data as if a GET command was run on it.
Here's an example of the code:
#app.route('/test', methods=['POST'])
#app.route('/test/<string:name>', methods=['GET'])
def test(name=None):
if request.method == 'POST':
return redirect(url_for('test', name='xyz'))
return name
If the first app.route is removed, then url_for('test', name='xyz') will correctly return "test/xyz".
However, with both app.route lines, it instead returns "test?name=xyz". This then causes name to be None, where the variable is actually located at request.args['name'].
I don't want to do a if name is None: name=request.args.get('name'), so is there any way I can force it to only look at routes with a GET method? My case right now is simple enough I could just do url_for('test')+'/xyz', but it seems like there should be better way of doing this.
According to the Flask Docs you can specify which method to map against use the _method argument.
flask.url_for(endpoint, **values)
And the values you can pass are:
endpoint – the endpoint of the URL (name of the function)
values – the variable arguments of the URL rule
_external – if set to True, an absolute URL is generated. Server address can be changed via SERVER_NAME configuration variable which falls back to the Host header, then to the IP and port of the request.
_scheme – a string specifying the desired URL scheme. The _external parameter must be set to True or a ValueError is raised. The default behavior uses the same scheme as the current request, or PREFERRED_URL_SCHEME from the app configuration if no request context is available. As of Werkzeug 0.10, this also can be set to an empty string to build protocol-relative URLs.
_anchor – if provided this is added as anchor to the URL.
_method – if provided this explicitly specifies an HTTP method. <---- This one
Specify the _method argument in url_for like this:
url_for('test', name='xyz', _method='GET')

Change URL to another URL using mitmproxy

I am trying to redirect one page to another by using mitmproxy and Python. I can run my inline script together with mitmproxy without issues, but I am stuck when it comes to changing the URL to another URL. Like if I went to google.com it would redirect to stackoverflow.com
def response(context, flow):
print("DEBUG")
if flow.request.url.startswith("http://google.com/"):
print("It does contain it")
flow.request.url = "http://stackoverflow/"
This should in theory work. I see http://google.com/ in the GUI of mitmproxy (as GET) but the print("It does contain it") never gets fired.
When I try to just put flow.request.url = "http://stackoverflow.com" right under the print("DEBUG") it won't work neither.
What am I doing wrong? I have also tried if "google.com" in flow.request.url to check if the URL contains google.com but that won't work either.
Thanks
The following mitmproxy script will
Redirect requesto from mydomain.com to newsite.mydomain.com
Change the request method path (supposed to be something like /getjson? to a new one `/getxml
Change the destination host scheme
Change the destination server port
Overwrite the request header Host to pretend to be the origi
import mitmproxy
from mitmproxy.models import HTTPResponse
from netlib.http import Headers
def request(flow):
if flow.request.pretty_host.endswith("mydomain.com"):
mitmproxy.ctx.log( flow.request.path )
method = flow.request.path.split('/')[3].split('?')[0]
flow.request.host = "newsite.mydomain.com"
flow.request.port = 8181
flow.request.scheme = 'http'
if method == 'getjson':
flow.request.path=flow.request.path.replace(method,"getxml")
flow.request.headers["Host"] = "newsite.mydomain.com"
You can set .url attribute, which will update the underlying attributes. Looking at your code, your problem is that you change the URL in the response hook, after the request has been done. You need to change the URL in the request hook, so that the change is applied before requesting resources from the upstream server.
Setting the url attribute will not help you, as it is merely constructed from underlying data. [EDIT: I was wrong, see Maximilian’s answer. The rest of my answer should still work, though.]
Depending on what exactly you want to accomplish, there are two options.
(1) You can send an actual HTTP redirection response to the client. Assuming that the client understands HTTP redirections, it will submit a new request to the URL you give it.
from mitmproxy.models import HTTPResponse
from netlib.http import Headers
def request(context, flow):
if flow.request.host == 'google.com':
flow.reply(HTTPResponse('HTTP/1.1', 302, 'Found',
Headers(Location='http://stackoverflow.com/',
Content_Length='0'),
b''))
(2) You can silently route the same request to a different host. The client will not see this, it will assume that it’s still talking to google.com.
def request(context, flow):
if flow.request.url == 'http://google.com/accounts/':
flow.request.host = 'stackoverflow.com'
flow.request.path = '/users/'
These snippets were adapted from an example found in mitmproxy’s own GitHub repo. There are many more examples there.
For some reason, I can’t seem to make these snippets work for Firefox when used with TLS (https://), but maybe you don’t need that.

Flask JSON request is None

I'm working on my first Flask app (version 0.10.1), and also my first Python (version 3.5) app. One of its pieces needs to work like this:
Submit a form
Run a Celery task (which makes some third-party API calls)
When the Celery task's API calls complete, send a JSON post to another URL in the app
Get that JSON data and update a database record with it
Here's the relevant part of the Celery task:
if not response['errors']: # response comes from the Salesforce API call
# do something to notify that the task was finished successfully
message = {'flask_id' : flask_id, 'sf_id' : response['id']}
message = json.dumps(message)
print('call endpoint now and update it')
res = requests.post('http://0.0.0.0:5000/transaction_result/', json=message)
And here's the endpoint it calls:
#app.route('/transaction_result/', methods=['POST'])
def transaction_result():
result = jsonify(request.get_json(force=True))
print(result.flask_id)
return result.flask_id
So far I'm just trying to get the data and print the ID, and I'll worry about the database after that.
The error I get though is this: requests.exceptions.ConnectionError: None: Max retries exceeded with url: /transaction_result/ (Caused by None)
My reading indicates that my data might not be coming over as JSON, hence the Force=True on the result, but even this doesn't seem to work. I've also tried doing the same request in CocoaRestClient, with a Content-Type header of application/json, and I get the same result.
Because both of these attempts break, I can't tell if my issue is in the request or in the attempt to parse the response.
First of all request.get_json(force=True) returns an object (or None if silent=True). jsonify converts objects to JSON strings. You're trying to access str_val.flask_id. It's impossible. However, even after removing redundant jsonify call, you'll have to change result.flask_id to result['flask_id'].
So, eventually the code should look like this:
#app.route('/transaction_result/', methods=['POST'])
def transaction_result():
result = request.get_json()
return result['flask_id']
And you are absolutely right when you're using REST client to test the route. It crucially simplifies testing process by reducing involved parts. One well-known problem during sending requests from a flask app to the same app is running this app under development server with only one thread. In such case a request will always be blocked by an internal request because the current thread is serving the outermost request and cannot handle the internal one. However, since you are sending a request from the Celery task, it's not likely your scenario.
UPD: Finally, the last one reason was an IP address 0.0.0.0. Changing it to the real one solved the problem.

In flask, how to route a link generated by OAuth 2?

I'm building a web app with flask. I used
#app.route('/')
def home():
.....
return render_template('home.html',url=url)
to generate the index page. There is a link(the second parameter, url) on the index page which leads to weibo (the chinese "twitter") for OAuth 2. After clicking the link and inputing the weibo username and password, I'm bounced back to a link like www.myflaskwebappaddress.com/?code=valueofcode.
My question is how can I catch the value of code and do something with the code in another page.
I tried the following:
#app.route(url_for('/', code=<tokencode>))
def show_data(tokencode):
.....want to use weibo api here using the tokencode.
But it doesn't work.
Everything after the ? is a query parameter; Flask parses those and provides them as part of the request.args object:
#app.route(url_for('/')
def home():
tokencode = request.args.get('code')
if tokencode is not None:
# there was a `?code=...` query parameter.
Also see The Request Object in the Flask quickstart.
Routing is only dependent on the path and the method of the request, the parameters don't play in that, so you cannot create a separate route for requests with that parameter.
You could, instead, use a before_request handler to look out for the code:
#app.before_request
def check_token():
tokencode = request.args.get('code')
if tokencode is not None:
# there was a `?code=...` query parameter.
If this function returns None, the normal routing takes place. But you can also return valid response; in that case that is the response returned to the end user and normal routing is skipped entirely.
This also means you can use any route on your site with a ?code=<tokencode> query parameter and have the before_request hook function handle it, not just your homepage.

AssertionError: Request global variable is not set

I know this is a duplicate question but by referring previous answers i couldn't find the solution yet.
I am using Google report api to fetch logs.
Please refer this link: https://developers.google.com/admin-sdk/reports/v1/libraries
Everything goes well and I am able to generate authorize URL using scope,client id etc.
But I am not able to redirect user to URL to fetch "code" from authorize URL.
I tried using webapp2 script but throws error = AssertionError: Request global variable is not set.
Here is the code I am using for redirection:
import webapp2
class MainPage(webapp2.RequestHandler):
def get(self):
import ipdb;ipdb.set_trace()
path='my authorize url path'
return self.redirect(path) #throws error on this line
a1=MainPage() #object to call class
a2=a1.get() #call method of class
Where i am going wrong ? If webapp2 having standard bug for self.redirect, then which other framework can help to to perform same operation?
If i use app = webapp2.WSGIApplication([('/', MainPage)]) instead of creating objects then it doesnt even call get(self) function.
Any help would be appreciated.
Thanks.

Categories