11.6 使用 OAuth 验证
OAuth 是一种开放协议,允许从 Web,移动和桌面应用程序的简单和标准方法进行安全授权。OAuth 用于针对常见的 Web 服务(如 Google、Facebook 和 Twitter)验证客户端。
** 注意: **
对于自定义 Web 服务,我们还可以使用标准的 HTTP 身份验证,例如在 get 方法中使用 XMLHttpRequest 用户名和密码(例如 xhr.open(verb, url, true, username, password))
OAuth 目前不是 QML/JS API 的一部分。所以我们需要编写一些 C++ 代码,并将认证导出到 QML/JS 端。另一个问题是访问令牌的安全存储。
这里有一些我们觉得有用的链接:
- http://oauth.net/
- http://hueniverse.com/oauth/
- https://github.com/pipacs/o2
- http://www.johanpaul.com/blog/2011/05/oauth2-explained-with-qt-quick/
11.7 Engin IO
Engin.IO 是由 DIGIA 运行的网络服务。它可以从 Qt/QML 应用程序访问 Engin.IO 的 NoSQL 存储。它是一个基于云的存储对象存储,具有易于访问的 Qt/QML API 和管理控制台。如果要从 QML 应用程序存储云中的一些数据,这将是一个简单的入口路径,具有出色的 QML/JS 支持。
有关进一步的帮助,请参阅 EnginIO 文档。
11.8 Web Sockets
WebSockets 模块提供了 WebSockets 客户端和服务器的 WebSockets 协议。它映射了 Qt/CPP 模块。它允许使用全双工通信通道发送字符串和二进制消息。通常通过与服务器建立 HTTP 连接来建立 websocket,然后服务器将连接 “升级到” WebSocket 连接。
在 Qt/QML 中,我们还可以简单地使用 WebSocket 和 WebSocketServer 对象来创建直接的 websocket 连接。websocket 协议使用 “ws” url 模式或 “wss” 进行安全连接。
我们可以通过对 web 套接字 qml 模块进行导入来使用它。
import Qt.WebSockets 1.0
WebSocket {
id: socket
}
要测试我们的网络套接字,我们将使用 http://websocket.org 的 echo 服务器。
import QtQuick 2.5
import Qt.WebSockets 1.0
Text {
width: 480
height: 48
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
WebSocket {
id: socket
url: "ws://echo.websocket.org"
active: true
onTextMessageReceived: {
text = message
}
onStatusChanged: {
if (socket.status == WebSocket.Error) {
console.log("Error: " + socket.errorString)
} else if (socket.status == WebSocket.Open) {
socket.sendTextMessage("ping")
} else if (socket.status == WebSocket.Closed) {
text += "\nSocket closed"
}
}
}
}
可以看到我们发送 socket.sendTextMessage("ping") 的 ping 消息作为文本字段中的响应。
11.8.1 WS 服务器
我们可以使用 Qt WebSocket 的 C++ 部分轻松创建自己的 WS 服务器,或使用不同的 WS 实现,我觉得这非常有趣。这是有趣的,因为它允许将令人惊叹的 QML 渲染质量与使用大量扩展的 Web 应用服务器连接起来。在这个例子中,我们将使用基于 Node JS 的 Web 套接字服务器使用 ws 模块。为此,我们首先需要安装节点 js。然后创建一个 ws_server 文件夹,并使用节点包管理器 (npm) 安装 ws 包。
代码将在 Node JS 中创建一个简单的回显服务器,将我们的消息回传给我们的 QML 客户端。
$ cd ws_server
$ npm install ws
npm 工具下载并将 ws 包和依赖项安装到本地文件夹中。
server.js 文件将是我们的服务器实现。服务器代码将在端口 3000 上创建一个 Web 套接字服务器,并侦听传入的连接。在传入的连接上,它会发出一个问候语,并等待客户端消息。客户端在套接字上发送的每个消息都将发送回客户端。
var WebSocketServer = require('ws').Server;
var server = new WebSocketServer({ port : 3000 });
server.on('connection', function(socket) {
console.log('client connected');
socket.on('message', function(msg) {
console.log('Message: %s', msg);
socket.send(msg);
});
socket.send('Welcome to Awesome Chat');
});
console.log('listening on port ' + server.options.port);
我们需要习惯 JavaScript 的符号和函数回调。
11.8.2 WS 客户端
在客户端,我们需要一个列表视图来显示消息和一个 TextInput,供用户输入一个新的聊天消息。
我们将在示例中使用白色的标签。
// Label.qml
import QtQuick 2.5
Text {
color: '#fff'
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}
我们的聊天视图是列表视图,文本附加到列表模型。每个条目都使用一行前缀和消息标签显示。 我们使用单元宽度 cw 因子将其分成 24 列。
// ChatView.qml
import QtQuick 2.5
ListView {
id: root
width: 100
height: 62
model: ListModel {}
function append(prefix, message) {
model.append({prefix: prefix, message: message})
}
delegate: Row {
width: root.width
height: 18
property real cw: width/24
Label {
width: cw*1
height: parent.height
text: model.prefix
}
Label {
width: cw*23
height: parent.height
text: model.message
}
}
}
聊天输入只是一个包含彩色边框的简单文本输入。
// ChatInput.qml
import QtQuick 2.5
FocusScope {
id: root
width: 240
height: 32
Rectangle {
anchors.fill: parent
color: '#000'
border.color: '#fff'
border.width: 2
}
property alias text: input.text
signal accepted(string text)
TextInput {
id: input
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 4
anchors.rightMargin: 4
onAccepted: root.accepted(text)
color: '#fff'
focus: true
}
}
当 Web 套接字收到消息时,会将消息附加到聊天视图。这同样适用于状态更改。此外,当用户输入聊天消息时,副本被附加到客户端的聊天视图,并且该消息被发送到服务器。
// ws_client.qml
import QtQuick 2.5
import Qt.WebSockets 1.0
Rectangle {
width: 360
height: 360
color: '#000'
ChatView {
id: box
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: input.top
}
ChatInput {
id: input
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
focus: true
onAccepted: {
print('send message: ' + text)
socket.sendTextMessage(text)
box.append('>', text)
text = ''
}
}
WebSocket {
id: socket
url: "ws://localhost:3000"
active: true
onTextMessageReceived: {
box.append('<', message)
}
onStatusChanged: {
if (socket.status == WebSocket.Error) {
box.append('#', 'socket error ' + socket.errorString)
} else if (socket.status == WebSocket.Open) {
box.append('#', 'socket open')
} else if (socket.status == WebSocket.Closed) {
box.append('#', 'socket closed')
}
}
}
}
我们需要先运行服务器,然后再运行客户端。我们的简单客户端没有重试连接机制。
运行服务器:
$ cd ws_server
$ node server.js
运行客户端:
$ cd ws_client
$ qmlscene ws_client.qml
输入文字并按 Enter 键时,应该会看到类似下图这样的界面。
11.9 本章总结
本章简单讲解了关于 QML 网络的知识。请记住,Qt 目前在 QML 方面拥有更丰富的网络 API。但本章的思想是推动 QML 网络的界限,以及如何与云服务进行集成。