# HTTP请求
在Qt中,通常,HTTP请求使用来自c++端的QNetworkRequest
和QNetworkReply
来完成,然后使用Qt/C++将响应推送集成到QML空间中。 因此,在这里尝试稍微突破一下,使用当前Qt Quick提供的工具与网络端点进行通信。 为此,使用辅助对象来发出HTTP请求、响应循环,它以javascript的XMLHttpRequest
对象的形式呈现。
XMLHttpRequest
对象允许用户注册一个响应处理函数和一个URL。 可以使用HTTP动词(get、post、put、delete……)之一发出请求;当响应到达时,处理函数被调用。 处理函数会被多次调用,每次请求状态发生变化时(例如标头已到达或请求已完成)都会被调用。
下面是一个简短的示例:
function request() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
print('HEADERS_RECEIVED');
} else if(xhr.readyState === XMLHttpRequest.DONE) {
print('DONE');
}
}
xhr.open("GET", "http://example.com");
xhr.send();
}
对于响应,可以获取XML格式内容,或仅获取原始文本格式。 可以迭代生成的XML;但现在,更常用的是原始文本格式,可以用于JSON格式的响应。 JSON文档将使用JSON.parse(text)
将文本转换为JS对象。
/* ... */
} else if(xhr.readyState === XMLHttpRequest.DONE) {
var object = JSON.parse(xhr.responseText.toString());
print(JSON.stringify(object, null, 2));
}
在响应处理器中,访问原始响应文本并将其转换为javascript对象。 这个JSON对象现在是一个有效的JS对象(在javascript中,一个js对象可以是一个对象或一个数组)。
提示
似乎首先使用toString()
转换会使代码更稳定。 如果没有这种显式转换,作者发现过几次解析器错误。 但是并不确定是什么原因造成的。
# Flickr调用
看一个更真实的例子。 一个典型的例子是使用Flickr服务来检索新上传图片的公共提要(Feed)。 为此,可以使用http://api.flickr.com/services/feeds/photos_public.gne
URL。 不幸的是,它默认返回的是一个XML流,可以很容易地被qml中的XmlListModel
解析;为了示例的目的,希望专注于JSON数据。 为了获得的是一个干净的JSON响应,需要在请求中附加一些参数:http://api.flickr.com/services/feeds/photos_public.gne?format=json&nojsoncallback=1
,这将返回一个没有JSON回调的JSON响应。
提示
JSON回调将JSON响应包装到函数调用中。 这是HTML编程中使用的快捷方式,其中脚本标签(tag)用于发出JSON请求。 响应将触发回调定义的本地函数。 QML中没有适用于JSON回调的机制。
首先使用curl检查响应:
curl "http://api.flickr.com/services/feeds/photos_public.gne?format=json&nojsoncallback=1&tags=munich"
响应将如下这样:
{
"title": "Recent Uploads tagged munich",
...
"items": [
{
"title": "Candle lit dinner in Munich",
"media": {"m":"http://farm8.staticflickr.com/7313/11444882743_2f5f87169f_m.jpg"},
...
},{
"title": "Munich after sunset: a train full of \"must haves\" =",
"media": {"m":"http://farm8.staticflickr.com/7394/11443414206_a462c80e83_m.jpg"},
...
}
]
...
}
返回的JSON文档具有已定义的结构,是具有标题(title)和项目(items)属性的对象。 其中,title是一个字符串,items是一个对象数组。 将此文本转换为JSON文档时,可以访问各个条目,因为它是一个有效的JS对象/数组结构。
// JS code
obj = JSON.parse(response);
print(obj.title) // => "Recent Uploads tagged munich"
for(var i=0; i<obj.items.length; i++) {
// iterate of the items array entries
print(obj.items[i].title) // title of picture
print(obj.items[i].media.m) // url of thumbnail
}
作为一个有效的JS数组,可以使用obj.items
数组作为列表视图的模型。 现在将努力实现这一目标。 首先,需要检索响应并将其转换为有效的JS对象;然后,可以将response.items
属性设置为列表视图的模型。
function request() {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
print('HEADERS_RECEIVED')
} else if(xhr.readyState === XMLHttpRequest.DONE) {
print('DONE')
const response = JSON.parse(xhr.responseText.toString())
// Set JS object as model for listview
view.model = response.items
}
}
xhr.open("GET", "http://api.flickr.com/services/feeds/photos_public.gne?format=json&nojsoncallback=1&tags=munich")
xhr.send()
}
这是完整的源代码。在加载组件时创建请求,然后将请求响应作为简单列表视图的模型。
import QtQuick
Rectangle {
id: root
width: 320
height: 480
ListView {
id: view
anchors.fill: parent
delegate: Thumbnail {
required property var modelData
width: view.width
text: modelData.title
iconSource: modelData.media.m
}
}
function request() {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
print('HEADERS_RECEIVED')
} else if(xhr.readyState === XMLHttpRequest.DONE) {
print('DONE')
const response = JSON.parse(xhr.responseText.toString())
// Set JS object as model for listview
view.model = response.items
}
}
xhr.open("GET", "http://api.flickr.com/services/feeds/photos_public.gne?format=json&nojsoncallback=1&tags=munich")
xhr.send()
}
Component.onCompleted: {
root.request()
}
}
当文档完全加载时(Component.onCompleted
),从Flickr请求最新的提要(Feed)内容。 结果到达后,解析JSON响应并将items
数组设置为视图的模型。 列表视图有一个代理,它在一行中显示缩略图图标和标题文本。
另一种选择是使用占位符ListModel
并将每个项附加到列表模型上。 为了支持更大的模型,需要支持分页(例如第 1 页,共 10 页
)和惰性内容检索。