# REST API
要使用网络服务,首先需要创建一个。 将使用Flask(https://flask.palletsprojects.com (opens new window))——一个基于python的简单HTTP应用服务器来创建一个简单的颜色web服务。 也可以使用其他所有能接受和返回JSON数据的Web服务器。 这个想法是有一个命了名颜色列表,可以通过网络服务进行管理。 在这种情况下,管理就是意味着CRUD(创建-读取-更新-删除)。
在Flask中,简单的Web服务可以写在一个文件中。 从一个空的server.py
文件开始。 在这个文件中,创建了一些样板代码并从外部JSON文件加载初始颜色。 更多请参阅Flask快速入门 (opens new window)文档。
from flask import Flask, jsonify, request
import json
with open('colors.json', 'r') as file:
colors = json.load(file)
app = Flask(__name__)
# Services registration & implementation...
if __name__ == '__main__':
app.run(debug = True)
当运行这个脚本时,它会在http://localhost:5000 (opens new window)上提供一个网络服务器,它现在还没有提供任何有用的东西。
现在将开始将CRUD(创建、读取、更新、删除)端点添加到这个小型Web服务中。
# 读取请求
为了从网络服务器中读取数据,将为所有颜色提供一个GET方法。
@app.route('/colors', methods = ['GET'])
def get_colors():
return jsonify({ "data" : colors })
这将返回/colors
节点下的颜色。 为了测试这一点,可以使用curl创建一个HTTP请求。
curl -i -GET http://localhost:5000/colors
这将把颜色列表作为JSON数据返回。
# 读取条目
要按名称读取单个颜色,提供了位于/colors/<name>
下的详细信息节点。 名称是节点的参数,用于标识单个颜色。
@app.route('/colors/<name>', methods = ['GET'])
def get_color(name):
for color in colors:
if color["name"] == name:
return jsonify(color)
return jsonify({ 'error' : True })
可以再次使用curl对其测试,例如获取红色条目。
curl -i -GET http://localhost:5000/colors/red
它将返回一个颜色条目作为JSON数据。
# 创建条目
到目前为止,只是使用了HTTP的GET方法。 要在服务器端创建条目,将使用POST方法并将新的颜色信息与POST数据一起传递。 节点位置与获取所有颜色使用的相同,但是这次使用的是一个POST请求。
@app.route('/colors', methods= ['POST'])
def create_color():
print('create color')
color = {
'name': request.json['name'],
'value': request.json['value']
}
colors.append(color)
return jsonify(color), 201
Curl足够灵活,允许在POST请求中使用JSON数据作为新条目。
curl -i -H "Content-Type: application/json" -X POST -d '{"name":"gray1","value":"#333"}' http://localhost:5000/colors
# 更新条目
要更新单个条目,使用HTTP的PUT方法。 节点与检索单个颜色条目相同。 颜色更新成功后,将更新后的颜色作为JSON数据返回。
@app.route('/colors/<name>', methods= ['PUT'])
def update_color(name):
for color in colors:
if color["name"] == name:
color['value'] = request.json.get('value', color['value'])
return jsonify(color)
return jsonify({ 'error' : True })
在curl请求中,仅使用JSON数据提供将要更新后的值,然后使用一个命名节点来标识要更新的颜色。
curl -i -H "Content-Type: application/json" -X PUT -d '{"value":"#666"}' http://localhost:5000/colors/red
# 删除条目
删除条目是使用HTTP的DELETE动词来完成的。 它还是与其他单个颜色操作使用相同的节点,但这次是使用HTTP的DELETE。
@app.route('/colors/<name>', methods=['DELETE'])
def delete_color(name):
for color in colors:
if color["name"] == name:
colors.remove(color)
return jsonify(color)
return jsonify({ 'error' : True })
此请求看起来类似于针对单个颜色的GET请求。
curl -i -X DELETE http://localhost:5000/colors/red
# HTTP动作
现在可以读取所有颜色、读取特定颜色、创建新颜色、更新颜色和删除颜色。 此外,知道了API的HTTP端点。
# 读取所有
GET http://localhost:5000/colors
# 创建实体
POST http://localhost:5000/colors
# 读取实体
GET http://localhost:5000/colors/${name}
# 更新实体
PUT http://localhost:5000/colors/${name}
# 删除实体
DELETE http://localhost:5000/colors/${name}
小型REST服务器现已完成,现在可以专注于QML和客户端。 为了创建一个易于使用的API,需要将每个操作映射到一个单独的HTTP请求,并向用户提供一个简单的API。
# 客户端REST
为了演示REST客户端,编写了一个小的颜色网格。 颜色网格显示通过HTTP请求从Web服务检索到的颜色。 用户界面提供了以下命令:
- 获取颜色列表
- 创建颜色
- 读取最新的颜色
- 更新最新颜色
- 删除最新的颜色
将API捆绑到一个名为colorservice.js
的JS文件中,并将其作为Service
导入UI。 在服务模块(colorservice.js
)中,创建了一个辅助函数来发出HTTP请求:
function request(verb, endpoint, obj, cb) {
print('request: ' + verb + ' ' + BASE + (endpoint ? '/' + endpoint : ''))
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function() {
print('xhr: on ready state change: ' + xhr.readyState)
if(xhr.readyState === XMLHttpRequest.DONE) {
if(cb) {
var res = JSON.parse(xhr.responseText.toString())
cb(res)
}
}
}
xhr.open(verb, BASE + (endpoint ? '/' + endpoint : ''))
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.setRequestHeader('Accept', 'application/json')
var data = obj ? JSON.stringify(obj) : ''
xhr.send(data)
}
它需要四个参数。 verb
,定义要使用的HTTP动词(GET、POST、PUT、DELETE);第二个参数,用作基础地址后面的后缀节点(例如“http://localhost:5000/colors (opens new window)”);第三个参数,可选的obj,将作为发送到服务的JSON数据。 最后一个参数,定义了响应返回时要调用的回调函数。 回调函数接收带有响应数据的响应对象。 在发送请求之前,通过修改请求头来表明发送和接受JSON数据。
使用这个请求辅助函数,可以实现之前定义的简单命令(创建、读取、更新、删除)。 此代码驻留在服务的实现中:
function getColors(cb) {
// GET http://localhost:5000/colors
request('GET', null, null, cb)
}
function createColor(entry, cb) {
// POST http://localhost:5000/colors
request('POST', null, entry, cb)
}
function getColor(name, cb) {
// GET http://localhost:5000/colors/${name}
request('GET', name, null, cb)
}
function updateColor(name, entry, cb) {
// PUT http://localhost:5000/colors/${name}
request('PUT', name, entry, cb)
}
function deleteColor(name, cb) {
// DELETE http://localhost:5000/colors/${name}
request('DELETE', name, null, cb)
}
在UI中,使用该服务来实现命令。 有一个id为gridModel
的ListModel
元素,它作为GridView
的数据提供者。 这些命令使用UI元素Button
标识。
导入服务所用的库非常简单:
import "colorservice.js" as Service
从服务器中读取颜色列表:
Button {
text: 'Read Colors'
onClicked: {
Service.getColors(function(response) {
print('handle get colors response: ' + JSON.stringify(response))
gridModel.clear()
const entries = response.data
for(let i=0; i<entries.length; i++) {
gridModel.append(entries[i])
}
})
}
}
在服务器上创建一个新的颜色条目:
Button {
text: 'Create New'
onClicked: {
const index = gridModel.count - 1
const entry = {
name: 'color-' + index,
value: Qt.hsla(Math.random(), 0.5, 0.5, 1.0).toString()
}
Service.createColor(entry, function(response) {
print('handle create color response: ' + JSON.stringify(response))
gridModel.append(response)
})
}
}
根据名称读取颜色:
Button {
text: 'Read Last Color'
onClicked: {
const index = gridModel.count - 1
const name = gridModel.get(index).name
Service.getColor(name, function(response) {
print('handle get color response:' + JSON.stringify(response))
message.text = response.value
})
}
}
根据颜色名称更新服务器上的颜色条目:
Button {
text: 'Update Last Color'
onClicked: {
const index = gridModel.count - 1
const name = gridModel.get(index).name
const entry = {
value: Qt.hsla(Math.random(), 0.5, 0.5, 1.0).toString()
}
Service.updateColor(name, entry, function(response) {
print('handle update color response: ' + JSON.stringify(response))
gridModel.setProperty(gridModel.count - 1, 'value', response.value)
})
}
}
按颜色名称删除颜色:
Button {
text: 'Delete Last Color'
onClicked: {
const index = gridModel.count - 1
const name = gridModel.get(index).name
Service.deleteColor(name)
gridModel.remove(index, 1)
}
}
使用REST API进行CRUD(创建、读取、更新、删除)的操作就到此结束。 还有其他可能生成Web服务API的方式。 一种是可以是基于模块的,每个模块都有一个节点;并且可以使用JSON RPC(http://www.jsonrpc.org/ (opens new window))定义API。 当然,基于XML的API也是可能的,但是基于的JSON方法具有很大的优势,因为它的解析是作为JavaScript的一部分内置到QML/JS中的。