QWebEngineView修改请求并获取响应

在自己的Qt程序中想通过QQ邮箱的通讯录来获取好友列表,考虑到登录过程的不确定性,希望效果是在浏览器中打开QQ邮箱登录页面,用户手动登录后浏览器窗口自动关闭,然后程序再请求需要的内容。

好友列表获取过程

打开浏览器控制台,登录QQ邮箱后可以在网络请求中找到一个请求地址为https://mail.qq.com/cgi-bin/laddr_lastlist的请求,响应内容中的有效信息包括好友邮箱地址、昵称、备注、分组。

测试发现url中t=addr_datanew&category=hot&sid=0123456789abcdef和cookie中的sid以及header中的Referer是必需的,其他的内容都可以省略。

响应内容并不是常用的json格式,观察后发现更像是用js代码声明了一个对象放在括号里,双引号会被转义为\x26quot;,其他的emoji表情,unicode字符也是被转义为HTML实体后用\x26代替&\会被转义为\x5c

为什么要用QWebEngineView?

Qt涉及到网络请求的话最先想到的应该是QNetworkAccessManager,程序中的下载队列用的就是这个。
但是概述中提到了”登录过程的不确定性“,具体来说就是我使用QQ帐号登录的时候,最理想的情况是点击登录后就登录成功,但实际情况下偶尔还会弹出滑块验证码,异地登录还有手机短信验证码,只是单次登录不需要记住密码的话直接扫二维码会更方便,因此使用QNetworkAccessManager就会有各种各样的情况要考虑,而且还不知道是否有情况遗漏。

于是就想到了之前在Qt中用过的WebEngine。Qt中的WebEngine是基于Chromium项目的,加上它感觉给项目内置了个浏览器进去,不过考虑到私有项目就自己一个人用,体积大点也不会有太大影响。

如何用QWebEngineView?

参考Qt WebEngine Widgets Module的结构图,QWebEngineView是处于最上面的一层,提供的API也比较少,功能比较丰富的API在QWebEnginePage这层。

根据前面的好友列表获取过程,理想的情况就是弹出浏览器窗口 -> 用户登录成功 -> 页面跳转 -> 浏览器截取目标网络响应 -> 关闭浏览器,页面跳转可以通过QWebEngineView的urlChanged(const QUrl &url)事件检测到,但没找到合适的方法准确地截取某个请求的响应,只能换一个思路:弹出浏览器窗口 -> 用户登录成功 -> 页面跳转 -> 隐藏浏览器窗口 -> 使浏览器主动发送目标请求 -> 获取浏览器当前内容 -> 关闭浏览器,主动发送请求可以调用QWebEngineView的load(const QWebEngineHttpRequest &request),获取浏览器当前内容则是用QWebEnginePage的toHtml(const QWebEngineCallback<const QString &> &resultCallback)

但在实现时还是遇到了坑。

QWebEngineHttpRequest无法设置Referer

主动发送目标请求时需要构造QWebEngineHttpRequest对象,url中的sid可以从登录后跳转url获取,同一个QWebEngineView对象的cookie会自动维护,不需要手动设置,因此就剩下了设置header中的Referer。通过QWebEngineHttpRequest提供的setHeader()方法设置Referer,结果请求还是提示”禁止GET方法调用“。本地nc -l -p 8000进行监听,代码中改为请求本地,发现设置的Referer无效,请求头中并没有Referer项,换成随意的其他项,如setHeader("abc", "123"),却可以正常携带该项。像是bug或者WebEngine限制,但未找到具体说明。
不过在找说明的过程中找到了一个解决办法Referrer HTTP Header no longer ignored when set via RequestInterceptor,通过QWebEnginePage的setUrlRequestInterceptor(QWebEngineUrlRequestInterceptor *interceptor)设置QWebEngineUrlRequestInterceptor拦截发出的目标请求,在这里加上Referer,可以成功发送并得到响应。

toHtml()返回空字符串