The request object | Learning Flask Ep. 20
Exploring the Flask request object
The Flask request object gives us access to all of the incoming request data, nicely parsed and ready for us to work with.
The request object is available globally and can be accessed to get information about the current request, ensuring we only get data from the active thread.
If you’ve been following along with the “Learning Flask” series, you’ve seen the request object in action, from parsing incoming form data and handling the various request methods, however there’s much more to explore.
In this part, we’re going to create a couple of routes to take a look at some of the useful and interesting ways to work with the Flask request object.
Importing request
To work with it, you’ll first need to import it from Flask:
from flask import request
The request object
To take a look at the request
object:
@app.route("/the/request/object")
def public_index():
print(request.__dict__.items())
return render_template("index.html")
dict_items([('environ', {'wsgi.version': (1, 0),
'wsgi.url_scheme': 'http', 'wsgi.input': <_io.BufferedReader name=6>,
'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>,
'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'werkzeug.server.shutdown': <function WSGIRequestHandler.make_environ.<locals>.shutdown_server at 0x7f0740152e18>,
'SERVER_SOFTWARE': 'Werkzeug/0.14.1', 'REQUEST_METHOD': 'GET',
'SCRIPT_NAME': '', 'PATH_INFO': '/', 'QUERY_STRING': '', 'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': 52087, 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '5000',
'SERVER_PROTOCOL': 'HTTP/1.1', 'HTTP_HOST': '127.0.0.1:5000',
'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0',
'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'HTTP_ACCEPT_LANGUAGE': 'en-GB,en;q=0.5', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate',
'HTTP_REFERER': 'http://127.0.0.1:5000/', 'HTTP_DNT': '1', 'HTTP_CONNECTION': 'keep-alive',
'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 'HTTP_PRAGMA': 'no-cache', 'HTTP_CACHE_CONTROL': 'no-cache',
'werkzeug.request': <Request 'http://127.0.0.1:5000/' [GET]>}), ('shallow', False), ('view_args',
{}), ('url_rule', <Rule '/' (OPTIONS, HEAD, GET) -> public_index>), ('cookies', {})])
Thankfully for us, Flask provides a much cleaner way to work with the request object.
Request method
request.method
returns the method used in the current request - GET
, POST
, PUT
, DELETE
etc..
Frequently used as a handler to trigger certain code blocks to run based on the type of incoming request, you’ll often see something similar to this:
@app.route("/log-in", methods=["GET", "POST"])
def login():
if request.method == "POST":
# Get the form data
# Do something else
# Otherwise default to render_template
return render_template("log-in.html")
Cookies
request.cookies
returns a dictionary with key-value pairs of any cookies sent in the request:
app.py
@app.route("/")
def public_index():
cookies = request.cookies
print(cookies)
return render_template("index.html")
Terminal
{'chocolate_chip': 'definitely'}
Query strings
request.args
returns a parsed dictionary of keys and values, representing the query string arguments.
We can access individual quary string values by calling request.args.get()
and passing in a key:
@app.route("/args")
def args():
""" Handling query string arguments """
# request.args returns an ImmutableMultidict with the quesy string values
args = request.args
print(args)
# Get an individual query string value
q = request.args.get("q")
print(q)
return render_template("index.html")
If we access this route without a query string in the URL, we get:
ImmutableMultiDict([])
None
If we send a query string in the URL, including a value for the q
parameter, we get:
ImmutableMultiDict([('q', 'hello'), ('w', 'world')])
# hello
Forms
request.form
returns an ImmutableMultiDict
containing key value pairs, with the name
attribute of the input elements of the form as keys.
Just like request.args
, we can access individual values from the form data using request.form.get()
and passing in the key we want to fetch:
@app.route("/form", methods=["GET", "POST"])
def form():
""" Handling form data """
# Use request.method to handle the request type
if request.method == "POST":
# request.form returns an ImmutableMultiDict of keys and values
req = request.form
print(req)
# Get individual form values based on the name attribute
email = request.form.get("email")
print(email)
return render_template("form.html")
Our form has 2 input fields - email
and password
. If we submit the form, we get the following output:
ImmutableMultiDict([('email', 'foo@bar.com'), ('password', 'foobarred')])
# foo@bar.com
In addition to request.form
, we can use request.values
to return a CombinedMultiDict
containing any query string paramaters and the form data, both parsed as ImmutableMultiDict
.
Using the HTML tag action="/form?h=hello"
in our form, we get the following output:
CombinedMultiDict([ImmutableMultiDict([('h', 'hello')]),
ImmutableMultiDict([('email', 'foo@bar.com'), ('password', 'foobarred')])])
JSON
Parsing JSON in flask is just as simple, with a few additional helper methods and attribites.
request.is_json
returnsTrue
if the request body contains JSON data.request.json
parses the JSON and returns a dictionary of keys and values.request.get_json()
also parses the JSON and returns a dictionary of keys and values. IT also accepts some arguments:
request.get_json(force=False, silent=False, cache=True)
force
- Ignores the mimetype and always tries to parse JSONsilent
- Silence any parsing arrors and returnNone
cache
- Store the parsed JSON to return for any subsequent calls
In this example, we have a route that renders a template containing a form for the user to submit some JSON to the server. We have another route below it where the data gets parsed and handled.
@app.route("/json")
def json():
""" Returns an HTML template with an input to post some JSON """
return render_template("json.html")
@app.route("/json/handler", methods=["POST"])
def json_handler():
""" Handles the posted JSON data """
# request.is_json returns True if the request body is JSON
if request.is_json:
# request.get_json returns a dictionary
json_data = request.get_json()
print(json_data)
# request.json also returns a dictionary
json_data = request.json
# Get individual values from the JSON
name = request.json.get("name")
print(name)
res = make_response(jsonify(json_data), 200)
return res
res = make_response(jsonify({}), 400)
return res
We use request.is_json
as a check before we attempt to parse the request body.
Posting a JSON object with values for name
and age
gives us the following output:
{'name': 'Foo', 'age': '29'}
# Foo
Files
request.files
returns an ImmutableMultiDict
containing the file as a FileStorage
object, a special class from the Werkzeug
library that sits underneath Flask and handles requests.
request.files.get("example")
returns a FileStorage
object which has a few useful methods, including:
filename
- The name of the filename
- The name of the form fieldsave(destination)
- Saves the file to the given destination
We’ll link at the bottom of this guide to the Werkzeug
documentation on FileStorage
.
In this example, we have a form containing a single file input browser, which the user can submit to the same route.
You’ll also notice a few more attributes of the request being called which we’ll discuss after:
@app.route("/file", methods=["GET", "POST"])
def single_file():
"""
Handles a single file upload
Note - Forms uploading files just have enctype="multipart/form-data" in the form tag
"""
# request.content_length returns the content length in bytes
content_length = request.content_length
print(f"Content length: {content_length}")
# content_type
content_type = request.content_type
print(f"Content type: {content_type}")
# request.mimetype returns the mimetype of the request
mimetype = request.mimetype
print(mimetype)
# Get an ImmutableMultiDict of the files
file = request.files
print(file)
# Get a specific file using the name attribute
if request.files.get("image"):
image = request.files["image"]
print(f"Filename: {image.filename}")
print(f"Name: {image.name}")
print(image)
# To save the image, call image.save() and provide a destination to save to
# image.save("/path/to/uploads/directory/filename")
return render_template("file.html")
Let’s take a look at the output. We’ve used f strings so you can see what’s what:
Content length: 213671
Content type: multipart/form-data; boundary=----WebKitFormBoundaryB33O2yBRsBxnrIWr
mimetype: multipart/form-data
ImmutableMultiDict([('image', <FileStorage: 'flask.png' ('image/png')>)])
Filename: flask.png
Name: image
<FileStorage: 'flask.png' ('image/png')>
request.content_length
returns the length of the request content in bytesrequest.content_type
returns the content type of the requestrequest.mimetype
returns the mimetype of the requestrequest.files
returns theImmutableMultiDict
containing any files sent in the requestif request.files.get("image"): image = request.files["image"]
We used
if request.files.get("image"):
to check for a specific file whereimage
is the name attribute of the form input, followed byimage = request.files["image"]
to pull out the file.
Multiple files
We can also use request.files.getlist("name")
to return a list of files in the request, where name
is the name attribute of the input element.
To upload multiple files from an HTML form, you’ll need to include the multiple
attribute in the input element, for example:
<form action="/files" method="POST" enctype="multipart/form-data">
<input type="file" name="files" multiple>
<button type="submit">Upload</button>
</form>
The route:
@app.route("/files", methods=["GET", "POST"])
def files():
""" Upload multiple files """
# request.files.getlist returns a lift of files
files = request.files.getlist("files")
print(files)
return render_template("files.html")
Uploading 3 files, produces the following output:
`[<FileStorage: 'JavaScript-logo.png' ('image/png')>,
<FileStorage: 'jinja.png' ('image/png')>,
<FileStorage: 'linux_logo.png' ('image/png')>]
At this point, it’s easy to iterate over the list of files with a for
loop and do something with each file, like save it.
You can read more about saving files here.
View arguments
request.view_args
parses and returns a dictionary of any arguments passed into the route:
@app.route("/view/args/<foo>/<bar>")
def view_arguments(foo, bar):
""" Handles arguments coming in from the URL """
# request.view_args returns a dict of any arguments passed to the view
view_args = request.view_args
print(view_args)
return render_template("index.html")
Going to /view/args/hello/world
returns the following:
{'foo': 'hello', 'bar': 'world'}
URL info
The request
object also contains lots of useful data about the request URL.
We’ll use f
strings so you can see the output for each of the request
attributes:
@app.route("/url/info")
def url_info():
host = request.host
print(f"host: {host}")
host_url = request.host_url
print(f"host_url: {host_url}")
path = request.path
print(f"path: {path}")
full_path = request.full_path
print(f"full_path: {path}")
url = request.url
print(f"url: {url}")
base_url = request.base_url
print(f"base_url: {base_url}")
url_root = request.url_root
print(f"url_root: {url_root}")
return render_template("index.html")
Going to /url/info?query=hello
returns the following output:
host: 127.0.0.1:5000
host_url: http://127.0.0.1:5000/
path: /url/info
full_path: /url/info
url: http://127.0.0.1:5000/url/info?query=hello
base_url: http://127.0.0.1:5000/url/info
url_root: http://127.0.0.1:5000/
Headers & misc
The request
object contains useful information including the request headers, along with other various things.
In this example, we’ll create a route and run through some of the other request
object attributes.
request.headers
returns the headers!:
@app.route("/the/request/object")
def the_request_object():
headers = request.headers
print(headers)
return render_template("index.html")
Host: 127.0.0.1:5000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Dnt: 1
Connection: keep-alive
Cookie: foo=bar
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
We can access individual headers with request.headers.get("Example")
, where Example
is the header:
@app.route("/the/request/object")
def the_request_object():
accept_encoding = request.headers.get("Accept-Encoding")
print(accept_encoding)
return render_template("index.html")
gzip, deflate
request.user_agent
returns the user agent of the request:
@app.route("/the/request/object")
def the_request_object():
user_agent = request.user_agent
print(user_agent)
return render_template("index.html")
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0
request.access_route
is an interesting feature. If a forwarded header exists, it returns a list of all ip addresses from the client ip to the last proxy server:
@app.route("/the/request/object")
def the_request_object():
access_route = request.access_route
print(access_route)
return render_template("index.html")
ImmutableList(['127.0.0.1'])
If we expose our application using ngrok
, we get the following:
ImmutableList(['82.2.172.100'])
request.endpoint
returns the name of the current function:
@app.route("/the/request/object")
def the_request_object():
endpoint = request.endpoint
print(endpoint)
return render_template("index.html")
the_request_object
request.is_multiprocess
returns True
if the application is served by a WSGI server that spawns multiple processes:
@app.route("/the/request/object")
def the_request_object():
is_multiproc = request.is_multiprocess
print(is_multiproc)
return render_template("index.html")
# False
request.is_multithread
returns True
if the application is served by a multithreaded WSGI server:
@app.route("/the/request/object")
def the_request_object():
is_multithread = request.is_multithread
print(is_multithread)
return render_template("index.html")
# True
request.is_secure
Note - I assumed this would return True
if the application is served over https, however after some quick testing using ngrok
to expose the application over https, it still returned False
.
I’ll do some more testing on a live Flask deployment served over https and update the article after, but please feel free to let me know if I’m “barking up he wrong tree” as they say. It might be something to do with running the app from the development server but I’m happy to be corrected!
@app.route("/the/request/object")
def the_request_object():
is_secure = request.is_secure
print(is_secure)
return render_template("index.html")
If we access the route from the development server:
False
Proxying the application through ngrok
over https also returns False
, but we can see from the headers that the application is being served over https:
X-Forwarded-Proto: https
We can also look at the scheme using request.scheme
:
@app.route("/the/request/object")
def the_request_object():
scheme = request.scheme
print(scheme)
return render_template("index.html")
If we access the route from the development server:
http
Again, exposing the app over https using ngrok
also returned http
so I must be missing something here! Will update the article after testing on a live application over https.
request.remote_addr
returns the remote address of the client:
@app.route("/the/request/object")
def the_request_object():
remote_addr = request.remote_addr
print(remote_addr)
return render_template("index.html")
If we access the route from the development server:
127.0.0.1
If we access the route exposed over ngrok
:
127.0.0.1
If we want to see if the request has been proxied, we can check for the X-Forwarded-For
and return a list of proxied IP’s:
if request.headers.get("X-Forwarded-For"):
proxies = request.headers.getlist("X-Forwarded-For")
print(proxies)
Making the same request over ngrok
returns:
['82.2.172.100']
request.url_rule.methods
returns a set of available request methods for the route:
@app.route("/the/request/object")
def the_request_object():
url_rule = request.url_rule.methods
print(url_rule)
return render_template("index.html")
As we haven’t provided the method
argument or values, we get:
{'HEAD', 'GET', 'OPTIONS'}
If we modify the route to include several request methods:
@app.route("/the/request/object", methods=["GET", "POST", "PUT", "PATCH", "DELETE"])
def the_request_object():
url_rule = request.url_rule.methods
print(url_rule)
return render_template("index.html")
We get the following:
{'OPTIONS', 'GET', 'DELETE', 'POST', 'PUT', 'PATCH', 'HEAD'}
This is an exaggerated example to show the use of request.url_rule.methods
.
Resources
Last modified · 07 Mar 2019
Source : .