使用 Flask 代理到另一个 Web 服务
Posted
技术标签:
【中文标题】使用 Flask 代理到另一个 Web 服务【英文标题】:Proxying to another web service with Flask 【发布时间】:2011-10-03 03:17:39 【问题描述】:我想将对我的 Flask 应用程序发出的请求代理到机器上本地运行的另一个 Web 服务。我宁愿为此使用 Flask,也不愿使用我们更高级别的 nginx 实例,这样我们就可以重用我们应用程序中内置的现有身份验证系统。我们越能保持这种“单点登录”就越好。
是否有现有的模块或其他代码来执行此操作?试图将 Flask 应用程序连接到 httplib 或 urllib 之类的东西被证明是一件痛苦的事情。
【问题讨论】:
在为不支持跨域安全的旧浏览器(如 IE7)执行 AJAX 服务时,这个问题也很重要。 您在使用 httplib 时遇到了什么具体问题? @jd:鉴于 Flask 位于 WSGI 的应用程序端,我不确定是否能有效转发所有数据。例如,Flask 请求对象似乎不包含我想要传递给 httplib 的原始请求(甚至请求标头)。并不是说不可能,这只是一种痛苦,我希望现有的模块已经做到了。 【参考方案1】:我花了很多时间在同一件事上工作,并最终使用 requests 库找到了一个似乎运行良好的解决方案。它甚至可以处理在一个响应中设置多个 cookie,这需要一些调查才能弄清楚。这是烧瓶视图功能:
from flask import request, Response
import requests
def _proxy(*args, **kwargs):
resp = requests.request(
method=request.method,
url=request.url.replace(request.host_url, 'new-domain.com'),
headers=key: value for (key, value) in request.headers if key != 'Host',
data=request.get_data(),
cookies=request.cookies,
allow_redirects=False)
excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
headers = [(name, value) for (name, value) in resp.raw.headers.items()
if name.lower() not in excluded_headers]
response = Response(resp.content, resp.status_code, headers)
return response
2021 年 4 月更新:excluded_headers
可能应该包含由RFC 2616 section 13.5.1 定义的所有“逐跳标头”。
【讨论】:
@Evan 不错的解决方案。但是,它不处理 3xx 重定向,因为重定向 url 可能指向代理主机 有人能补充一下你在 MWE 应用程序中如何称呼它吗? 这太好了,非常感谢! (这是让 ngrok 同时处理前端和后端所需要的。)但是,对我来说request.host_url
包括 http://
和一个斜杠,所以我的替换行是:request.url.replace(request.host_url, 'http://new-domain.com/')
@Ire 我遇到了这个问题并添加了一个编辑来修复。我所做的只是用headers = [(name, value) if (name.lower() != 'location') else (name, value.replace('http://new-domain.com/', request.host_url)) for (name, value) in resp.raw.headers.items() if name.lower() not in excluded_headers]
替换了标题过滤器行。这只是修复了 Location 标头中的 URL。 (感谢@jbasko 指出斜杠的问题)
您还可以流式传输响应内容,而不是完全在服务器上读取它。为此,将上面的 resp.content
替换为 resp.iter_content(chunk_size=10*1024)
并将 content_type=r.headers['Content-Type']
参数添加到 Response
构造函数。【参考方案2】:
我在基于 Werkzeug 的应用程序中使用 httplib 实现了代理(在您的情况下,我需要使用 webapp 的身份验证和授权)。
虽然 Flask 文档没有说明如何访问 HTTP 标头,但您可以使用 request.headers
(请参阅 Werkzeug documentation)。如果您不需要修改响应,并且被代理的应用程序使用的标头是可预测的,那么代理很简单。
注意,如果你不需要修改响应,你应该使用werkzeug.wsgi.wrap_file
来包装httplib的响应流。这允许将打开的操作系统级文件描述符传递给 HTTP 服务器以获得最佳性能。
【讨论】:
谢谢,我今天下午破解了一些东西。但是,由于 httplib 不能很好地处理它们,所以 cookie 存在各种问题。不幸的是,我认为我需要修改响应以进行一些简单的 URL 重写(即我最初的计划是让面向公众的 URL 类似于 http://www.example.com/admin/myapp
代理到 http://myapp.internal.example.com/
。沿着这条路走下去会导致疯狂。
大多数 web 应用程序,尤其是自托管的应用程序,都假定它们将在 HTTP 服务器的根目录下运行,并执行诸如通过绝对路径引用其他文件之类的操作。要解决此问题,您必须在各处重写 URL:位置标头和 html、javascript 和 CSS 文件。
我做了write a Flask proxy blueprint,它做到了这一点,虽然它对于我真正想要代理的一个 web 应用程序来说足够好,但它是不可持续的。这是一大堆正则表达式。
最后,我在 nginx 中设置了一个新的虚拟主机,并使用了它自己的代理。由于两者都位于主机的根目录,因此几乎不需要重写 URL。 (还有一点是必要的,处理的是 nginx 的代理模块。)被代理的 webapp 进行自己的身份验证,这对于现在来说已经足够了。
【讨论】:
“我设置了一个新的虚拟主机”的一些插图会很好。 绝对是你的最后一段。 Flask 的强度不能作为代理,所以如果可能的话,最好避免作为一个代理使用。我能想到的唯一正当理由是某些应用程序逻辑(如身份验证或授权)是必需的,而其他应用程序不支持。以上是关于使用 Flask 代理到另一个 Web 服务的主要内容,如果未能解决你的问题,请参考以下文章
centos部署flask+nginx+uwsgi之踩坑指南
SweetCodeHQ如何为Flask应用程序创建一个Envoy服务代理