# 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为gridModelListModel元素,它作为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中的。

最后更新: 1/23/2022, 11:46:02 PM