我想将对我的 Flask 应用程序发出的请求代理到机器上本地运行的另一个 Web 服务。我宁愿为此使用 Flask 而不是我们的更高级别的 nginx 实例,以便我们可以重用我们应用程序中内置的现有身份验证系统。我们越能保持这种“单点登录”就越好。
是否有现有的模块或其他代码来执行此操作?试图将 Flask 应用程序连接到 httplib 或 urllib 之类的东西被证明是一种痛苦。
我花了很多时间在同一件事上工作,并最终找到了一个使用请求库的解决方案,看起来效果很好。它甚至可以处理在一个响应中设置多个 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.example'),
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 定义的所有“逐跳标头”。
我在基于 Werkzeug 的应用程序中使用 httplib 实现了代理(在您的情况下,我需要使用 webapp 的身份验证和授权)。
尽管 Flask 文档没有说明如何访问 HTTP 标头,但您可以使用 request.headers
(请参阅 Werkzeug documentation)。如果您不需要修改响应,并且代理应用程序使用的标头是可预测的,那么代理是直接的。
请注意,如果您不需要修改响应,则应使用 werkzeug.wsgi.wrap_file
包装 httplib 的响应流。这允许将开放的操作系统级文件描述符传递给 HTTP 服务器以获得最佳性能。
我最初的计划是让面向公众的 URL 类似于 http://www.example.com/admin/myapp
代理到 http://myapp.internal.example.com/
。沿着这条路走下去会导致疯狂。
大多数 webapp,尤其是自托管的,都假设它们将在 HTTP 服务器的根目录下运行,并执行诸如通过绝对路径引用其他文件之类的事情。要解决这个问题,您必须在各处重写 URL:位置标头和 HTML、JavaScript 和 CSS 文件。
我做了write a Flask proxy blueprint,它做到了这一点,虽然它对于我真正想要代理的一个 web 应用程序来说足够好,但它是不可持续的。这是一大堆正则表达式。
最后,我在 nginx 中设置了一个新的虚拟主机,并使用了它自己的代理。由于两者都位于主机的根目录,因此几乎不需要重写 URL。 (几乎不需要什么,处理的是 nginx 的代理模块。)被代理的 webapp 进行自己的身份验证,这对于现在来说已经足够好了。
不定期副业成功案例分享
request.host_url
包括http://
和一个斜杠,所以我的替换行是:request.url.replace(request.host_url, 'http://new-domain.com/')
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
构造函数。