Ep.20 The request object

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 returns True 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 JSON
  • silent - Silence any parsing arrors and return None
  • 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 file
  • name - The name of the form field
  • save(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 bytes
  • request.content_type returns the content type of the request
  • request.mimetype returns the mimetype of the request
  • request.files returns the ImmutableMultiDict containing any files sent in the request

    if request.files.get("image"):
    image = request.files["image"]
    

    We used if request.files.get("image"): to check for a specific file where image is the name attribute of the form input, followed by image = 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 : .