Настроить Nginx в качестве HTTPS-прокси для вашего REST приложения Python на Flask и Waitress на Ubuntu

#24  вторник, 18 июля 2023 г.  12 минут(ы)  1084 слова

Настройка Nginx

Вам нужно настроить Nginx, чтобы использовать SSL и перенаправлять запросы на ваше приложение Flask.

Если вы используете файл /etc/nginx/sites-available/default, добавьте следующий server для нужного поддомена например api.pixelrobot.ru:

server {
    # Add index.php to the list if you are using PHP
    index index.html index.htm index.nginx-debian.html;
    server_name api.pixelrobot.ru; # managed by Certbot


    location / {
        proxy_pass http://localhost:5000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;            
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Host $host:$server_port;
            proxy_set_header X-Forwarded-Port $server_port; 
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/pixelrobot.ru/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/pixelrobot.ru/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

Давайте разберем каждую директиву внутри блока "location":

  • proxy_pass http://localhost:5000; Эта директива указывает адрес бэкэнд-сервера https://api.pixelrobot.ru/, на который будут перенаправляться входящие запросы.

  • proxy_set_header Host $host; Эта директива устанавливает заголовок "Host" в перенаправляемом запросе со значением из исходного заголовка "Host". Она обеспечивает передачу правильного имени хоста на бэкэнд-сервер.

  • proxy_set_header X-Real-IP $remote_addr; Эта директива устанавливает заголовок "X-Real-IP" в перенаправляемом запросе со значением IP-адреса клиента, совершающего запрос. Она может быть полезна для логирования или определения IP-адреса клиента на бэкэнд-сервере.

  • proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; Эта директива устанавливает заголовок "X-Forwarded-For" в перенаправляемом запросе. Если заголовок "X-Forwarded-For" уже присутствует, то она добавляет IP-адрес клиента к существующему значению. Этот заголовок обычно используется для отслеживания исходного IP-адреса клиента при прохождении через промежуточные прокси.

  • proxy_set_header X-Forwarded-Host $host:$server_port; Эта директива устанавливает заголовок "X-Forwarded-Host" в перенаправляемом запросе со значением из исходного заголовка "Host", за которым следует номер порта сервера.

  • proxy_set_header X-Forwarded-Port $server_port; Эта директива устанавливает заголовок "X-Forwarded-Port" в перенаправляемом запросе со значением номера порта сервера.

Вместе эти директивы настраивают обратный прокси для передачи оригинального запроса на бэкэнд-сервер, указанный как http://localhost:5000, с сохранением соответствующих заголовков для правильной работы и идентификации.

В этой конфигурации все запросы HTTP перенаправляются на HTTPS, а запросы HTTPS передаются на ваше приложение Flask, которое, как предполагается, работает на порту 5000.

Перезапуск Nginx

Сначала проверим, все ли изменения в конфигурации Nginx были сделаны правильно:

sudo nginx -t

Если все в порядке, перезапустите Nginx:

sudo nginx -s reload

Простое приложение на Python, Flask и Waitress

Вот пример простого REST приложения на Flask и Waitress.

Сначала установим необходимые библиотеки через pip, в среде выполнения python3:

pip install flask waitress

Далее, создадим простое Flask приложение с одним маршрутом:

#get.py
from flask import Flask, request, jsonify
from waitress import serve

app = Flask(__name__)

@app.route('/get', methods=['GET'])
def get():
    # Access URL parameters with request.args
    name = request.args.get('name')
    age = request.args.get('age')

    if name and age:
        print(f"Received data: Name - {name}, Age - {age}")
        return "Data received and processed", 200
    else:
        return "No data received", 400

#if __name__ == '__main__':
#    app.run(debug=True)

if __name__ == "__main__":
    #app.run(host='0.0.0.0',port=5000)
    #We now use this syntax to server our app. 
    #serve(app, host='0.0.0.0', port=5000)
    serve(app, host='0.0.0.0', port=5000, url_scheme='https')

# C:\curl\curl "https://api.pixelrobot.ru/get?name=John&age=30"

Это пример приложения слушает на порту 5000 и возвращает приветственное сообщение при обращении к маршруту '/get'.

Вы можете запустить приложение из командной строки:

python app.py

Теперь, при переходе на https://api.pixelrobot.ru/get?name=John&age=30, вы получите ответ Data received and processed.

А в терминале на сервере будет выведен текст:

(env311) d00m4ace@server:~/projects/python$ python3 get.py
Received data: Name - John, Age - 30

Если вы хотите добавить больше маршрутов или функциональности, вы можете сделать это, добавив больше маршрутов и функций в ваше приложение Flask.

REST приложение на Python, Flask и Waitress

Мы определим несколько базовых REST маршрутов, которые будут возвращать некоторые данные в формате JSON.

# app.py
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/api/tasks', methods=['GET'])
def get_tasks():
    tasks = [
        {"id": 1, "title": "Buy groceries", "completed": False},
        {"id": 2, "title": "Study for test", "completed": True},
    ]
    return jsonify(tasks)

@app.route('/api/tasks/<int:task_id>', methods=['GET'])
def get_task(task_id):
    tasks = [
        {"id": 1, "title": "Buy groceries", "completed": False},
        {"id": 2, "title": "Study for test", "completed": True},
    ]
    for task in tasks:
        if task["id"] == task_id:
            return jsonify(task)
    return jsonify({"error": "Task not found"}), 404


if __name__ == '__main__':
    from waitress import serve
    serve(app, host="0.0.0.0", port=5000)

В этом примере мы имеем два маршрута. Один для получения всех задач (GET /api/tasks), и один для получения конкретной задачи по её id (GET /api/tasks/).

Мы используем waitress для запуска нашего приложения. Это production-ready WSGI сервер, который рекомендован для использования с Flask в production.

Для запуска приложения используйте команду:

python app.py

Теперь приложение будет слушать на порту 5000 и будет доступно по адресу https://api.pixelrobot.ru/api/tasks и https://api.pixelrobot.ru/api/tasks/.

POST приложение на Python, Flask и Waitress

Сначала установим необходимые библиотеки через pip:

pip install flask waitress

Flask приложение на Python обрабатывает разные типы входящих данных:

# app.py
from flask import Flask, request, jsonify
from waitress import serve

app = Flask(__name__)

#app.config['SECRET_KEY'] = 'df0331cefc6c2b9a5d0208a726a5d1c0fd37324feba25506'

@app.route('/post', methods=['POST'])
def post():
    if request.is_json:
        # For JSON data, use request.get_json()
        data = request.get_json()
        print(f"Received JSON data: {data}")
    elif request.headers['Content-Type'] == 'application/x-www-form-urlencoded':
        # For form data, use request.form
        data = request.form
        print(f"Received form data: {data}")
    else:
        # For plain text, use request.data
        data = request.data.decode('utf-8') 
        print(f"Received plain text data: {data}")

    return "Data received and processed", 200

#if __name__ == '__main__':
#    app.run(debug=True)

if __name__ == "__main__":
    serve(app, host='0.0.0.0', port=5000, url_scheme='https')

Для запуска приложения используйте команду:

python app.py

С помощью команды curl отправим POST запрос с JSON данными на конечную точку /post по указанному URL.

C:\curl\curl -X POST -H "Content-Type: application/json" -d "{\"name\":\"John\", \"age\":30}" https://api.pixelrobot.ru/post

Заголовок "Content-Type: application/json" сообщает серверу, что клиент (в данном случае curl) отправляет данные в формате JSON. Это позволяет серверу правильно интерпретировать и обрабатывать отправленные данные.

В контексте Flask приложения, когда вы делаете POST запрос и указываете "Content-Type: application/json", Flask знает, что ему следует получить данные из request.get_json().

C:\curl\curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "name=John&age=30" https://api.pixelrobot.ru/post

Эта команда curl отправляет POST запрос с данными, закодированными как application/x-www-form-urlencoded, на конечную точку /post по указанному URL.

Формат "application/x-www-form-urlencoded" обычно используется при отправке данных формы HTML. В этом формате, пары имя/значение записываются как name=value, с разделением пар амперсандом (&).

В контексте Flask приложения, когда вы делаете POST запрос и указываете "Content-Type: application/x-www-form-urlencoded", Flask будет искать данные в request.form.

C:\curl\curl -X POST -H "Content-Type: text/plain" -d "Hello, World!" https://api.pixelrobot.ru/post

Эта команда curl отправляет POST запрос с данными в формате text/plain на конечную точку /post по указанному URL.

Формат text/plain используется для отправки обычного текста. В данном случае, вы отправляете строку "Hello, World!".

В контексте Flask приложения, когда вы делаете POST запрос и указываете "Content-Type: text/plain", Flask будет искать данные в request.data.

Ваш URL https://api.pixelrobot.ru/post в этом примере должен быть заменен на реальный URL вашего сервера.