REST API

To use a web-service, we first need to create one. We will use Flask (https://flask.palletsprojects.comREST API - 图1 (opens new window)) a simple HTTP app server based on python to create a simple color web-service. You could also use every other web server which accepts and returns JSON data. The idea is to have a list of named colors, which can be managed via the web-service. Managed in this case means CRUD (create-read-update-delete).

A simple web-service in Flask can be written in one file. We start with an empty server.py file. Inside this file, we create some boiler-code and load our initial colors from an external JSON file. See also the Flask quickstartREST API - 图2 (opens new window) documentation.

  1. from flask import Flask, jsonify, request
  2. import json
  3. colors = json.load(file('colors.json', 'r'))
  4. app = Flask(__name__)
  5. ... service calls go here
  6. if __name__ == '__main__':
  7. app.run(debug = True)

When you run this script, it will provide a web-server at http://localhost:5000REST API - 图3 (opens new window), which does not serve anything useful yet.

We will now start adding our CRUD (Create,Read,Update,Delete) endpoints to our little web-service.

Read Request

To read data from our web-server, we will provide a GET method for all colors.

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

This will return the colors under the ‘/colors’ endpoint. To test this we can use curl to create an HTTP request.

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

Which will return us the list of colors as JSON data.

Read Entry

To read an individual color by name we provide the details endpoint, which is located under /colors/<name>. The name is a parameter to the endpoint, which identifies an individual color.

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

And we can test it with using curl again. For example to get the red color entry.

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

It will return one color entry as JSON data.

Create Entry

Till now we have just used HTTP GET methods. To create an entry on the server side, we will use a POST method and pass the new color information with the POST data. The endpoint location is the same as to get all colors. But this time we expect a POST request.

  1. @app.route('/colors', methods= ['POST'])
  2. def create_color():
  3. color = {
  4. 'name': request.json['name'],
  5. 'value': request.json['value']
  6. }
  7. colors.append(color)
  8. return jsonify( color ), 201

Curl is flexible enough to allow us to provide JSON data as the new entry inside the POST request.

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

Update Entry

To update an individual entry we use the PUT HTTP method. The endpoint is the same as to retrieve an individual color entry. When the color was updated successfully we return the updated color as JSON data.

  1. @app.route('/colors/<name>', methods= ['PUT'])
  2. def update_color(name):
  3. for color in colors:
  4. if color["name"] == name:
  5. color['value'] = request.json.get('value', color['value'])
  6. return jsonify( color )

In the curl request, we only provide the values to be updated as JSON data and then a named endpoint to identify the color to be updated.

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

Delete Entry

Deleting an entry is done using the DELETE HTTP verb. It also uses the same endpoint for an individual color, but this time the DELETE HTTP verb.

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

This request looks similar to the GET request for an individual color.

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

HTTP Verbs

Now we can read all colors, read a specific color, create a new color, update a color and delete a color. Also, we know the HTTP endpoints to our API.

  1. # Read All
  2. GET http://localhost:5000/colors
  3. # Create Entry
  4. POST http://localhost:5000/colors
  5. # Read Entry
  6. GET http://localhost:5000/colors/${name}
  7. # Update Entry
  8. PUT http://localhost:5000/colors/${name}
  9. # Delete Entry
  10. DELETE http://localhost:5000/colors/${name}

Our little REST server is complete now and we can focus on QML and the client side. To create an easy to use API we need to map each action to an individual HTTP request and provide a simple API to our users.

Client REST

To demonstrate a REST client we write a small color grid. The color grid displays the colors retrieved from the web-service via HTTP requests. Our user interface provides the following commands:

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

We bundle our API into an own JS file called colorservice.js and import it into our UI as Service. Inside the service module, we create a helper function to make the HTTP requests for us:

  1. // colorservice.js
  2. function request(verb, endpoint, obj, cb) {
  3. print('request: ' + verb + ' ' + BASE + (endpoint?'/' + endpoint:''))
  4. var xhr = new XMLHttpRequest();
  5. xhr.onreadystatechange = function() {
  6. print('xhr: on ready state change: ' + xhr.readyState)
  7. if(xhr.readyState === XMLHttpRequest.DONE) {
  8. if(cb) {
  9. var res = JSON.parse(xhr.responseText.toString())
  10. cb(res);
  11. }
  12. }
  13. }
  14. xhr.open(verb, BASE + (endpoint?'/' + endpoint:''));
  15. xhr.setRequestHeader('Content-Type', 'application/json');
  16. xhr.setRequestHeader('Accept', 'application/json');
  17. var data = obj?JSON.stringify(obj):''
  18. xhr.send(data)
  19. }

It takes four arguments. The verb, which defines the HTTP verb to be used (GET, POST, PUT, DELETE). The second parameter is the endpoint to be used as a postfix to the BASE address (e.g. ‘http://localhost:5000/colorsREST API - 图4 (opens new window)’). The third parameter is the optional obj, to be sent as JSON data to the service. The last parameter defines a callback to be called when the response returns. The callback receives a response object with the response data. Before we send the request, we indicate that we send and accept JSON data by modifying the request header.

Using this request helper function we can implement the simple commands we defined earlier (create, read, update, delete):

  1. // colorservice.js
  2. function get_colors(cb) {
  3. // GET http://localhost:5000/colors
  4. request('GET', null, null, cb)
  5. }
  6. function create_color(entry, cb) {
  7. // POST http://localhost:5000/colors
  8. request('POST', null, entry, cb)
  9. }
  10. function get_color(name, cb) {
  11. // GET http://localhost:5000/colors/${name}
  12. request('GET', name, null, cb)
  13. }
  14. function update_color(name, entry, cb) {
  15. // PUT http://localhost:5000/colors/${name}
  16. request('PUT', name, entry, cb)
  17. }
  18. function delete_color(name, cb) {
  19. // DELETE http://localhost:5000/colors/${name}
  20. request('DELETE', name, null, cb)
  21. }

This code resides in the service implementation. In the UI we use the service to implement our commands. We have a ListModel with the id gridModel as a data provider for the GridView. The commands are indicated using a Button UI element.

Reading the color list from the server.

  1. // rest.qml
  2. import "colorservice.js" as Service
  3. ...
  4. // read colors command
  5. Button {
  6. text: 'Read Colors';
  7. onClicked: {
  8. Service.get_colors( function(resp) {
  9. print('handle get colors resp: ' + JSON.stringify(resp));
  10. gridModel.clear();
  11. var entries = resp.data;
  12. for(var i=0; i<entries.length; i++) {
  13. gridModel.append(entries[i]);
  14. }
  15. });
  16. }
  17. }

Create a new color entry on the server.

  1. // rest.qml
  2. import "colorservice.js" as Service
  3. ...
  4. // create new color command
  5. Button {
  6. text: 'Create New';
  7. onClicked: {
  8. var index = gridModel.count-1
  9. var entry = {
  10. name: 'color-' + index,
  11. value: Qt.hsla(Math.random(), 0.5, 0.5, 1.0).toString()
  12. }
  13. Service.create_color(entry, function(resp) {
  14. print('handle create color resp: ' + JSON.stringify(resp))
  15. gridModel.append(resp)
  16. });
  17. }
  18. }

Reading a color based on its name.

  1. // rest.qml
  2. import "colorservice.js" as Service
  3. ...
  4. // read last color command
  5. Button {
  6. text: 'Read Last Color';
  7. onClicked: {
  8. var index = gridModel.count-1
  9. var name = gridModel.get(index).name
  10. Service.get_color(name, function(resp) {
  11. print('handle get color resp:' + JSON.stringify(resp))
  12. message.text = resp.value
  13. });
  14. }
  15. }

Update a color entry on the server based on the color name.

  1. // rest.qml
  2. import "colorservice.js" as Service
  3. ...
  4. // update color command
  5. Button {
  6. text: 'Update Last Color'
  7. onClicked: {
  8. var index = gridModel.count-1
  9. var name = gridModel.get(index).name
  10. var entry = {
  11. value: Qt.hsla(Math.random(), 0.5, 0.5, 1.0).toString()
  12. }
  13. Service.update_color(name, entry, function(resp) {
  14. print('handle update color resp: ' + JSON.stringify(resp))
  15. var index = gridModel.count-1
  16. gridModel.setProperty(index, 'value', resp.value)
  17. });
  18. }
  19. }

Delete a color by the color name.

  1. // rest.qml
  2. import "colorservice.js" as Service
  3. ...
  4. // delete color command
  5. Button {
  6. text: 'Delete Last Color'
  7. onClicked: {
  8. var index = gridModel.count-1
  9. var name = gridModel.get(index).name
  10. Service.delete_color(name)
  11. gridModel.remove(index, 1)
  12. }
  13. }

This concludes the CRUD (create, read, update, delete) operations using a REST API. There are also other possibilities to generate a Web-Service API. One could be module based and each module would have one endpoint. And the API could be defined using JSON RPC (http://www.jsonrpc.org/REST API - 图5 (opens new window)). Sure also XML based API is possible and but the JSON approach has great advantages as the parsing is built into the QML/JS as part of JavaScript.