繁体   English   中英

如何替换 StreamingResponse 中的超链接?

[英]How to replace hyperlinks in StreamingResponse?

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

我正在使用下面的代码来 stream 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>标签并在其中创建<head></head><script> )。 您可以使用window.onload事件在整个页面加载后运行脚本,或者最好在初始 HTML 文档已完全加载和解析(无需等待样式表、图像等完成加载)时运行DOMContentLoaded事件。 使用这种方法,您不必通过每个块 go 来修改服务器端的每个href链接,而是注入脚本然后在客户端进行替换。 例子:

# ...
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
   }
});

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM