如何替换StreamingResponse中的超链接



是否可以替换StreamingResponse中的超链接?

我正在使用以下代码来流式传输HTML内容。

from starlette.requests import Request
from starlette.responses import StreamingResponse
from starlette.background import BackgroundTask
import httpx
client = httpx.AsyncClient(base_url="http://containername:7800/")

async def _reverse_proxy(request: Request):
url = httpx.URL(path=request.url.path, query=request.url.query.encode("utf-8"))
rp_req = client.build_request(
request.method, url, headers=request.headers.raw, content=await request.body()
)
rp_resp = await client.send(rp_req, stream=True)
return StreamingResponse(
rp_resp.aiter_raw(),
status_code=rp_resp.status_code,
headers=rp_resp.headers,
background=BackgroundTask(rp_resp.aclose),
)

app.add_route("/titles/{path:path}", _reverse_proxy, ["GET", "POST"])

它工作正常,但我想更换a href链接
有可能吗?

我已经厌倦了像下面这样包装发电机:

async def adjust_response(iterable):
# Adjust hyperlinks in response.
async for element in iterable.aiter_raw():
yield element.decode("utf-8").replace("/admin", "/gateway/admins/SERVICE_A").encode("utf-8")

但这导致了错误:

h11._util.LocalProtocolError: Too much data for declared Content-Length

一个解决方案显然是读取原始响应生成器(如上面注释部分所述(,修改每个href链接,然后生成修改后的内容。

另一种解决方案是使用JavaScript查找HTML文档中的所有链接,并相应地对其进行修改。如果您可以访问外部服务的HTML文件,则只需添加一个脚本来修改所有href链接,前提是Window.location没有指向服务的主机(例如if (window.location.host != "containername:7800" ) {...}(。即使您不能访问外部HTML文件,您仍然可以在服务器端访问。您可以创建一个StaticFiles实例来为replace.js脚本文件提供服务,只需在HTML页面的<head>部分中使用<script>标记注入该脚本(注意:如果没有提供<head>标记,则查找<html>标记并创建包含<script><head></head>(。当使用window.onload事件加载了整个页面时,或者最好在使用DOMContentLoaded事件完全加载和解析了初始HTML文档时(无需等待样式表、图像等完成加载(,可以运行脚本。使用这种方法,您不必在服务器端遍历每个区块来修改每个href链接,而是注入脚本,然后在客户端进行替换。

顺便说一句,如果传入的请求有一个相当大的主体,无法放入RAM(例如,如果请求中包含大型文件(,并且会导致应用程序速度减慢甚至崩溃,那么与其使用await request.body()将整个主体读取到RAM中,不如使用Starlette的stream()方法将其分块读取(请参阅此答案和此答案(,它返回一个CCD_ 19字节生成器(参见CCD_ 20的流请求文档(;因此,您可以使用:client.build_request(..., content=request.stream())

工作示例:

# ...
from fastapi.staticfiles import StaticFiles
app = FastAPI()
app.mount("/static-js", StaticFiles(directory="static-js"), name="static-js")
client = httpx.AsyncClient(base_url="http://containername:7800/")
async def iter_content(r):
found = False
async for chunk in r.aiter_raw():
if not found:
idx = chunk.find(bytes('<head>', 'utf-8'))
if idx != -1:
found = True
b_arr = bytearray(chunk)
b_arr[idx+6:] = bytes('<script src="/static-js/replace.js"></script>', 'utf-8') + b_arr[idx+6:]
chunk = bytes(b_arr)
yield chunk
async def _reverse_proxy(request: Request):
url = httpx.URL(path=request.url.path, query=request.url.query.encode("utf-8"))
rp_req = client.build_request(
request.method, url, headers=request.headers.raw, content=await request.body()
)
rp_resp = await client.send(rp_req, stream=True)
return StreamingResponse(
iter_content(rp_resp),
status_code=rp_resp.status_code,
headers=rp_resp.headers,
background=BackgroundTask(rp_resp.aclose),
)
app.add_route("/titles/{path:path}", _reverse_proxy, ["GET", "POST"])

JS脚本(replace.js(:

document.addEventListener('DOMContentLoaded', (event) => {
var anchors = document.getElementsByTagName("a");
for (var i = 0; i < anchors.length; i++) {
let path = anchors[i].pathname.replace('/admin', '/admins/SERVICE_A');
anchors[i].href = path + anchors[i].search + anchors[i].hash;
}
});

相关内容

  • 没有找到相关文章

最新更新