resurrected user
resurrected user

Reputation: 492

How to write content to QNetworkReply (was: QWebview with generated content)

I am using a QWebview to display html generated by the same program. Now the html can have references to other resources, e.g. <FRAME src=...> in a fram set. When the browser would start downloading that resource, I must intercept that request and suply the content myself, since there's no webserver involved. Where are the hooks that I may use to catch up the requested url and supply the generated content?

to create the browser widget:

self.browser = QWebView()
self.layout.addWidget(self.browser)

to load the frame set:

self.browser.setHtml(ret.text)

Now what I would expect to find is some signal and then

self.browser.requestURI.connect(myhandler)

But I don't see anything alike it. What is the better approach here?


EDIT:

The major problem seems to be using setHtml. Thus, all loading mechanisms appear to be bypassed. With load() in combination with a QNetworkAccessManager I had better results (see below). Now the response-object is offered to my content manager, however, I sofar failed to write anything to the response object (or to instantiate a fresh one). It can be opened passing an access mode parameter. Then the READ-ONLY error disappears, but still write returns -1.

I rephrase the title of this question accordingly.

from PyQt5.Qt import *  # @UnusedWildImport

class ContentManager(object):
    def handleRequest(self, request, response):
#         response.writeData(bytearray("hello new year", "utf-8")) #THIS WORKS NOT
        return response


class NAM(QNetworkAccessManager):
    def __init__(self, contentManager):
        super().__init__()
        self.contentManager = contentManager

    def createRequest(self, operation, request, device):
        response = super().createRequest(operation, request, device)
        return self.contentManager.handleRequest(request, response)

class Browser(QWidget):
    def __init__(self):
        super().__init__()

    def open(self, url):
        self.browser.load(QUrl(url))

    def build(self, contentManager):
        layout = QVBoxLayout(self)

        view = QWebView()
        page = view.page(); view.setPage(page)
        page.setNetworkAccessManager(NAM(contentManager))
        layout.addWidget(view)

        self.browser = view        

if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)
    w   = Browser()
    w.build(ContentManager())
    w.open("index.html")
    w.show()

    app.exec_() 

Upvotes: 0

Views: 646

Answers (1)

Maurice Meyer
Maurice Meyer

Reputation: 18136

You need to implement a custom QNetworkReply, in general it is possible to serve whatever you want to 'inject' into webview (html, text, images).

I am not sure about local files and frames, but when you are using a 'fake domain' that has to be resolved by QNetworkAccessManager it will work.

The following very simple example works using Python 3.6.3 and PyQt 5.10.0:

from PyQt5.Qt import *  # @UnusedWildImport
import signal, os, sys
signal.signal(signal.SIGINT, signal.SIG_DFL)


class ContentHandler(object):
    def __init__(self, url):
        self.url = url

    def __call__(self):
        print ('ContentHandler >>', self.url)
        path, basename = os.path.split(self.url)

        if basename == 'index.html':
            return b"hello new year", 'text/html'


class DownloadReply(QNetworkReply):
    def __init__(self, parent, operation, request):
        super(DownloadReply, self).__init__(parent)
        self.setRequest(request)
        self.setOperation(operation)
        self.setUrl(request.url())
        self.bytes_read = 0
        self.content = b''

        # give webkit time to connect to the finished and readyRead signals
        QTimer.singleShot(200, self.load_content)

    def load_content(self):
        self.content, self.data = ContentHandler(str(self.url().toString()))()
        self.offset = 0

        self.open(QIODevice.ReadOnly | QIODevice.Unbuffered)
        self.setHeader(QNetworkRequest.ContentTypeHeader, QVariant(self.data))
        self.setHeader(QNetworkRequest.ContentLengthHeader, QVariant(len(self.content)))

        self.readyRead.emit()
        self.finished.emit()

    def abort(self):
        pass

    def isSequential(self):
        return True

    def bytesAvailable(self):
        ba = len(self.content) - self.bytes_read + super(DownloadReply, self).bytesAvailable()
        return ba

    def readData(self, size):
        if self.bytes_read >= len(self.content):
            return None
        data = self.content[self.bytes_read:self.bytes_read + size]
        self.bytes_read += len(data)
        return data

    def manager(self):
        return self.parent()


class NetworkAccessManager(QNetworkAccessManager):
    def __init__(self, parent=None):
        super(NetworkAccessManager, self).__init__(parent=parent)

    def createRequest(self, operation, request, device):
        if str(request.url().host()).lower() == "myfakedom.ain":
            print ('request:', request.url().host())
            return DownloadReply(self, self.GetOperation, request)
        return super(NetworkAccessManager, self).createRequest(operation, request, device)


if __name__ == '__main__':
    app = QApplication(sys.argv)

    webView = QWebView()
    webView.settings().setAttribute(QWebSettings.PluginsEnabled, True)
    webView.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
    webView.settings().setAttribute(QWebSettings.AutoLoadImages, True)
    webView.settings().setAttribute(QWebSettings.JavascriptEnabled, True)

    webInspector = QWebInspector()

    nam = NetworkAccessManager()
    webView.page().setNetworkAccessManager(nam)
    webView.load(QUrl('http://myFakeDom.ain/index.html'))
    webInspector.setPage(webView.page())

    window = QMainWindow()
    window.setCentralWidget(webView)
    window.setFixedSize(1200, 840)
    window.setWindowTitle('Test')
    window.show()

    sys.exit(app.exec_())

Upvotes: 3

Related Questions