转载自 http://python.jobbole.com/87118/

什么是 REST

REST 全称是 Representational State Transfer,翻译成中文是『表现层状态转移』,估计读者看到这个词也是云里雾里的,我当初也是!这里,我们先不纠结这个词到底是什么意思。事实上,REST 是一种 Web 架构风格,它有六条准则,满足下面六条准则的 Web 架构可以说是 Restuful 的。

  1. 客户端-服务器(Client-Server)服务器和客户端之间有明确的界限。一方面,服务器端不再关注用户界面和用户状态。另一方面,客户端不再关注数据的存储问题。这样,服务器端跟客户端可以独立开发,只要它们共同遵守约定。
  2. 无状态(Stateless)来自客户端的每个请求必须包含服务器所需要的所有信息,也就是说,服务器端不存储来自客户端的某个请求的信息,这些信息应由客户端负责维护。
  3. 可缓存(Cachable)服务器的返回内容可以在通信链的某处被缓存,以减少交互次数,提高网络效率。
  4. 分层系统(Layered System)允许在服务器和客户端之间通过引入中间层(比如代理,网关等)代替服务器对客户端的请求进行回应,而且这些对客户端来说不需要特别支持。
  5. 统一接口(Uniform Interface)客户端和服务器之间通过统一的接口(比如 GET, POST, PUT, DELETE 等)相互通信。
  6. 支持按需代码(Code-On-Demand,可选)服务器可以提供一些代码(比如 Javascript)并在客户端中执行,以扩展客户端的某些功能。

使用 Flask 提供 REST Web 服务

REST Web 服务的核心概念是资源(resources)。资源被 URI(Uniform Resource Identifier, 统一资源标识符)定位,客户端使用 HTTP 协议操作这些资源,我们用一句不是很全面的话来概括就是:URI 定位资源,用 HTTP 动词(GET, POST, PUT, DELETE 等)描述操作。下面列出了 REST 架构 API 中常用的请求方法及其含义:

HTTP Method Action Example
GET 从某种资源获取信息 http://example.com/api/articles (获取所有文章)
GET 从某个资源获取信息 http://example.com/api/articles/1 (获取某篇文章)
POST 创建新资源 http://example.com/api/articles (创建新文章)
PUT 更新资源 http://example.com/api/articles/1 (更新文章)
DELETE 删除资源 http://example.com/api/articels/1 (删除文章)

设计一个简单的 Web Service

现在假设我们要为一个 blog 应用设计一个 Web Service。

首先,我们先明确访问该 Service 的根地址是什么。这里,我们可以这样定义:

 
 
 
 
 

Python

 
1
http://[hostname]/blog/api/

然后,我们明确有哪些资源是要公开的。可以知道,我们这个 blog 应用的资源就是 articles。

下一步,我们要明确怎么去操作这些资源,如下所示:

HTTP Method URI Action
GET http://[hostname]/blog/api/articles 获取所有文章列表
GET http://[hostname]/blog/api/articles/[article_id] 获取某篇文章内容
POST http://[hostname]/blog/api/articles 创建一篇新的文章
PUT http://[hostname]/blog/api/articles/[article_id] 更新某篇文章
DELETE http://[hostname]/blog/api/articles/[article_id] 删除某篇文章

为了简便,我们定义一篇 article 的属性如下:

  • id:文章的 id,Numeric 类型
  • title: 文章的标题,String 类型
  • content: 文章的内容,TEXT 类型

至此,我们基本完成了这个 Web Service 的设计,下面我们就来实现它。

使用 Flask 提供 RESTful api

在实现这个 Web 服务之前,我们还有一个问题没有考虑到:我们应该怎么存储我们的数据。毫无疑问,我们应该使用数据库,比如 MySql、MongoDB 等。但是,数据库的存储不是我们这里要讨论的重点,所以我们采用一种偷懒的做法:使用一个内存中的数据结构来代替数据库。

GET 方法

下面我们使用 GET 方法获取资源。

 
 
 
 
 
 

Python

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# -*- coding: utf-8 -*-
from flask import Flask, jsonify, abort, make_response
app = Flask(__name__)
articles = [
    {
        'id': 1,
        'title': 'the way to python',
        'content': 'tuple, list, dict'
    },
    {
        'id': 2,
        'title': 'the way to REST',
        'content': 'GET, POST, PUT'
    }
]
@app.route('/blog/api/articles', methods=['GET'])
def get_articles():
    """ 获取所有文章列表 """
    return jsonify({'articles': articles})
@app.route('/blog/api/articles/<int:article_id>', methods=['GET'])
def get_article(article_id):
    """ 获取某篇文章 """
    article = filter(lambda a: a['id'] == article_id, articles)
    if len(article) == 0:
        abort(404)
    return jsonify({'article': article[0]})
@app.errorhandler(404)
def not_found(error):
    return make_response(jsonify({'error': 'Not found'}), 404)
if __name__ == '__main__':
    app.run(host='127.0.0.1', port=5632, debug=True)

将上面的代码保存为文件 app.py,通过 python app.py 启动这个 Web Service。

接下来,我们进行测试。这里,我们采用命令行语句 curl 进行测试。

开启终端,敲入如下命令进行测试:

 
 
 
 
 

Python

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
$ curl -i http://localhost:5632/blog/api/articles
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 224
Server: Werkzeug/0.11.4 Python/2.7.11
Date: Tue, 16 Aug 2016 15:21:45 GMT
{
  "articles": [
    {
      "content": "tuple, list, dict",
      "id": 1,
      "title": "the way to python"
    },
    {
      "content": "GET, POST, PUT",
      "id": 2,
      "title": "the way to REST"
    }
  ]
}
$ curl -i http://localhost:5632/blog/api/articles/2
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 101
Server: Werkzeug/0.11.4 Python/2.7.11
Date: Wed, 17 Aug 2016 02:37:48 GMT
{
  "article": {
    "content": "GET, POST, PUT",
    "id": 2,
    "title": "the way to REST"
  }
}
$ curl -i http://localhost:5632/blog/api/articles/3
HTTP/1.0 404 NOT FOUND
Content-Type: application/json
Content-Length: 26
Server: Werkzeug/0.11.4 Python/2.7.11
Date: Wed, 17 Aug 2016 02:32:10 GMT
{
  "error": "Not found"
}

上面,我们分别测试了『获取所有文章列表』、『获取某篇文章』和『获取不存在的文章』这三个功能,结果也正是我们所预料的。

POST 方法

下面我们使用 POST 方法创建一个新的资源。在上面的代码中添加以下代码:

 
 
 
 
 

Python

 
1
2
3
4
5
6
7
8
9
10
11
12
from flask import request
@app.route('/blog/api/articles', methods=['POST'])
def create_article():
    if not request.json or not 'title' in request.json:
        abort(400)
    article = {
        'id': articles[-1]['id'] + 1,
        'title': request.json['title'],
        'content': request.json.get('content', '')
    }
    articles.append(article)
    return jsonify({'article': article}), 201

测试如下:

 
 
 
 
 
 

Python

 
1
2
3
4
5
6
7
8
9
10
11
12
13
$ curl -i -H "Content-Type: application/json" -X POST -d '{"title":"the way to java"}' http://localhost:5632/blog/api/articles
HTTP/1.0 201 CREATED
Content-Type: application/json
Content-Length: 87
Server: Werkzeug/0.11.4 Python/2.7.11
Date: Wed, 17 Aug 2016 03:07:14 GMT
{
  "article": {
    "content": "",
    "id": 3,
    "title": "the way to java"
  }
}

可以看到,创建一篇新的文章也是很简单的。request.json 保存了请求中的 JSON 格式的数据。如果请求中没有数据,或者数据中没有 title 的内容,我们将会返回一个 “Bad Request” 的 400 错误。如果数据合法(必须要有 title 的字段),我们就会创建一篇新的文章。

PUT 方法

下面我们使用 PUT 方法更新文章,继续添加代码:

 
 
 
 
 
 

Python

 
1
2
3
4
5
6
7
8
9
10
11
@app.route('/blog/api/articles/<int:article_id>', methods=['PUT'])
def update_article(article_id):
    article = filter(lambda a: a['id'] == article_id, articles)
    if len(article) == 0:
        abort(404)
    if not request.json:
        abort(400)
    
    article[0]['title'] = request.json.get('title', article[0]['title'])
    article[0]['content'] = request.json.get('content', article[0]['content'])
    return jsonify({'article': article[0]})

测试如下:

 
 
 
 
 
 

Python

 
1
2
3
4
5
6
7
8
9
10
11
12
13
$ curl -i -H "Content-Type: application/json" -X PUT -d '{"content": "hello, rest"}' http://localhost:5632/blog/api/articles/2
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 98
Server: Werkzeug/0.11.4 Python/2.7.11
Date: Wed, 17 Aug 2016 03:44:09 GMT
{
  "article": {
    "content": "hello, rest",
    "id": 2,
    "title": "the way to REST"
  }
}

可以看到,更新文章也是很简单的,上面我们更新了第 2 篇文章的内容。

DELETE 方法

下面我们使用 DELETE 方法删除文章,继续添加代码:

 
 
 
 
 
 

Python

 
1
2
3
4
5
6
7
@app.route('/blog/api/articles/<int:article_id>', methods=['DELETE'])
def delete_article(article_id):
    article = filter(lambda t: t['id'] == article_id, articles)
    if len(article) == 0:
        abort(404)
    articles.remove(article[0])
    return jsonify({'result': True})

测试如下:

 
 
 
 
 
 

Python

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ curl -i -H "Content-Type: application/json" -X DELETE http://localhost:5632/blog/api/articles/2
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 20
Server: Werkzeug/0.11.4 Python/2.7.11
Date: Wed, 17 Aug 2016 03:46:04 GMT
{
  "result": true
}
$ curl -i http://localhost:5632/blog/api/articles
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 125
Server: Werkzeug/0.11.4 Python/2.7.11
Date: Wed, 17 Aug 2016 03:46:09 GMT
{
  "articles": [
    {
      "content": "tuple, list, dict",
      "id": 1,
      "title": "the way to python"
    }
  ]
}

附录

常见 HTTP 状态码

HTTP 状态码主要有以下几类:

  • 1xx —— 元数据
  • 2xx —— 正确的响应
  • 3xx —— 重定向
  • 4xx —— 客户端错误
  • 5xx —— 服务端错误

常见的 HTTP 状态码可见以下表格:

代码 说明
100 Continue。客户端应当继续发送请求。
200 OK。请求已成功,请求所希望的响应头或数据体将随此响应返回。
201 Created。请求成功,并且服务器创建了新的资源。
301 Moved Permanently。请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。
302 Found。服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
400 Bad Request。服务器不理解请求的语法。
401 Unauthorized。请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
403 Forbidden。服务器拒绝请求。
404 Not Found。服务器找不到请求的网页。
500 Internal Server Error。服务器遇到错误,无法完成请求。

curl 命令参考

选项 作用
-X 指定 HTTP 请求方法,如 POST,GET, PUT
-H 指定请求头,例如 Content-type:application/json
-d 指定请求数据
–data-binary 指定发送的文件
-i 显示响应头部信息
-u 指定认证用户名与密码
-v 输出请求头部信息

完整代码

本文的完整代码可在 Gist 查看。

参考链接

Python flask 基于 Flask 提供 RESTful Web 服务的更多相关文章

  1. 让你的 wowza server提供 RESTful web 服务

            有时我们 nginx 须要和 wowza 服务器交互以进行一些 LB 事宜:有时我们的管理员须要实时了解 wowza 服务器的一些其它状态信息(比方一些自己定义对象的状态等等).而用 ...

  2. 基于jersey和Apache Tomcat构建Restful Web服务(二)

    基于jersey和Apache Tomcat构建Restful Web服务(二) 上篇博客介绍了REST以及Jersey并使用其搭建了一个简单的“Hello World”,那么本次呢,再来点有趣的东西 ...

  3. 基于jersey和Apache Tomcat构建Restful Web服务(一)

    基于jersey和Apache Tomcat构建Restful Web服务(一) 现如今,RESTful架构已然成为了最流行的一种互联网软件架构,它结构清晰.符合标准.易于理解.扩展方便,所以得到越来 ...

  4. RESTful Web 服务:教程

    RESTful Web 服务:教程   随着 REST 成为大多数 Web 和 Mobile 应用的默认选择,势必要对它的基本原理有所了解. 在它提出十多年后的今天,REST 已经成为最重要的 Web ...

  5. 用于构建 RESTful Web 服务的多层架构

    作者:Bruce Sun, Java 架构师, IBM 出处:http://www.ibm.com/developerworks/cn/web/wa-aj-multitier/ 用于构建 RESTfu ...

  6. 使用 Spring 3 MVC HttpMessageConverter 功能构建 RESTful web 服务

    原文地址:http://www.ibm.com/developerworks/cn/web/wa-restful/ 简介: Spring,构建 Java™ 平台和 Enterprise Edition ...

  7. 构建一个基于 Spring 的 RESTful Web Service

    本文详细介绍了基于Spring创建一个“hello world” RESTful web service工程的步骤. 目标 构建一个service,接收如下HTTP GET请求: http://loc ...

  8. [译]Spring Boot 构建一个RESTful Web服务

    翻译地址:https://spring.io/guides/gs/rest-service/ 构建一个RESTful Web服务 本指南将指导您完成使用spring创建一个“hello world”R ...

  9. Spring MVC中发布Restful Web服务

      对于企业应用来说,数据是许多业务的命脉,软件通常是可替换的,但是多年积累的数据是永远不能替换的.   近些年来,以信息为中心的表述性状态转移(Representational State Tran ...

随机推荐

  1. Python文件读写

    读写文件是最常见的IO操作.Python内置了读写文件的函数,用法和C是兼容的 文件打开模式 模式 意义 r 文本只读 w 文本只写 rb 二进制读 rw 二进制写 打开文件 选择一个模式打开一个文件 ...

  2. Beta版本冲刺计划及安排

    经过紧张的Alpha阶段,很多组已经从完全不熟悉语言和环境,到现在能够实现初步的功能.下一阶段即将加快编码进度,完成系统功能.强化软件工程的体会.Beta阶段的冲刺时间为期七天,安排在2016.12. ...

  3. Masonry介绍与使用实践:快速上手Autolayout

    1 MagicNumber -> autoresizingMask -> autolayout 以上是纯手写代码所经历的关于页面布局的三个时期 在iphone1-iphone3gs时代 w ...

  4. 利用StringEscapeUtils对字符串进行各种转义与反转义(Java)

    apache工具包common-lang中有一个很有用的处理字符串的工具类,其中之一就是StringEscapeUtils,这个工具类是在2.3版本以上加上的去的,利用它能很方便的进行html,xml ...

  5. vuex(1.0版本写法)

    Vuex 是一个专门为 Vue.js 应用所设计的集中式状态管理架构. 官方文档:http://vuex.vuejs.org/zh-cn/  2.0和1.0都能在此找到 每一个 Vuex 应用的核心就 ...

  6. Code Conventions for the Java

    Oracel官方JAVA编码规范指引 http://www.oracle.com/technetwork/java/codeconvtoc-136057.html

  7. PHP日志记录规范PSR-3

    .note-content { font-family: "Helvetica Neue", Arial, "Hiragino Sans GB", STHeit ...

  8. 14 Iterator和for...of循环

    Iterator和for...of循环 首先 Iterator 是一个接口. 标准是 function makeIterator(array) { var nextIndex = 0; return ...

  9. 精通css 高级web标准解决方案——可视化格式模型-盒模型

    1-盒模型的两种标准: IE :width 和 height属性 是包括padding和border在内的. w3c:width 和 height 属性,就是单纯的内容的宽高,padding 是内容之 ...

  10. 解决httpd: Could not reliably determine the server's fully qualified domain name

    解决方案: 用记事本打开 httpd.conf 将里面的 #ServerName localhost:80 注释去掉即可. 再执行 httpd 然后可以通过浏览器访问 http://localhost ...