Python Restful API 接口服务示例项目

Table of Contents

概览

本项目参考Flask官方文档(但部分细节有变化),实现如下事项:

  • 使用Flask实现Restful服务器

    基于官方文档微调的Restful api Demo

  • 使用Flask-RESTful设计Restful API

    为数据库中的部分游戏数据开放RESTful接口

示例项目步骤

安装Flask

创建虚拟环境,我通过conda创建,flask官方文档通过virtualenv创建虚拟环境,你可以选择任意方式创建。

conda create -n flask python=3.8

激活flask虚拟环境

conda activate flask

创建Flask网页应用

from flask import Flask
app = Flask(__name__)

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

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

运行程序后提示如下信息即可通过访问 http://localhost:5000查看flask程序的效果。

通过Flask实现Restful服务

通过@app.route函数指定服务入口(接口),通过接口返回数据(示例中数据存放在内存中,web服务结束后就失效)

from flask import Flask, jsonify

app = Flask(__name__)

musics = [
    {
        'id': 1,
        'title': u'南海姑娘',
        'singer': u'王菲',
        'lyric': '椰风挑动银浪,夕阳躲云偷看'
    },
    {
        'id': 2,
        'title': u'Heart Of Glass',
        'singer': u'王菲',
        'lyric': 'in between what i found is ...'
    },
    {
        'id': 3,
        'title': u'Dreams',
        'singer': u'卡百利',
        'lyric': 'Oh my dream...'
    }
]


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


@app.route('/music/api/v1.0/musics', methods=['GET'])
def get_musics():
    return jsonify({
        'musics': musics
    })


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

执行app.py启动web服务,打开http://127.0.0.1:5000/music/api/v1.0/musics

注意到返回的数据里中文并没有正常显示,这是因为jsonify默认以ASCII码返回,不能正常显示中文。对于此问题网上大多数“解决方法“是更改Flask app的编码配置,通过如下代码完成

app.config["JSON_AS_ASCII"] = False

但在我本机测试无法解决(Python 3.8.16,Flask 2.3.2,Werkzeug 2.3.4)

最终通过json.dumps方式实现中文正常显示效果。

@app.route('/music/api/v1.0/musics', methods=['GET'])
def get_musics():
     json_string = json.dumps({"musics":musics }, ensure_ascii=False)
     response = Response(json_string, content_type="application/json; charset=utf-8")
     return response

小结:get_musics函数允许通过GET方法访问/music/api/v1.0/musics获取资源,资源响应通过JSON格式返回。

使用curl测试web服务

为了模拟更多的HTTP请求,建议通过curl而非浏览器发送请求,curl简单用法如下:

curl -i http://127.0.0.1:5000/music/api/v1.0/musics
HTTP/1.1 200 OK
Server: Werkzeug/2.3.4 Python/3.8.16
Date: Wed, 31 May 2023 17:09:22 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 301
Connection: close

{"musics": [{"id": 1, "lyric": "椰风挑动银浪,夕阳躲云偷看", "singer": "王菲", "title": "南海姑娘"}, {"id": 2, "lyric": "in between what i found is ...", "singer": "王菲", "title": "Heart Of Glass"}, {"id": 3, "lyric": "Oh my dream...", "singer": "卡百利", "title": "Dreams"}]}%
curl -i http://127.0.0.1:5000
HTTP/1.1 200 OK
Server: Werkzeug/2.3.4 Python/3.8.16
Date: Wed, 31 May 2023 17:10:04 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 29
Connection: close

Hello, world! 你好世界!%

更多接口

GET: 查询某一条记录(单独返回一个音乐)
@app.route('/music/api/v1.0/music/<int:music_id>', methods=['GET'])
def get_music(music_id):
    music = filter(lambda t:t['id']==music_id, musics)
    music = list(music)
    if music:
        json_string = json.dumps({"music":music[0] }, ensure_ascii=False)
        response = Response(json_string, content_type="application/json; charset=utf-8")
        return response
    else:
        abort(404)

如果希望以json格式响应返回404的提示信息,而非使用默认的html响应,可重写404错误码处理函数如下:

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

POST新增数据
curl -i -H "Content-Type: application/json" -X POST -d '{"title":"Crazy"}' http://localhost:5000/music/api/v1.0/musics

PUT更新数据
@app.route("/music/api/v1.0/music/<int:music_id>", methods=["PUT"])
def update_music(music_id):
    music = filter(lambda m:m["id"]==music_id, musics)
    music = list(music)
    if len(music)==0:
        # data not exist
        abort(404)
    if not request.json:
        # illegal request
        abort(400)
    # input check
    if 'title' in request.json and type(request.json['title']) != str:
        abort(400)
    if 'singer' in request.json and type(request.json['singer']) is not str:
        abort(400)
    if 'lyric' in request.json and type(request.json['lyric']) is not str:
        abort(400)
    music[0]["title"] = request.json.get("title", music[0]["title"])
    music[0]["singer"] = request.json.get("singer", music[0]["singer"])
    music[0]["lyric"] = request.json.get("lyric", music[0]["lyric"])
    json_string = json.dumps({"music": music[0]}, ensure_ascii=False)
    response = Response(json_string, content_type="application/json; charset=utf-8")
    return response
curl -i -H "Content-Type: application/json" -X PUT -d "{\"lyric\":\"love is so confusing and I'm feeling fine..\"}" http://localhost:5000/music/api/v1.0/music/2

注意curl命令指定json数据时,在单双引号共存的情况下需要对json中的双引号进行转义,否则curl会报错。错误示范如下:

curl -i -H "Content-Type: application/json" -X PUT -d '{"lyric":"love is so confusing and I'm feeling fine.."}' http://localhost:5000/music/api/v1.0/music/2
DELETE删除数据
@app.route('/music/api/v1.0/musics/<int:music_id>', methods=['DELETE'])
def delete_music(music_id):
    music = filter(lambda t: t['id'] == music_id, musics)
    music = list(music)
    if len(music) == 0:
        abort(404)
    musics.remove(music[0])
    return jsonify({'result': True})

优化Web Service接口

直接返回id让客户端构造URI不便与用户使用,也不利于资源发布方对URI进行变更。以上述Restful接口为例,不建议直接返回音乐的id,而是直接返回其uri。

flask提供了uri_for方法用于构造url。

Create dynamic URLs in Flask with url_for()

加强RESTful web service的安全性

有时候我们不希望接口对所有人公开,因此需要为接口提供认证功能,要求只有通过认证的客户端方能访问RESTful接口。一种简单认证方式是遵循HTTP协议的认证机制,这一功能可通过flask-httpauth扩展方便地实现。

Flask-HTTPAuth

pip install flask-httpauth
from flask_httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()

@auth.get_password
def authenticate(username):
    if username == "coolcats":
        return "restful"
    return None

@auth.error_handler
def unauthorized():
    return make_response(jsonify({"error": "unauthorized access"}), 401)

小结

PUT和POST的区别

HTTP PUT 请求方法创建一个新的资源或用请求的有效载荷替换目标资源的表示。

PUT 与 POST 方法的区别是,PUT 方法是幂等的:调用一次与连续调用多次效果是相同的(即没有作用),而连续调用多次相同的 POST 方法可能会有副作用,比如多次提交同一订单。

HTTP协议认证机制

Basic和Digest

基于Flask-RESTful 设计Restful API

Flask-RESTful 是一个可以简化 APIs 的构建的 Flask 扩展。

以游戏数据为例,假定我们有两个数据表T_GAME, T_REVIEW,需要针对这些数据设计一些接口供外部查询,并设计修改和提交新数据的接口供经过权限认证的用户使用。主要需要的接口如下

HTTP方法 URL 动作
GET http://[hostname]/game/api/v1/games?limit=<limit:int> 检索所有游戏
GET http://[hostname]/game/api/v1/games/<game_id:int> 检索某个游戏
GET http://[hostname]/game/api/v1/reviews?limit=<limit:int> 检索所有评论
GET http://[hostname]/game/api/v1/games/<game_id:int>/reviews/ 检索某个游戏的评论
GET http://[hostname]/game/api/v1/reviews/<review_id:int> 检索某个评论
POST http://[hostname]/game/api/v1/reviews/<review_id:int> 新增评论
PUT http://[hostname]/game/api/v1/reviews/<review_id:int> 更新评论
delete http://[hostname]/game/api/v1/reviews/<review_id:int> 删除评论

还可以在URL参数指定接口返回数据的数量(limit);对数据进行分页,按指定每页的数据量返回;按一定的属性进行排序等;

准备工作

安装flask-restful

pip install flask-restful

pass

参考文档

使用 Python 和 Flask 设计 RESTful API — Designing a RESTful API with Python and Flask 1.0 documentation

HTTP Authentication: Basic and Digest Access Authentication

CoolCats
CoolCats
理学学士

我的研究兴趣是时空数据分析、知识图谱、自然语言处理与服务端开发