QML Book 第十一章 网络 2

11.5 REST API

要使用网络服务,我们首先需要创建一个。我们将使用 Flask(http://flask.pocoo.org) 一个基于 python 的简单的 HTTP 应用服务器来创建一个简单的彩色 Web 服务。我们还可以使用接受并返回 JSON 数据的其他所有 Web 服务器。具体的想法是具有可以通过 Web 服务进行管理的命名颜色列表。在这种情况下管理是指 CRUD(create-read-update-delete)。

Flask 中的一个简单的 Web 服务可以写在一个文件中。我们从空的 server.py 文件开始。在这个文件中,我们创建了一些 boiler-code,并从外部 JSON 文件加载了我们的初始颜色。另请参阅 Flask 快速入门文档。

from flask import Flask, jsonify, request
import json

colors = json.load(file('colors.json', 'r'))

app = Flask(__name__)

# ... service calls go here

if __name__ == '__main__':
    app.run(debug = True)

当我们运行此脚本时,它将提供一个 http://localhost:5000 的 Web 服务器,但是目前它不提供任何有用的东西。

我们现在将开始将我们的 CRUD(创建,读取,更新,删除)端点添加到我们的小型 Web 服务中。

11.5.1 读取请求

要从我们的 Web 服务器读取数据,我们将为所有颜色提供 GET 方法。

@app.route('/colors', methods = ['GET'])
def get_colors():
    return jsonify( { "colors" :  colors })

这将返回 “/colors” 端点下的颜色。为了测试这个功能,我们可以使用 curl 创建 http 请求。

curl -i -GET http://localhost:5000/colors

这将把我们的颜色列表作为 JSON 数据返回给我们。

11.5.2 读取条目

要按名称读取单个颜色,我们提供位于 “/colors/<name>” 下方的详细信息终点。该名称是端点的参数,用于标识单个颜色。

@app.route('/colors/<name>', methods = ['GET'])
def get_color(name):
    for color in colors:
        if color["name"] == name:
            return jsonify( color )

我们可以再次使用curl来测试它。例如获取红色条目:

curl -i -GET http://localhost:5000/colors/red

它将返回一个颜色条目作为 JSON 数据。

11.5.3 创建条目

到目前为止,我们刚刚使用 HTTP GET 方法。要在服务器端创建一个条目,我们将使用 POST 方法,并使用 POST 数据传递新的颜色信息。端点位置与获取所有颜色相同。但是这次我们期待一个 POST 请求。

@app.route('/colors', methods= ['POST'])
def create_color():
    color = {
        'name': request.json['name'],
        'value': request.json['value']
    }
    colors.append(color)
    return jsonify( color ), 201

Curl 具有足够的灵活性,允许我们提供 JSON 数据作为 POST 请求中的新条目。

curl -i -H "Content-Type: application/json" -X POST -d '{"name":"gray1","value":"#333"}' http://localhost:5000/colors

11.5.4 更新条目

要更新信息条目,我们使用 PUT HTTP 方法。端点与检索单个颜色条目相同。当颜色更新成功时,我们将更新的颜色作为 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 )

在 curl 请求中,我们仅提供要作为 JSON 数据更新的值和命名的端点,以标识要更新的颜色。

curl -i -H "Content-Type: application/json" -X PUT -d '{"value":"#666"}' http://localhost:5000/colors/red

11.5.5 删除条目

使用 DELETE HTTP 动词删除条目。它也为单个颜色使用相同的端点,但这次是 DELETE HTTP 动词。

@app.route('/colors/<name>', methods=['DELETE'])
def delete_color(name):
    success = False
    for color in colors:
        if color["name"] == name:
            colors.remove(color)
            success = True
    return jsonify( { 'result' : success } )

该请求看起来类似于单个颜色的 GET 请求。

curl -i -X DELETE http://localhost:5000/colors/red

现在我们可以读取所有颜色,读取指定颜色,创建新的指定颜色,更新指定颜色和删除指定颜色。我们也知道我们的 API 的 HTTP 端点。

活动 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。

11.5.6 客户端 REST

为了演示一个 REST 客户端,我们编写一个小的颜色网格。颜色网格通过 HTTP 请求显示从 Web 服务检索的颜色。我们的用户界面提供以下命令:

  • Get color list
  • Create color
  • Read last color
  • Update last color
  • Delete last color

我们将 API 绑定到一个名为 colorservice.js 的 JS 文件中,并将其导入到我们的 UI 中作为服务。在服务模块中,我们创建了一个帮助函数来为我们提供 HTTP 请求:

// colorservice.js
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)。第二个参数是用作 BASE 地址的后缀的端点(例如 “http://localhost:5000/colors”)。第三个参数是可选的 obj,将作为 JSON 数据发送到服务器。当响应返回时,最后一个参数定义要调用的回调。回调接收到具有响应数据的响应对象。在我们发送请求之前,我们指出我们通过修改请求头来发送和接受 JSON 数据。

使用这个请求帮助函数,我们可以实现我们之前定义的简单命令(create,read,update,delete):

// colorservice.js
function get_colors(cb) {
    // GET http://localhost:5000/colors
    request('GET', null, null, cb)
}

function create_color(entry, cb) {
    // POST http://localhost:5000/colors
    request('POST', null, entry, cb)
}

function get_color(name, cb) {
    // GET http://localhost:5000/colors/<name>
    request('GET', name, null, cb)
}

function update_color(name, entry, cb) {
    // PUT http://localhost:5000/colors/<name>
    request('PUT', name, entry, cb)
}

function delete_color(name, cb) {
    // DELETE http://localhost:5000/colors/<name>
    request('DELETE', name, null, cb)
}

该代码驻留在服务实现中。在 UI 中,我们使用该服务来实现我们的命令。我们有一个 ListModel,它的 id 为 gridModel,作为 GridView 的数据提供者。使用 Button ui 元素指示命令。

从服务器读取颜色列表。

// rest.qml
import "colorservice.js" as Service
...
// read colors command
Button {
    text: 'Read Colors';
    onClicked: {
        Service.get_colors( function(resp) {
            print('handle get colors resp: ' + JSON.stringify(resp));
            gridModel.clear();
            var entries = resp.data;
            for(var i=0; i<entries.length; i++) {
                gridModel.append(entries[i]);
            }
        });
    }
}

在服务器上创建一个新的颜色条目。

// rest.qml
import "colorservice.js" as Service
...
// create new color command
Button {
    text: 'Create New';
    onClicked: {
        var index = gridModel.count-1
        var entry = {
            name: 'color-' + index,
            value: Qt.hsla(Math.random(), 0.5, 0.5, 1.0).toString()
        }
        Service.create_color(entry, function(resp) {
            print('handle create color resp: ' + JSON.stringify(resp))
            gridModel.append(resp)
        });
    }
}

根据其名称读取颜色。

// rest.qml
import "colorservice.js" as Service
...
// read last color command
Button {
    text: 'Read Last Color';
    onClicked: {
        var index = gridModel.count-1
        var name = gridModel.get(index).name
        Service.get_color(name, function(resp) {
            print('handle get color resp:' + JSON.stringify(resp))
            message.text = resp.value
        });
    }
}

根据颜色名称更新服务器上的颜色条目。

// rest.qml
import "colorservice.js" as Service
...
// update color command
Button {
    text: 'Update Last Color'
    onClicked: {
        var index = gridModel.count-1
        var name = gridModel.get(index).name
        var entry = {
            value: Qt.hsla(Math.random(), 0.5, 0.5, 1.0).toString()
        }
        Service.update_color(name, entry, function(resp) {
            print('handle update color resp: ' + JSON.stringify(resp))
            var index = gridModel.count-1
            gridModel.setProperty(index, 'value', resp.value)
        });
    }
}

按颜色名称删除颜色。

// rest.qml
import "colorservice.js" as Service
...
// delete color command
Button {
    text: 'Delete Last Color'
    onClicked: {
        var index = gridModel.count-1
        var name = gridModel.get(index).name
        Service.delete_color(name)
        gridModel.remove(index, 1)
    }
}

使用 REST API 来完成 CRUD(创建,读取,更新,删除)操作。还有其他可能性来生成 Web-Service API。可以是一个基于模块的,每个模块都有一个端点。 并且可以使用 JSON RPC(http://www.jsonrpc.org/)定义 API。当然,基于 XML 的 API 也是可行的,但是 JSON 方法具有很大的优势,因为解析是作为 JavaScript 的一部分直接构建到 QML/JS 中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容