专栏名称: Python开发者
人生苦短,我用 Python。伯乐在线旗下账号「Python开发者」分享 Python 相关的技术文章、工具资源、精选课程、热点资讯等。
目录
相关文章推荐
Python爱好者社区  ·  大模型最佳实践.pdf ·  3 天前  
Python爱好者社区  ·  72k,直接封神! ·  4 天前  
Python中文社区  ·  免费金融数据 + Python ... ·  6 天前  
Python爱好者社区  ·  写个爬虫,一单一个W ·  5 天前  
51好读  ›  专栏  ›  Python开发者

基于 Flask 提供 RESTful Web 服务

Python开发者  · 公众号  · Python  · 2017-01-06 20:19

正文

(点击上方公众号,可快速关注)


来源:FunHacks

链接:funhacks.net/2016/08/21/restful_api_with_flask/

如有好文章投稿,请点击 → 这里了解详情


什么是 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 中常用的请求方法及其含义:




设计一个简单的 Web Service


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


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


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


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


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



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


  • id:文章的 id,Numeric 类型

  • title: 文章的标题,String 类型

  • content: 文章的内容,TEXT 类型


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


使用 Flask 提供 RESTful api


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


GET 方法


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


# -*- 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/', 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 进行测试。


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


$ 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 方法创建一个新的资源。在上面的代码中添加以下代码:


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


测试如下:


$ 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 方法更新文章,继续添加代码:


@app.route('/blog/api/articles/', 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]})


测试如下:


$ 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 方法删除文章,继续添加代码:


@app.route('/blog/api/articles/', 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})


测试如下:


$ 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 状态码可见以下表格:



curl 命令参考



完整代码


本文的完整代码可在 Gist 查看(https://gist.github.com/ethan-funny/cf19aa89175055de6517d851759ad743)。


参考链接


  • 理解本真的REST架构风格

  • 基于 Flask 实现 RESTful API | Ross’s Page

  • (译)使用Flask实现RESTful API – nummy的专栏 – SegmentFault

  • 怎样用通俗的语言解释什么叫 REST,以及什么是 RESTful? – 知乎

  • What Does RESTful Really Mean? – DZone Integration

  • HTTP状态码 – 维基百科,自由的百科全书

  • 理解RESTful架构 – 阮一峰的网络日志


觉得本文对你有帮助?请分享给更多人

关注「Python开发者」

看更多技术干货

推荐文章
Python爱好者社区  ·  大模型最佳实践.pdf
3 天前
Python爱好者社区  ·  72k,直接封神!
4 天前
Python爱好者社区  ·  写个爬虫,一单一个W
5 天前
好奇小姐的好奇心  ·  原成龙贴身保镖,竟被路人暴打,真相如此惨烈
7 年前
美食家常菜谱做法  ·  大鱼大肉没完没了?一定要看!清淡也美味! ​
7 年前
声乐助手  ·  8月21日|每天一道乐理习题
7 年前