In recent years REST (REpresentational State Transfer) has become a web services and web APIs as standard.
In this article I will show you how easy it is to use Python and Flask framework to create a RESTful web service.
What is REST?
Six design specification defines the characteristics of a REST system:
- Client - Server : isolation between the client and the server, the server provides services to clients to consume.
- Stateless : each request from the client to the server the request must contain the information necessary for understanding. In other words, information is not stored on the server a client request for a next use.
- Cacheable : The server must express the client request can cache.
- Layer system : a communication between the client and the server should be in a standard manner, that is, the intermediate layer in place when the server responds, the client does not need to make any changes.
- Unified interface : server and client communication methods must be unified.
- Demand coding : The server can provide executable code or script for the client to perform in their environment. This is the only constraint is optional.
What is a RESTful web service?
The original purpose of REST architecture is adapted to the World Wide Web HTTP protocol.
The core RESTful web services concept is "resource." Resources can be URI to represent. HTTP protocol defines client uses the method to send a request to these URIs, of course, can lead to changes in the state of "resource" is accessed.
HTTP standard methods are as follows:
========== ===================== ==================================
HTTP method behavior example
========== ===================== ==================================
GET access to information resources http://example.com/api/orders
GET get information http://example.com/api/orders/123 a particular resource
POST create a new resource http://example.com/api/orders
PUT updates the resource http://example.com/api/orders/123
DELETE delete resources http://example.com/api/orders/123
========== ====================== ==================================
REST design does not require a particular data format. In the request data can be JSON form or url query term is sometimes used as parameters.
Design a simple web service
Adhere to the guidelines for the design of a REST web service API, or a task becomes a resource identifier is shown out and how they are affected by different request methods of practice.
For example, we want to write a to-do application for it and we want to design a web service. The first thing to do is to decide what kind of access the service using the root URL. For example, we can access this by:
http://[hostname]/todo/api/v1.0/
Here I have decided to include the name and version number of the API application in the URL. The application name included in the URL helps provide a namespace to distinguish other services on the same system. Can help contain future updates version number in the URL, if new and potentially incompatible functions exist in the new version, you can not rely on the influence of the older features of the application.
The next step is to select the service by exposure to (show) resources. This is a very simple application, we only have the task, so the only resource is the task of our backlog.
Our task will be to use the HTTP resource as follows:
========== =============================================== =============================
HTTP Method URL action
========== =============================================== ==============================
GET http: // [hostname] /todo/api/v1.0/tasks retrieval task list
GET http: // [hostname] /todo/api/v1.0/tasks/ [task_id] to retrieve a task
POST http: // [hostname] /todo/api/v1.0/tasks create a new task
PUT http: // [hostname] /todo/api/v1.0/tasks/ [task_id] update tasks
DELETE http: // [hostname] /todo/api/v1.0/tasks/ [task_id] Delete Task
========== ================================================ =============================
We define the task has the following attributes:
- the above mentioned id : unique identifier for the task. Digital type.
- title : a brief description of the task. String type.
- the Description : specific tasks described. Text type.
- DONE : the task is completed. Boolean value.
So far about our web service designed basically completed. The remaining thing is to realize it!
About Flask framework
If you read Flask Mega-Tutorial series , you know Flask is a simple but very powerful Python web framework.
Before we delve into the details of web services, let's look at the structure of an ordinary Flask Web applications.
I will first assume you know the basics of Python works on your platform. I will explain examples of work in a Unix-like operating system. In short, this means they can work in Linux, Mac OS X and Windows (if you use Cygwin). If you're using Python version of Windows natively, then the command will be different.
Let us begin the installation Flask in a virtual environment. If you do not virtualenv on your system, you can from https://pypi.python.org/pypi/virtualenv download:
$ Mkdir todo-fire
$ Cd all-api
$ virtualenv flask
New python executable in flask/bin/python
Installing setuptools............................done.
Installing pip...................done.
$ flask/bin/pip install flask
Now that you have installed Flask, now simply create a web application, we put it in a file called app.py in:
#!flask/bin/python
from flask import Flask
app = Flask(__name__) @app.route('/') def index(): return "Hello, World!" if __name__ == '__main__': app.run(debug=True)
To run this program we must perform app.py:
$ chmod a+x app.py
$ ./app.py
* Running on http://127.0.0.1:5000/
* Restarting with reloader
Now you can start your web browser, type http: // localhost: 5000 to see the effect of this small application.
Simple, right? Now we will convert this application into our RESTful service!
Python and Flask implement RESTful services
Flask use to build web services is very simple, than I do in Mega-Tutorial for more than simply a complete application server build in.
There are many extensions with Flask to help us build RESTful services, but in my opinion this task is very simple, no need to use Flask extension.
Our web service clients need to add, delete and modify tasks of service, so obviously we need a way to store tasks. The most direct way is to build a small database, but the database is not the subject of this article. Learn to use the appropriate database Flask, I strongly recommend reading the Mega-the Tutorial .
Here we store directly to the task list, task list, so these will only work in the web server is running in memory, at the end of it fail. This applies only way to develop our own web server, and does not apply to the production environment web server, this is a suitable database structures is necessary.
We now realize the first entry in a web service:
#!flask/bin/python
from flask import Flask, jsonify app = Flask(__name__) tasks = [ { 'id': 1, 'title': u'Buy groceries', 'description': u'Milk, Cheese, Pizza, Fruit, Tylenol', 'done': False }, { 'id': 2, 'title': u'Learn Python', 'description': u'Need to find a good Python tutorial on the web', 'done': False } ] @app.route('/todo/api/v1.0/tasks', methods=['GET']) def get_tasks(): return jsonify({'tasks': tasks}) if __name__ == '__main__': app.run(debug=True)
As you can see, there is not much change. Our task is to create a memory database, this is nothing more than a dictionary and arrays. Properties task in the array with each element defined above.
Replaced the home page, we now have a get_tasks function, URI access to /todo/api/v1.0/tasks, and only allow the HTTP GET method.
This response is not a function of the text, we used data format in response JSON, in the Flask jsonify function generation from our data structure.
Use a Web browser to test our web service is not the best attention, because the method can not simulate all HTTP requests easily on the web browser. Instead, we will use the curl. If you have not curl installed, install it immediately.
By executing app.py, start web service. Then open a new console window, run the following command:
$ curl -i http://localhost:5000/todo/api/v1.0/tasks
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 294
Server: tool / 0.8.3 Python / 2.7.3
Date: Mon, 20 May 2013 04:53:53 GMT
{
"tasks": [
{
"description": "Milk, Cheese, Pizza, Fruit, Tylenol",
"done": false,
"id": 1,
"title": "Buy groceries"
},
{
"description": "Need to find a good Python tutorial on the web",
"done": false,
"id": 2,
"title": "Learn Python"
}
]
}
We have successfully call a function of our RESTful service!
Now we start writing the second version of the GET method request our mission resources. This is a function that returns a single task:
from flask import abort
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['GET']) def get_task(task_id): task = filter(lambda t: t['id'] == task_id, tasks) if len(task) == 0: abort(404) return jsonify({'task': task[0]})
The second function some meaning. Here we get the id URL in the task, and then convert it into Flask task_id parameters of function.
We use this parameter to search for our task array. If there is no id search our database, we will return an error similar to 404, according to the HTTP specification means "Resource not found."
If we find the task, so we just use it jsonify packaged into JSON format and sends it as a response, just as we handle the entire task set before.
The results call curl request are as follows:
$ curl -i http://localhost:5000/todo/api/v1.0/tasks/2
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 151
Server: tool / 0.8.3 Python / 2.7.3
Date: Mon, 20 May 2013 05:21:50 GMT
{
"task": {
"description": "Need to find a good Python tutorial on the web",
"done": false,
"id": 2,
"title": "Learn Python"
}
}
$ curl -i http://localhost:5000/todo/api/v1.0/tasks/3
HTTP/1.0 404 NOT FOUND
Content-Type: text/html
Content-Length: 238
Server: tool / 0.8.3 Python / 2.7.3
Date: Mon, 20 May 2013 05:21:52 GMT
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server.</p><p>If you entered the URL manually please check your spelling and try again.</p>
When we requested resource id # 2, we get to, but when we requested # 3 returns a 404 error. The strange thing is about the error returns HTML information rather than JSON, because Flask generated 404 response according to the default mode. Since this is a Web service clients want us to always respond in JSON format, so we need to improve our 404 error handler:
from flask import make_response
@app.errorhandler(404) def not_found(error): return make_response(jsonify({'error': 'Not found'}), 404)
We will get a friendly error message:
$ curl -i http://localhost:5000/todo/api/v1.0/tasks/3
HTTP/1.0 404 NOT FOUND
Content-Type: application/json
Content-Length: 26
Server: tool / 0.8.3 Python / 2.7.3
Date: Mon, 20 May 2013 05:36:54 GMT
{
"error": "Not found"
}
Then there is the POST method, we used to insert a new task in our job database:
from flask import request
@app.route('/todo/api/v1.0/tasks', methods=['POST']) def create_task(): if not request.json or not 'title' in request.json: abort(400) task = { 'id': tasks[-1]['id'] + 1, 'title': request.json['title'], 'description': request.json.get('description', ""), 'done': False } tasks.append(task) return jsonify({'task': task}), 201
Add a new task is quite easy. Only when the request form in JSON format, request.json will have the requested data. If there is no data, or data but there is a lack of title item, we will return 400, which is a request was invalid.
Then we will create a new task dictionary, using the last of a task id + 1 as the task id. We allow description field is missing, and it is assumed done field to False.
We add a new task to our task array, and to add new tasks and state 201 in response to the client.
Use the following curl command to test the new functions:
$ curl -i -H "Content-Type: application/json" -X POST -d '{"title":"Read a book"}' http://localhost:5000/todo/api/v1.0/tasks
HTTP/1.0 201 Created
Content-Type: application/json
Content-Length: 104
Server: tool / 0.8.3 Python / 2.7.3
Date: Mon, 20 May 2013 05:56:21 GMT
{
"task": {
"description": "",
"done": false,
"id": 3,
"title": "Read a book"
}
}
Note: If you and run Cygwin version of curl on Windows, the above command will not have any problems. However, if you use the curl native, command a little different:
curl -i -H "Content-Type: application/json" -X POST -d "{"""title""":"""Read a book"""}" http://localhost:5000/todo/api/v1.0/tasks
Of course, after the completion of this request, we can get the updated list of tasks:
$ curl -i http://localhost:5000/todo/api/v1.0/tasks
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 423
Server: tool / 0.8.3 Python / 2.7.3
Date: Mon, 20 May 2013 05:57:44 GMT
{
"tasks": [
{
"description": "Milk, Cheese, Pizza, Fruit, Tylenol",
"done": false,
"id": 1,
"title": "Buy groceries"
},
{
"description": "Need to find a good Python tutorial on the web",
"done": false,
"id": 2,
"title": "Learn Python"
},
{
"description": "",
"done": false,
"id": 3,
"title": "Read a book"
}
]
}
The remaining two functions are as follows:
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['PUT']) def update_task(task_id): task = filter(lambda t: t['id'] == task_id, tasks) if len(task) == 0: abort(404) if not request.json: abort(400) if 'title' in request.json and type(request.json['title']) != unicode: abort(400) if 'description' in request.json and type(request.json['description']) is not unicode: abort(400) if 'done' in request.json and type(request.json['done']) is not bool: abort(400) task[0]['title'] = request.json.get('title', task[0]['title']) task[0]['description'] = request.json.get('description', task[0]['description']) task[0]['done'] = request.json.get('done', task[0]['done']) return jsonify({'task': task[0]}) @app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['DELETE']) def delete_task(task_id): task = filter(lambda t: t['id'] == task_id, tasks) if len(task) == 0: abort(404) tasks.remove(task[0]) return jsonify({