QtWebEngine 使用自定义 URL 模式服务回复拦截请求

QtWebEngine intercepting request with custom URL schema serving reply

我正在尝试使用自定义 URL 方案重写多个 HTTP 请求:
http://static.foo.bar 的所有请求都应重新写入 static://... 并提供一些回复。

问题:
拦截和重定向似乎有效,但无论我的 QWebEngineUrlSchemeHandler 实现返回什么(图像或 html)总是替换完整的 HTML 页面。

预期结果:
SchemeHandler 提供的示例图片 /tmp/iphone.jpg 嵌入在 HTML 页面中,因此 HTML 显示 <h1> 标题和 2 张图片。

版本:
Python3.7.4
PyQt:5.14.1

示例代码:

import sys
import signal
from PyQt5 import QtCore
from PyQt5.QtCore import QUrl, QObject
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings, QWebEnginePage, QWebEngineProfile
from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor, QWebEngineUrlSchemeHandler, QWebEngineUrlScheme
from PyQt5.QtWidgets import QApplication, QMainWindow


class WebEngineUrlRequestInterceptor(QWebEngineUrlRequestInterceptor):
    # Everything requested from static.foo.bar goes to static://
    # //static.foo.bar/1/2/4.jpeg >> static://1/2/4.jpeg
    def interceptRequest(self, info):
        print("interceptRequest")
        print(info.requestUrl())
        if 'static.foo.bar' in str(info.requestUrl()):
            url = QUrl()
            url.setScheme(MyWebEngineUrlScheme.scheme.decode())
            url.setHost('baz.jpg')
            print('Intercepting and redirecting to: %s' % url)
            info.redirect(url)


class MyWebEnginePage(QWebEnginePage):
    # debugging
    def acceptNavigationRequest(self, url, _type, isMainFrame):
        print("acceptNavigationRequest: %s" % url)
        return QWebEnginePage.acceptNavigationRequest(self, url, _type, isMainFrame)


class SchemeHandler(QWebEngineUrlSchemeHandler):
    def __init__(self, app):
        super().__init__(app)

    def requestStarted(self, request):
        url = request.requestUrl()
        print('SchemeHandler requestStarted: %s' % url)

        # Returns a sample image
        raw_html = open('/tmp/iphone.jpg', 'rb').read()
        buf = QtCore.QBuffer(parent=self)
        request.destroyed.connect(buf.deleteLater)
        buf.open(QtCore.QIODevice.WriteOnly)
        buf.write(raw_html)
        buf.seek(0)
        buf.close()
        request.reply(b"image/jpeg", buf)
        return


class MyWebEngineUrlScheme(QObject):
    # Register scheme
    scheme = b"static"

    def __init__(self, parent=None):
        super().__init__(parent)
        scheme = QWebEngineUrlScheme(MyWebEngineUrlScheme.scheme)
        QWebEngineUrlScheme.registerScheme(scheme)
        self.m_functions = dict()

    def init_handler(self, profile=None):
        if profile is None:
            profile = QWebEngineProfile.defaultProfile()
        handler = profile.urlSchemeHandler(MyWebEngineUrlScheme.scheme)
        if handler is not None:
            profile.removeUrlSchemeHandler(handler)

        self.handler = SchemeHandler(self)
        print("registering %s to %s" % (MyWebEngineUrlScheme.scheme, self.handler))
        profile.installUrlSchemeHandler(MyWebEngineUrlScheme.scheme, self.handler)


schemeApp = MyWebEngineUrlScheme()
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QApplication(sys.argv)
win = QMainWindow()
win.resize(800, 600)

html = """
<html>
<body>
<h1>test</h1>
<hr>
<p>First image</p>
<img src="http://static.foo.bar/baz.jpg" />
<hr>
<p>Second image</p>
<img src="https://store.storeimages.cdn-apple.com/4668/as-images.apple.com/is/iphone-xr-red-select-201809?wid=1200&hei=630&fmt=jpeg&qlt=95&op_usm=0.5,0.5&.v=1551226038669" />
</body>
</html>
"""

browser = QWebEngineView()
interceptor = WebEngineUrlRequestInterceptor()
profile = QWebEngineProfile()
profile.setUrlRequestInterceptor(interceptor)
page = MyWebEnginePage(profile, browser)
schemeApp.init_handler(profile)

browser.settings().setAttribute(QWebEngineSettings.PluginsEnabled, True)
browser.settings().setAttribute(QWebEngineSettings.JavascriptCanOpenWindows, False)
browser.settings().setAttribute(QWebEngineSettings.LinksIncludedInFocusChain, False)
browser.settings().setAttribute(QWebEngineSettings.LocalStorageEnabled, True)
browser.settings().setAttribute(QWebEngineSettings.JavascriptEnabled, True)

page.setHtml(html)
browser.setPage(page)
browser.show()

win.setCentralWidget(browser)
win.show()

sys.exit(app.exec_())

如果您分析页面请求的 urls,您会得到:

PyQt5.QtCore.QUrl('data:text/html;charset=UTF-8,%0A%3Chtml%3E%0A%3Cbody%3E%0A%3Ch1%3Etest%3C%2Fh1%3E%0A%3Chr%3E%0A%3Cp%3EFirst image%3C%2Fp%3E%0A%3Cimg src%3D%22http%3A%2F%2F<b>static.foo.bar</b>%2Fbaz.jpg%22 %2F%3E%0A%3Chr%3E%0A%3Cp%3ESecond image%3C%2Fp%3E%0A%3Cimg src%3D%22https%3A%2F%2Fstore.storeimages.cdn-apple.com%2F4668%2Fas-images.apple.com%2Fis%2Fiphone-xr-red-select-201809%3Fwid%3D1200%26hei%3D630%26fmt%3Djpeg%26qlt%3D95%26op_usm%3D0.5%2C0.5%26.v%3D1551226038669%22 %2F%3E%0A%3C%2Fbody%3E%0A%3C%2Fhtml%3E%0A')
PyQt5.QtCore.QUrl('http://<b>static.foo.bar</b>/baz.jpg')
PyQt5.QtCore.QUrl('https://store.storeimages.cdn-apple.com/4668/as-images.apple.com/is/iphone-xr-red-select-201809?wid=1200&hei=630&fmt=jpeg&qlt=95&op_usm=0.5,0.5&.v=1551226038669')

第一个和第二个满足条件 if 'static.foo.bar' in str(info.requestUrl()): url((为了更好的可视化,我将其标记为粗体)。

解决方法是改进过滤器:

class WebEngineUrlRequestInterceptor(QWebEngineUrlRequestInterceptor):
    # Everything requested from static.foo.bar goes to static://
    # //static.foo.bar/1/2/4.jpeg >> static://1/2/4.jpeg
    def interceptRequest(self, info):
        print("interceptRequest")
        print(info.requestUrl())
        if info.requestUrl().host().startswith("static.foo.bar"): # or if info.requestUrl().host() == "static.foo.bar":
            url = QUrl()
            url.setScheme(MyWebEngineUrlScheme.scheme.decode())
            url.setHost(info.requestUrl().path()[1:])  # remove "/"
            print("Intercepting and redirecting to: %s" % url)
            info.redirect(url)