如何用python构建restful api

 我来答
从空去听8
2017-10-27 · TA获得超过7439个赞
知道大有可为答主
回答量:6907
采纳率:93%
帮助的人:5576万
展开全部
最近这些年,REST已经成为web services和APIs的标准架构,很多APP的架构基本上是使用RESTful的形式了。
本文将会使用python的Flask框架轻松实现一个RESTful的服务。
REST的六个特性:
Client-Server:服务器端与客户端分离。
Stateless(无状态):每次客户端请求必需包含完整的信息,换句话说,每一次请求都是独立的。
Cacheable(可缓存):服务器端必需指定哪些请求是可以缓存的。
Layered System(分层结构):服务器端与客户端通讯必需标准化,服务器的变更并不会影响客户端。
Uniform Interface(统一接口):客户端与服务器端的通讯方法必需是统一的。
Code on demand(按需执行代码?):服务器端可以在上下文中执行代码或者脚本?
Servers can provide executable code or scripts for clients to execute in their context. This constraint is the only one that is optional.(没看明白)
RESTful web service的样子
REST架构就是为了HTTP协议设计的。RESTful web services的核心概念是管理资源。资源是由URIs来表示,客户端使用HTTP当中的'POST, OPTIONS, GET, PUT, DELETE'等方法发送请求到服务器,改变相应的资源状态。
HTTP请求方法通常也十分合适去描述操作资源的动作:
HTTP方法 动作 例子
GET 获取资源信息
http://example.com/api/orders
(检索订单清单)

GET 获取资源信息
http://example.com/api/orders/123
(检索订单 #123)

POST 创建一个次的资源
http://example.com/api/orders
(使用带数据的请求,创建一个新的订单)

PUT 更新一个资源
http://example.com/api/orders/123
(使用带数据的请求,更新#123订单)

DELETE 删除一个资源
http://example.com/api/orders/123
删除订单#123

REST请求并不需要特定的数据格式,通常使用JSON作为请求体,或者URL的查询参数的一部份。
设计一个简单的web service
下面的任务将会练习设计以REST准则为指引,通过不同的请求方法操作资源,标识资源的例子。
我们将写一个To Do List 应用,并且设计一个web service。第一步,规划一个根URL,例如:
http://[hostname]/todo/api/v1.0/
上面的URL包括了应用程序的名称、API版本,这是十分有用的,既提供了命名空间的划分,同时又与其它系统区分开来。版本号在升级新特性时十分有用,当一个新功能特性增加在新版本下面时,并不影响旧版本。
第二步,规划资源的URL,这个例子十分简单,只有任务清单。
规划如下:
HTTP方法 URI 动作
GET http://[hostname]/todo/api/v1.0/tasks 检索任务清单
GET http://[hostname]/todo/api/v1.0/tasks/[task_id] 检索一个任务
POST http://[hostname]/todo/api/v1.0/tasks 创建一个新任务
PUT http://[hostname]/todo/api/v1.0/tasks/[task_id] 更新一个已存在的任务
DELETE http://[hostname]/todo/api/v1.0/tasks/[task_id] 删除一个任务
我们定义任务清单有以下字段:
id:唯一标识。整型。
title:简短的任务描述。字符串型。
description:完整的任务描述。文本型。
done:任务完成状态。布尔值型。
以上基本完成了设计部份,接下来我们将会实现它!
简单了解Flask框架
Flask好简单,但是又很强大的Python web 框架。这里有一系列教程Flask Mega-Tutorial series。(注:Django\Tornado\web.py感觉好多框:()
在我们深入实现web service之前,让我们来简单地看一个Flask web 应用的结构示例。
这里都是在Unix-like(Linux,Mac OS X)操作系统下面的演示,但是其它系统也可以跑,例如windows下的Cygwin。可能命令有些不同吧。(注:忽略Windows吧。)
先使用virtualenv安装一个Flask的虚拟环境。如果没有安装virtualenv,开发python必备,最好去下载安装。https://pypi.python.org/pypi/virtualenv

$ mkdir todo-api
$ cd todo-api
$ virtualenv flask
New python executable in flask/bin/python
Installing setuptools............................done.
Installing pip...................done.
$ flask/bin/pip install flask

这样做好了一个Flask的开发环境,开始创建一个简单的web应用,在当前目录里面创建一个app.py文件:

#!flask/bin/python
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
return "Hello, World!"

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

去执行app.py:
$ chmod a+x app.py
$ ./app.py
* Running on http://127.0.0.1:5000/
* Restarting with reloader

现在可以打开浏览器,输入http://localhost:5000去看看这个Hello,World!
好吧,十分简单吧。我们开始转换到RESTful service!
使用Python 和 Flask实现RESTful services
使用Flask建立web services超级简单。
当然,也有很多Flask extensions可以帮助建立RESTful services,但是这个例实在太简单了,不需要使用任何扩展。
这个web service提供增加,删除、修改任务清单,所以我们需要将任务清单存储起来。最简单的做法就是使用小型的数据库,但是数据库并不是本文涉及太多的。可以参考原文作者的完整教程。Flask Mega-Tutorial series
在这里例子我们将任务清单存储在内存中,这样只能运行在单进程和单线程中,这样是不适合作为生产服务器的,若非就必需使用数据库了。
现在我们准备实现第一个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)

正如您所见,并没有改变太多代码。我们将任务清单存储在list内(内存),list存放两个非常简单的数组字典。每个实体就是我们上面定义的字段。
而 index 入口点有一个get_tasks函数与/todo/api/v1.0/tasks URI关联,只接受http的GET方法。
这个响应并非一般文本,是JSON格式的数据,是经过Flask框架的 jsonify模块格式化过的数据。
使用浏览器去测试web service并不是一个好的办法,因为要创建不同类弄的HTTP请求,事实上,我们将使用curl命令行。如果没有安装curl,快点去安装一个。
像刚才一样运行app.py。
打开一个终端运行以下命令:

$ curl -i http://localhost:5000/todo/api/v1.0/tasks
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 294
Server: Werkzeug/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"
}
]
}

这样就调用了一个RESTful service方法!
现在,我们写第二个版本的GET方法获取特定的任务。获取单个任务:

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]})

第二个函数稍稍复杂了一些。任务的id包含在URL内,Flask将task_id参数传入了函数内。
通过参数,检索tasks数组。如果参数传过来的id不存在于数组内,我们需要返回错误代码404,按照HTTP的规定,404意味着是"Resource Not Found",资源未找到。
如果找到任务在内存数组内,我们通过jsonify模块将字典打包成JSON格式,并发送响应到客户端上。就像处理一个实体字典一样。
试试使用curl调用:

$ curl -i http://localhost:5000/todo/api/v1.0/tasks/2
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 151
Server: Werkzeug/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: Werkzeug/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>

当我们请求#2 id的资源时,可以获取,但是当我们请求#3的资源时返回了404错误。并且返回了一段奇怪的HTML错误,而不是我们期望的JSON,这是因为Flask产生了默认的404响应。客户端需要收到的都是JSON的响应,因此我们需要改进404错误处理:
from flask import make_response

@app.errorhandler(404)
def not_found(error):
return make_response(jsonify({'error': 'Not found'}), 404)

这样我们就得到了友好的API错误响应:

$ 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: Werkzeug/0.8.3 Python/2.7.3
Date: Mon, 20 May 2013 05:36:54 GMT

{
"error": "Not found"
}

接下来我们实现 POST 方法,插入一个新的任务到数组中:

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

request.json里面包含请求数据,如果不是JSON或者里面没有包括title字段,将会返回400的错误代码。
当创建一个新的任务字典,使用最后一个任务id数值加1作为新的任务id(最简单的方法产生一个唯一字段)。这里允许不带description字段,默认将done字段值为False。
将新任务附加到tasks数组里面,并且返回客户端201状态码和刚刚添加的任务内容。HTTP定义了201状态码为“Created”。
测试上面的新功能:

$ 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: Werkzeug/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"
}
}

注意:如果使用原生版本的curl命令行提示符,上面的命令会正确执行。如果是在Windows下使用Cygwin bash版本的curl,需要将body部份添加双引号:
curl -i -H "Content-Type: application/json" -X POST -d "{"""title""":"""Read a book"""}" http://localhost:5000/todo/api/v1.0/tasks

基本上在Windows中需要使用双引号包括body部份在内,而且需要三个双引号转义序列。
完成上面的事情,就可以看到更新之后的list数组内容:

$ curl -i http://localhost:5000/todo/api/v1.0/tasks
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 423
Server: Werkzeug/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"
}
]
}

剩余的两个函数如下:

@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({'result': True})

delete_task函数没什么太特别的。update_task函数需要检查所输入的参数,防止产生错误的bug。确保是预期的JSON格式写入数据库里面。
测试将任务#2的done字段变更为done状态:

$ curl -i -H "Content-Type: application/json" -X PUT -d '{"done":true}' http://localhost:5000/todo/api/v1.0/tasks/2
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 170
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Mon, 20 May 2013 07:10:16 GMT

{
"task": [
{
"description": "Need to find a good Python tutorial on the web",
"done": true,
"id": 2,
"title": "Learn Python"
}
]
}

改进Web Service接口
当前我们还有一个问题,客户端有可能需要从返回的JSON中重新构造URI,如果将来加入新的特性时,可能需要修改客户端。(例如新增版本。)
我们可以返回整个URI的路径给客户端,而不是任务的id。为了这个功能,创建一个小函数生成
已赞过 已踩过<
你对这个回答的评价是?
评论 收起
收起 1条折叠回答
推荐律师服务: 若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询

为你推荐:

下载百度知道APP,抢鲜体验
使用百度知道APP,立即抢鲜体验。你的手机镜头里或许有别人想知道的答案。
扫描二维码下载
×

类别

我们会通过消息、邮箱等方式尽快将举报结果通知您。

说明

0/200

提交
取消

辅 助

模 式