[PYTHON] Simply check Content-Type in Flask (@content_type)

1.First of all

In Previous article, I created a concept app for microservices using flask, cerberus, and peewee. This time, I will explain how to check Content-Type by flask, which was omitted at that time. I would also like to introduce how to create a function decorator that checks the Content-Type.

1.1. Specifications of the @content_type function decorator introduced this time

@content_Example of using the type function decorator


# rest api
@app.route('/questions', methods=['POST'])
@content_type('application/json')
def register_question():

1.2. Verification environment

2. See request.headers for HTTP request headers

In flask, you can refer to the HTTP request header information with request.headers. request.headers is a dictionary-type object whose key is the header name. So, when checking Content-Type, get the value using Content-Type as a key. See http://flask.pocoo.org/docs/0.12/api/#incoming-request-data for more information.

2.1. Sample code to check Content-Type (not good enough)

Poor sample code


# -*- coding: utf-8 -*-
import os
from flask import Flask, abort, request, make_response, jsonify

# flask
app = Flask(__name__)

# rest api
@app.route('/questions', methods=['POST'])
def register_question():
    #Content type (application/json) check
    if not request.headers.get("Content-Type") == 'application/json':
        error_message = {
            'error':'not supported Content-Type'
        }
        return make_response(jsonify(error_message), 400)

    # omitted :Execution of business logic
    #Return of processing result (JSON format)
    return make_response(jsonify({'result': True}))

@app.route('/questions/<string:question_code>', methods=['PUT'])
def update_question(question_code):
    #Content type (application/json) check
    if not request.headers.get("Content-Type") == 'application/json':
        error_message = {
            'error':'not supported Content-Type'
        }
        return make_response(jsonify(error_message), 400)
    
    # omitted :Execution of business logic
    #Return of processing result (JSON format)
    return make_response(jsonify({'result': True}))

# main
if __name__ == "__main__":
    app.run(host=os.getenv("APP_ADDRESS", 'localhost'), \
    port=os.getenv("APP_PORT", 3000))

2.2. Problems with sample code

Since there are many places to check the Content-Type, the problem of Code Clone / Duplicate Code will occur. It can be defined as a utility function, but since it does not meet the prerequisites before calling the API, it is just a personal impression, but I did not want to describe the process of calling the utility.

A function decorator can only call the target function (here REST API) if it passes the check, that is, if the prerequisites are met. As a result, I decided to implement the function decorator shown below.

3. Create a @content_type function decorator for a modern look

3.1. Sample code to check Content-Type

content_type_check_app.py


# -*- coding: utf-8 -*-
import os
from flask import Flask, abort, request, make_response, jsonify
import functools

# flask
app = Flask(__name__)

# check content-type decorator
def content_type(value):
    def _content_type(func):
        @functools.wraps(func)
        def wrapper(*args,**kwargs):
            if not request.headers.get("Content-Type") == value:
                error_message = {
                    'error': 'not supported Content-Type'
                }
                return make_response(jsonify(error_message), 400)

            return func(*args,**kwargs)
        return wrapper
    return _content_type

# rest api
@app.route('/questions', methods=['POST'])
@content_type('application/json')
def register_question():
    # omitted :Execution of business logic
    #Return of processing result (JSON format)
    return make_response(jsonify({'result': True}))

@app.route('/questions/<string:question_code>', methods=['PUT'])
@content_type('application/json')
def update_question(question_code):
    # omitted :Execution of business logic
    #Return of processing result (JSON format)
    return make_response(jsonify({'result': True}))

# main
if __name__ == "__main__":
    app.run(host=os.getenv("APP_ADDRESS", 'localhost'), \
    port=os.getenv("APP_PORT", 3000))

3.2. Result of using @content_type

You can now just add @ content_type ('application / json') to your REST API functions. You can intuitively see that the Content-Type should be application / json without the code cloning problem. (If necessary, make application / json constant)

** By the way, if you change the description order of @ app.route and @ content_type, an error will occur. ** ** Python's function decorators are nesting of functions rather than landmarks like Java annotations, so the order of nesting affects the processing flow. This is because the function of flask is called in the processing of @content_type, so it is necessary to execute flask first as a nest.

4. Operation check

4.1. Client source code

democlient.py


# -*- coding: utf-8 -*-
import requests
import json

def register_question_none_content_type(url, data):
    print("[POST] /questions : none content-type")
    response = requests.post(url, json.dumps(question))
    print(response.status_code)
    print(response.content)

def register_question(url, data):
    print("[POST] /questions : content-type application/json")
    response = requests.post(url, json.dumps(question), \
        headers={'Content-type': 'application/json'})
    print(response.status_code)
    print(response.content)

# main
if __name__ == "__main__":
    url = 'http://localhost:3000/questions'
    question = {
        'question_code' : '9999999999',
        'category' : 'demo',
        'message' : 'hello'
    }
    # Content-No Type
    register_question_none_content_type(url, question)
    # Content-Type : application/json
    register_question(url, question)

4.1.1. Do not specify Content-Type application / json in requests

As described in Previous article, if you specify data in the json argument like json = data, requests will automatically apply / Set the Content-Type of json. Since this function is not used this time, the direct transmission data (json.dumps (question)) is set in the second argument.

4.2. Start the server

Server startup (and execution log)


C:\tmp\>python content_type_check_app.py
 * Running on http://localhost:3000/ (Press CTRL+C to quit)
127.0.0.1 - - [16/Aug/2017 20:02:30] "POST /questions HTTP/1.1" 400 -
127.0.0.1 - - [16/Aug/2017 20:02:30] "POST /questions HTTP/1.1" 200 -

The first line of the execution log is an HTTP request for which Content-Type is not specified, and the HTTP status is 400. The second line is the specified HTTP request. In this case, the process is executed normally and 200 is returned.

4.3. Starting the client

Client execution


C:\tmp\python democlient.py
[POST] /questions : none content-type
400
{
  "error": "not supported Content-Type"
}

[POST] /questions : content-type application/json
200
{
  "result": true
}

We have confirmed that HTTP requests that do not specify Content-Type will return an HTTP status of 400 error and the error content {'error':'not supported Content-Type'} as designed. Since the next request specifies application / json for Content-Type, it has passed the Content-Type check and the correct processing result with HTTP status of 200 is returned.

5. Finally

This time I explained how to check Content-Type in flask. If ** flask can perform request mapping even with Content-Type, Content-Type will be guaranteed when REST API is called **, so the @content_type created this time will not be necessary.

Also, I created a function decorator for the first time this time, but since I can add functions later like AOP, I felt that it could be used in various other ways.

Recommended Posts

Simply check Content-Type in Flask (@content_type)
Image uploader in Flask
HTTP environment variables in Flask
Celery asynchronous processing in Flask
Upload multiple files in Flask
Check for external commands in python
Create a CSV reader in Flask
Fixed Apscheduler running twice in Flask
About parameter processing in Flask request.args
Check and move directories in Python
Check the data summary in CASTable
Configuration file best practices in Flask