简体   繁体   English

如何替换 StreamingResponse 中的超链接?

[英]How to replace hyperlinks in StreamingResponse?

Is that possible to replace hyperlinks in StreamingResponse ?是否可以替换StreamingResponse中的超链接?

I'm using below code to stream HTML content.我正在使用下面的代码来 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"])

It's working fine, but I would like to replace a href links.它工作正常,但我想替换a href链接。
Is that possible?那可能吗?

I've tired to wrap the generator like below:我已经厌倦了像下面这样包装生成器:

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")

but this caused that error:但这导致了该错误:

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

One solution would clearly be to read from the original response generator (as mentioned in the comments section above), modify each href link, and then yield the modified content.一种解决方案显然是从原始响应生成器中读取(如上面评论部分所述),修改每个href链接,然后生成修改后的内容。

Another solution would be to use JavaScript to find all links in the HTML document and modify them accordingly.另一种解决方案是使用 JavaScript 查找 HTML 文档中的所有链接并相应地修改它们。 If you had access to the external service's HTML files, you could just add a script to modify all the href links, only if the Window.location is not pointing to the service's host (eg, if (window.location.host:= "containername.7800" ) {...} ).如果您有权访问外部服务的 HTML 文件,则只需添加一个脚本来修改所有href链接,前提是Window.location未指向服务的主机(例如, if (window.location.host:= "containername.7800" ) {...} )。 Even though you don't have access to the external HTML files, you could still do that on server side.即使您无权访问外部 HTML 文件,您仍然可以在服务器端执行此操作。 You can create a StaticFiles instance to serve a replace.js script file, and simply inject that script using a <script> tag in the <head> section of the HTML page (Note: if no <head> tag is provided, then find the <html> tag and create the <head></head> with the <script> in it).您可以创建一个StaticFiles实例来提供replace.js脚本文件,然后只需在 HTML 页面的<head>部分中使用<script>标记注入该脚本(注意:如果未提供<head>标记,则查找<html>标签并在其中创建<head></head><script> )。 You can have the script run when the whole page has loaded, using window.onload event, or, preferably, when the initial HTML document has been completely loaded and parsed (without waiting for stylesheets, images, etc., to finish loading) using DOMContentLoaded event.您可以使用window.onload事件在整个页面加载后运行脚本,或者最好在初始 HTML 文档已完全加载和解析(无需等待样式表、图像等完成加载)时运行DOMContentLoaded事件。 Using this approach, you don't have to go through each chunk to modify each href link on server side, but rather inject the script and then have the replacement taking place on client side.使用这种方法,您不必通过每个块 go 来修改服务器端的每个href链接,而是注入脚本然后在客户端进行替换。 Example:例子:

# ...
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"])

The JS script ( replace.js ): 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