跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

Heroku 上 Flask API 与 Dash 应用的部署与集成

本文探讨了在 Heroku 部署 Flask API 与 Dash 应用时常见的 405 Method Not Allowed 错误及其解决方案。核心问题在于 Heroku 的 Procfile 配置与 Flask 和 Dash 应用实例的交互方式。通过将 Dash 应用集成到主 Flask 实例中,并正确配置 Procfile 指向单一的 Flask 应用入口,可以有效解决 路由 冲突,实现 API 和 UI 的协同工作。教程将提供集成示例代码,并强调部署注意事项。 1. 背景与问题描述 在 heroku 平台部署 web 应用时,开发者常遇到将后端 api(如使用 flask 构建)与 前端 交互式界面(如使用 dash 构建)结合的需求。一个典型的场景是,flask api 负责数据接收和处理(例如,将远程数据写入 postgresql 数据库),而 dash 应用则提供 数据可视化 或管理界面。然而,当尝试在同一个 heroku 应用中同时运行这两个组件时,可能会遭遇 405 method not allowed 错误,尤其是在尝试向 flask api 端点发送 post 请求时。 原始问题中的错误信息 response content: b'\n\n405 Method Not Allowed\n

Method Not Allowed

\n

The method is not allowed for the requested URL.

\n' 明确指出服务器不接受对指定 URL 使用请求的方法(POST)。这通常不是认证问题,而是路由或服务器配置问题。 2. 深入理解问题根源:Flask、Dash 与 Heroku Procfile 问题的核心在于 Flask 和 Dash 应用实例的独立性以及 Heroku Procfile 的工作方式。 在提供的代码中,存在两个独立的应用程序实例: app = Flask(__name__):这是一个标准的 Flask 应用实例,用于定义 API 路由(例如 /ingest)。 dash_app = dash.Dash(__name__,):这是一个 Dash 应用实例,它内部也运行着一个 Flask 服务器 (dash_app.server)。 当在 Procfile 中定义 Heroku 的 Web 进程时,我们必须指定一个单一的入口点供 Gunicorn(Heroku 推荐的 WSGI HTTP 服务器)启动。 如果 Procfile 指向 your_module_name:app,Gunicorn 将启动 app = Flask(__name__) 实例。此时,@app.route 定义的 API 路由将可用,但 dash_app 及其路由将无法访问。 如果 Procfile 指向 your_module_name:server(这里的 server 通常指 dash_app.server),Gunicorn 将启动 Dash 应用内部的 Flask 实例。此时,Dash 界面将可用,但 app = Flask(__name__) 实例上定义的 API 路由将无法访问,从而导致 405 Method Not Allowed 错误,因为 /ingest 路径对于 Dash 应用的内部 Flask 实例而言可能不存在或不允许 POST 方法。 简而言之,尝试在同一个 Heroku dyno 中通过单一 Procfile 入口同时运行两个独立的 Flask/Dash 实例是行不通的。 3. 解决方案:将 Dash 应用集成到现有 Flask 应用中 最推荐且最健壮的解决方案是将 Dash 应用作为子应用集成到主 Flask 应用中。这样,所有路由(无论是 Flask API 路由还是 Dash UI 路由)都将由同一个 Flask 服务器实例处理。 php版微信js-sdk支付接口类 php版微信js-sdk支付接口类 下载 3.1 修正后的应用代码 以下是整合了 Flask API 和 Dash UI 的 Python 应用代码示例:
from flask import Flask, request, jsonify, make_response from flask_cors import CORS import dash from dash import dcc, html, Input, Output import json import os # 用于获取数据库连接字符串 # 1. 创建主 Flask 应用实例 app = Flask(__name__) CORS(app) # 为主 Flask 应用启用 CORS # 2. 将 Dash 应用集成到现有的 Flask 应用中 # 通过 server=app 参数,Dash 会使用我们已经创建的 Flask 应用实例 # url_base_pathname 可以指定 Dash 应用的根路径,例如 /dashboard/ dash_app = dash.Dash(__name__, server=app, url_base_pathname='/dashboard/') # 3. 定义 Flask API 路由 # 这个路由现在属于主 Flask 应用 @app.route('/ingest', methods=['OPTIONS', 'POST']) def handle_ingest(): # 处理 CORS 预检请求 if request.method == 'OPTIONS': response = make_response() response.headers.add('Access-Control-Allow-Origin', '*') # 生产环境请指定具体域名 response.headers.add('Access-Control-Allow-Headers', 'Authorization, Content-Type') response.headers.add('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') return response # 认证逻辑 token = request.headers.get('Authorization') # 客户端发送的是 'Bearer too_many_secrets',所以这里也要匹配 valid_tokens = ["Bearer too_many_secrets"] if token in valid_tokens: data = request.json # 假设数据以 JSON 格式发送 # --- 在这里执行数据验证和写入 PostgreSQL 数据库的逻辑 --- # 示例:连接到 Heroku Postgres 数据库并插入数据 # import psycopg2 # DATABASE_URL = os.environ.get('DATABASE_URL') # Heroku 会自动提供 # try: # conn = psycopg2.connect(DATABASE_URL, sslmode='require') # cur = conn.cursor() # # 示例:创建一个表并插入数据 # # cur.execute("CREATE TABLE IF NOT EXISTS sensor_data (id SERIAL PRIMARY KEY, sensor TEXT, value REAL, timestamp TIMESTAMPTZ DEFAULT NOW());") # # cur.execute("INSERT INTO sensor_data (sensor, value) VALUES (%s, %s);", (data.get('sensor'), data.get('value'))) # conn.commit() # cur.close() # conn.close() # print(f"Data ingested successfully: {data}") # return jsonify({"message": "Data ingested successfully", "received_data": data}), 200 # except Exception as e: # print(f"Database error: {e}") # return jsonify({"message": "Failed to ingest data due to database error"}), 500 # 仅为演示,实际应写入数据库 print(f"Success: Data ingested successfully: {data}") return jsonify({"message": "Data ingested successfully", "received_data": data}), 200 else: print("Unauthorized user: Your token was Invalid") return jsonify({"message": "Unauthorized"}), 401 # 4. 定义 Dash 应用的布局和回调 # Dash 应用现在是主 Flask 应用的一个部分 dash_app.layout = html.Div(children=[ html.H1(children='Heroku 集成应用'), html.P('欢迎来到 Dash 仪表板!'), dcc.Link('访问数据摄取 API 端点', href='/ingest', refresh=True), # 链接到 Flask API html.Div(id='output-message', style={'margin-top': '20px'}) ]) # 示例 Dash 回调 (如果需要) # @dash_app.callback( # Output('output-message', 'children'), # Input('url', 'pathname') # 需要 dcc.Location 组件才能获取 pathname # ) # def display_page(pathname): # if pathname == '/dashboard/': # return html.Div("您正在查看 Dash 仪表板首页。") # elif pathname == '/ingest': # return html.Div("您已点击了 API 端点链接,但此页面本身不提供交互。") # return html.Div("未知页面") # 5. 主程序入口 if __name__ == '__main__': # 在本地运行,Flask 应用将作为主服务器 app.run(debug=True)
3.2 客户端请求脚本 客户端请求脚本保持不变,因为它只需知道 API 端点。
import requests data = { "sensor": "temperature", "value": 25.5 } # 假设 Heroku 应用的 URL 是 'https://my_app.herokuapp.com/' # API 端点现在是 '/ingest' api_endpoint = 'https://my_app.herokuapp.com/ingest' token = 'too_many_secrets' # 客户端的原始 token headers = {'Authorization': f'Bearer {token}'} # 按照约定发送 Bearer token response = requests.post(api_endpoint, json=data, headers=headers, verify=True) if response.status_code == 200: print("Data sent successfully") print(f"Response: {response.json()}") else: print(f"Failed to send data. Status code: {response.status_code}") print(f'Response content: {response.content.decode()}') # 解码以便阅读
3.3 Heroku Procfile 配置 由于我们将 Dash 集成到了主 Flask 应用 app 中,现在只需要 Procfile 指向这个主 Flask 应用实例。 假设你的 Python 文件名为 app.py:
web: gunicorn app:app
这里的 app:app 表示: app (第一个):指的是你的 Python 模块文件名(例如 app.py)。 app (第二个):指的是该模块中 Gunicorn 应该启动的 Flask 应用程序实例的变量名 (app = Flask(__name__))。 4. 部署注意事项 依赖管理 : 确保 requirements.txt 文件中包含了所有必要的库,例如 flask, dash, dash-core-components, dash-html-components, flask-cors, gunicorn, psycopg2 (如果使用 PostgreSQL)。 环境变量 : 对于敏感信息(如数据库连接字符串、API 密钥),应使用 Heroku 环境变量而非硬 编码 。例如,Heroku 会自动为 Heroku Postgres 数据库提供 DATABASE_URL 环境变量。 CORS 配置 : 在生产环境中,Access-Control-Allow-Origin: '*' 应该被替换为你的前端应用或客户端的特定域名,以增强安全性。 授权令牌 : 确保客户端发送的授权令牌格式(例如 Bearer )与服务器端验证的格式一致。 Gunicorn 配置 : 如果需要更高级的 Gunicorn 配置(例如工作进程数量、超时时间),可以在 Procfile 中添加参数或创建 gunicorn.conf.py 文件。 数据库连接 : 在 Heroku 上连接 PostgreSQL 数据库时,通常需要 psycopg2-binary 库,并且连接字符串(DATABASE_URL)会自动注入到环境中。 5. 总结 通过将 Dash 应用作为子应用集成到主 Flask 应用中,并确保 Procfile 正确指向这个统一的 Flask 实例,我们能够成功地在 Heroku 上部署一个同时提供 API 服务和交互式 UI 的应用。这种方法避免了多个应用实例之间的冲突,简化了部署和管理,并解决了 405 Method Not Allowed 这一常见的部署问题。理解 Heroku Procfile 与应用实例的对应关系是成功部署此类复杂应用的关键。

相关文章