You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flask+Gunicorn+Nginx环境下自定义请求头无法接收问题

自定义请求头(如Api_key)无法被Flask服务接收的排查问题

我有一台配置Certbot SSL证书的VPS,采用Nginx+Gunicorn部署Flask应用,域名访问正常,但发送携带自定义请求头(如Api_key)的请求时,服务端无法接收这些头信息。已尝试配置Flask的CORS支持、在Nginx中添加underscores_in_headers指令,但问题仍未解决。


1. 服务端实际接收的请求头

DEBUG:root:Host: myweb.com
X-Forwarded-Proto: https
Connection: close
Content-Type: application/json
User-Agent: PostmanRuntime/7.37.3
Accept: */*
Postman-Token: 27fd8606-d9ce-4120-9a68-c359b3fe0e60
Accept-Encoding: gzip, deflate, br

2. Flask应用代码(已配置CORS)

import subprocess
from flask import Flask, jsonify, request, send_file
import os
from flask_cors import CORS
from flask_cors import cross_origin
import requests
import logging

logging.basicConfig(filename='flask_debug.log', level=logging.DEBUG)
app = Flask(__name__)
CORS(app, origins='*')

@app.after_request
def after_request(response):
    response.headers.add("Access-Control-Allow-Headers", "Content-Type, Api_key")
    return response

def verify_api_key(api_key):
    response = requests.get(
        "https://myweb.com/api/verify-api-key", headers={'Api_key': api_key}
    )

    if response.status_code == 200:
        result = response.json()
        return result.get("isValid", False)
    else:
        return False

@app.route('/')
def home():
    return 'El servidor está en línea y funcionando !'

@app.route("/api/get-user-videos", methods=["GET"])
def get_user_videos():
    api_key = request.headers.get("Api_key")
    if not api_key:
        return jsonify({'error': 'Se requiere una API_KEY'}), 401
    
    username = request.args.get("username")
    if not username:
        return jsonify({'error': 'Se requiere un nombre de usuario'}), 400

    if not verify_api_key(api_key):
        return jsonify({'error': 'API_KEY no válida'}), 401

    user_videos_folder = os.path.join("results", username)
    if not os.path.isdir(user_videos_folder):
        return jsonify({'error': 'Usuario no encontrado'}), 404

    video_files = [
        f
        for f in os.listdir(user_videos_folder)
        if f.endswith(".mp4")
    ]

    if not video_files:
        return jsonify({'error': 'Usuario sin videos'}), 404
        
    return jsonify(video_files), 200

        
@app.route("/api/get-user-videos-list", methods=["GET"])
def get_user_videos_list():
    api_key = request.headers.get("Api_key")
    logging.debug(request.headers)
    if not api_key:
        return jsonify({'error': 'Se requiere una API_KEY'}), 401

    if not verify_api_key(api_key):
        return jsonify({'error': 'API_KEY no válida'}), 401
    
    username = request.args.get("username")
    if not username:
        return jsonify({'error': 'Se requiere un nombre de usuario'}), 401

    user_videos_folder = os.path.join("uploads", username)
    if not os.path.isdir(user_videos_folder):
        return jsonify({'error': 'Usuario no encontrado'}), 404

    video_files = [
        f
        for f in os.listdir(user_videos_folder)
        if f.endswith(".mp4")
    ]

    if not video_files:
        return jsonify({'error': 'Usuario sin videos'}), 404
    
    alphabet = "ABCDEFGHIJKLMNÑOPQRSTUVWXYZ"
    missing_letters = [letter for letter in alphabet if letter not in ''.join(video_files)]
    
    if not missing_letters:
        return jsonify(video_files), 200

    return jsonify({'error': 'Alfabeto incompleto !'}), 400

@app.route("/api/get-user-video/<username>/<video_name>", methods=["GET"])
def get_user_video(username, video_name):
    api_key = request.headers.get("Api_key")
    if not api_key:
        return jsonify({'error': 'Se requiere una API_KEY'}), 401

    if not verify_api_key(api_key):
        return jsonify({'error': 'API_KEY no válida'}), 401
    
    if not username:
        return jsonify({'error': 'Se requiere un username'}), 401

    user_video_path = os.path.join("uploads", username, video_name)
    if not os.path.isfile(user_video_path):
        return jsonify({'error': 'Video no encontrado'}), 404

    return send_file(user_video_path, as_attachment=True)

if __name__ == '__main__':
    app.run(host='0.0.0.0', threaded=True)

3. Gunicorn服务配置文件

[Unit]
Description=Server Videos
After=network.target

[Service]
User=myuser
Group=www-data
WorkingDirectory=/home/videos/flask-server-videos
Environment="PATH=/home/videos/env/videos/bin"
ExecStart=/home/videos/env/videos/bin/gunicorn --workers 2 --bind unix:app.sock -m 007 wsgi:app
Restart=always

[Install]
WantedBy=multi-user.target

4. Nginx配置文件(已添加underscores_in_headers指令尝试解决)

server {
    underscores_in_headers on;
    location / {
        include proxy_params;
        proxy_pass http://unix:/home/videos/flask-server-videos/app.sock;
    }

    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/myweb.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/myweb.com/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
}

server {
    if ($host = myweb.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80;
    server_name myweb.com;
    return 404; # managed by Certbot
}

5. 测试请求示例

curl "https://myweb.com/api/get-user-videos-list?username=Jorge" ^
  -H "Accept: */*" ^
  -H "Accept-Language: es,en-US;q=0.9,en;q=0.8,es-MX;q=0.7" ^
  -H "Api_key: abcdefgh123456789" ^
  -H "Connection: keep-alive" ^
  -H "Content-Type: application/json" ^
  -H "Origin: https://anotherweb.com" ^
  -H "Referer: https://myweb.com/" ^
  -H "User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36 Edg/124.0.0.0" ^
  -H ^"sec-ch-ua: ^\^"Chromium^\^";v=^\^"124^\^", ^\^"Microsoft Edge^\^";v=^\^"124^\^", ^\^"Not-A.Brand^\^";v=^\^"99^\^"^" ^

排查建议

  1. 检查Nginx的proxy_params文件
    默认的proxy_params可能过滤带下划线的请求头,打开/etc/nginx/proxy_params查看,确保包含必要的转发配置,可手动添加自定义头转发:

    proxy_set_header Api_key $http_api_key;
    

    添加后重启Nginx:sudo systemctl restart nginx

  2. 修改请求头命名规范
    遵循HTTP标准,将自定义头改为X-Api-Key格式,同时更新Flask代码中的获取逻辑:request.headers.get("X-Api-Key"),并同步更新CORS允许头配置。

  3. 直接测试Gunicorn服务
    临时修改Gunicorn配置绑定端口(如--bind 0.0.0.0:8000),绕过Nginx直接测试:

    curl "http://localhost:8000/api/get-user-videos-list?username=Jorge" -H "Api_key: abcdefgh123456789"
    

    若能正常接收,说明问题出在Nginx配置;反之则排查Flask中间件或请求头处理逻辑。

  4. 检查Nginx日志
    在Nginx的server块中添加详细日志格式,记录自定义头:

    log_format debug_log '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" "$http_api_key"';
    access_log /var/log/nginx/debug_access.log debug_log;
    

    重启Nginx后发起请求,查看日志确认请求头是否到达Nginx。

  5. 打印Flask原始请求头
    在Flask代码中添加原始请求头打印逻辑,确认头信息是否被修改:

    from werkzeug.datastructures import EnvironHeaders
    logging.debug(dict(EnvironHeaders(request.environ)))
    

    查看日志输出,确认自定义头是否存在。


内容的提问来源于stack exchange,提问作者Jorge Zapien

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 14:41:00