设计RESTful(flask)
前记
当前很多WEB框架都有一些库来支持RESTful了,但是不了解的话还是会用得一头雾水。在找了一些资料后,找了RESTful设计的规范,并用falsk框架尝试写出RESTful 的api
编写一个应用
在未使用RESTful之前,简单的web应用可能是这样的(我全部都使用了get)
这个应用目前记录的书的isbn,书的名字,还有书的数据(以时间来记录)
忽略<后的\
url:/post/<\int:isbn>/<\title> 建立一本书的信息
url:/get/<\int:isbn>/ 获取一本书的信息
url:/put/<\int:isbn>/<\data> 更新书的日期
url:/delete/<\int:isbn> 删除书的信息
1 |
|
url设计
统一的url
在RESTful设计中,统一接口,使用不同的http方法表达不同的行为:
- GET:从服务器获出资源(一项或多项)
- POST:在服务器新建一个资源
- PUT:在服务器更新资源(用户提供完整数据)
- PATCH:在服务器更新资源(用户供需要修改的资源数据)
- DELETE:从服务器删除资源
其他URL设计
版本
RESTful给出的方法是使用HTTPheader中的accept来传递版本信息,但也有一些是在url中添加版本信息,如:/api/v1/ #v1就是指版本1
使用名词
设计url时,应使用名词而非动词资源合集
获取资源合集时,有两种设计方法,一种是把要获取的资源当成一个子合集,另外一种是把需要的资源当成从总资源中过滤出来的。如书的例子,假设data有年,月,日三个值,我们要获取17年的十月的书时,url可以按照以下设计:- 子合集
url: book/data/17/10
- 过滤出来的资源
url: book/data/?y=17&m=10
- 子合集
url区分大小写
api/demo和api/demo/不一样
如果设计为:api/demo/时,用户输入api/demo会重定向到api/demo/。但是设计为api/demo时,用户输入api/demo/只能404 not found。使用-而不使用_
由于网页的超链接会有下划线,所以_可能会让别人误会为空格
带有RESTful设计规范url的应用
按照之前的应用,HTTP方法也只使用了’POST’, ‘GET’,’PUT’,’DELETE’。(PUT是模拟出用户提供了日期时间,实际代码中是自己获取了- -。),可以把代码改为下面这样(其他URL设计中的资源合集并没有在这个例子体现出来)。
1 |
|
PS:测试RESTful如果也是CHROME浏览器的话可以使用postman REST client
输入以下url测试,并选择好对应的按钮,再点击send:
http://127.0.0.1:5000/api/v1/book/123/?title=abc POST
http://127.0.0.1:5000/api/v1/book/123/ GET
http://127.0.0.1:5000/api/v1/book/123/ PUT
http://127.0.0.1:5000/api/v1/book/123/ DELETE
后端返回的内容
返回的数据类型
RESTful API规范统一的数据格式json或xml(一般都是json),如果API响应客户端请求后,
返回json,需要在header中添加Content-Type=application/json
返回xml,需要在header中添加Content-Type=application/atom+xml
在falsk中们可以使用json.jsonify返回json数据
1 |
|
返回的状态码
在客户端发送请求给服务器后,服务器也会返回信息给用户
- 当GET, PUT和PATCH请求成功时,返回对应的数据,及状态码200
- 当POST创建数据成功时,返回创建的数据,及状态码201
- 当DELETE删除数据成功时,不返回数据,返回状态码204
- 当GET 不到数据时,返回状态码404
- 当验请求数据时发现错误,返回状态码 400
- 当API 请求需要用户认证时,如果request中的认证信息不正确,返回状态码 401
- 当API 请求需要验证用户权限时,如果当前用户无相应权限,返回状态码 403
Flask的路由函数可以选择额外返回两个值,这两个值将被分别设为HTTP状态码和自定义的HTTP响应标头
也就是使用
1 |
|
就能返回文本,状态码,和http响应头,但是使用json.jsonify的话自带{‘Content-Type’: ‘application/json’}。
但如果要自己定义返回的错误信息的话,可以通过修改abort(状态码)和修饰器@app.errorhandler(状态码)来定义,不过比较繁琐,冗余比较多,可以自己定义一个类来自定义返回错误信息(原出处):
编写一个处理错误类
1 |
|
利用@app.errorhandler定义一个可以返回信息和状态码的函数,继承于我们编写的处理错误类
1 |
|
调用示例:
1 |
|
提供分页url
由于使用RESTful后是无状态的,所以要提供类似与分页的url,如上一页和下一页
如果服务器记录了应用的状态(stateful),那么你只要向服务询问『我要看下一页』,那么服务器自然就会返回第二页。类似的,如果你当前在第二页,想服务器请求『我要看下一页』,那就会得到第三页。但是REST的服务器恰恰是无状态的(stateless),服务器并没有保持你当前处于第几页,也就无法响应『下一页』这种具有状态性质的请求。因此客户端需要去维护当前应用的状态(application state),也就是『如何获取下一页资源』。当然,『下一页资源』的业务逻辑必然是由服务端来提供。服务器在文章列表的atom表征中加入一个URI超链接(hyper link),指向下一页文章列表对应的资源。客户端就可以使用统一接口(Uniform Interface)的方式,从这个URI中获取到他想要的下一页文章列表资源。上面的『能够进入下一页』就是应用的状态(State)。服务器把『能够进入下一页』这个状态以atom表征形式传输(Transfer)给客户端就是表征状态传输(REpresentational State Transfer)这个概念。
作者:季文昊
链接:https://www.zhihu.com/question/28557115/answer/48120528
来源:知乎
假设应用里的书可以给别人查看一页一页的查看,那返回的数据需要这些(例如现在处于第二页):
xml返回的格式(忽略\link中的\):
<\link href=”http://***/book/page?pn=2” rel=”self” />
<\link href=”http://***/book/page?pn=3” rel=”next” />
<\link href=”http://***/book/page?pn=1” rel=”prev” />
json返回的数据要包含:
{“link”: {
“rel”: “http:///book/page?pn=2”,
“next”: “http:///book/page?pn=3”,
“prev”: “http://***/book/page?pn=1”
“title”: “book.title”,
“type”: “application/vnd.yourformat+json”
}}
或者
{
“page”: 2, # 当前是第几页
“pages”: 3, # 总共多少页
“per_page”: 20, # 每页多少数据
“has_next”: true, # 是否有下一页数据
“has_prev”: true, # 是否有前一页数据
“total”: 59 # 总共多少数据
}
按照以上规范进行修改,就可以得出一段代码不太好看的应用了- -(没有从数据库抽取数据,数据都是post生成的,很多判断都叠加在一起了)
1 |
|
如果返回的是要有url的话,如查看的是书的第二页,但书还有第三页,需要返回第三页的url,这个url可以使用url_for()来构造,在这个例子中的api是
api/v1/book/isbn/page
也就是调用api时能直接显示到书的对应页数的内容
用到的url_for()要引用
1 |
|
api_url调用的函数是这样
1 |
|
构造URL的函数:
注意的是url_for中的’new_api’这样url_for()才知道要构建的是哪个URL
1 |
|
当输入:api/v1/book/123/2/时,就会返回url
api/v1/book/123/1/
api/v1/book/123/3/
验证与限制
数据检验
数据校验,有些是在前端进行校验,但是如果是给其他用户使用(如开发者API)时,数据校验需要放在后端进行,如果提交的字段不符合时,则需要返回对应的错误。虽然数据检验是RESTful设计中的一个可选项,但它对API的安全、服务器的开销和交互的友好性而言都具有重要意义。常见的数据类型校验如下:
- 数据类型校验,如规定的字段类型是int或者str
- 数据格式校验,如字段需要满足相应的正则表达式
- 数据逻辑校验,如数据包含出生日期和年龄两个字段,如果这两个字段的数据不一致,则数据校验失败
上面那段代码的check()函数就数据数据校验了
速度限制
为了避免请求泛滥,给API设置速度限制很重要。为此 RFC 6585 引入了HTTP状态码429(too many requests)。加入速度设置之后,应该提示用户,至于如何提示标准上没有说明,不过流行的方法是使用HTTP的返回头。
下面是几个必须的返回头(依照twitter的命名规则):
X-Rate-Limit-Limit :当前时间段允许的并发请求数
X-Rate-Limit-Remaining:当前时间段保留的请求数。
X-Rate-Limit-Reset:当前时间段剩余秒数
为什么使用当前时间段剩余秒数而不是时间戳?
时间戳保存的信息很多,但是也包含了很多不必要的信息,用户只需要知道还剩几秒就可以再发请求了这样也避免了clock skew问题。
在flask中有个拓展提供了类似的功能传送门
用户认证与权限机制
一般都使用OAuth认证授权。Oauth认证会生成一个令牌,可以授权用户或者第三方系统在一定的时间内获取特定的资源。目前使用的几乎是Oauth 2.0。由于Oauth比较安全,现在Oauth已成为RESTful设计中最常用的认证机制。
关于使用RESTFUL认证,这里有篇文章写得不错——传送门
其他
到了这里,就已经能写出一个初步成形RESTful规范的后台应用了,为了更加深入RESTful和减少开发时间,可以使用一些RESTful库。
python中web框架的RESTful库:
- Django
- django-rest-framework:一个强大灵活的工具,用来构建 web API。
- django-tastypie:为Django 应用开发API。
- django-formapi:为 Django 的表单验证,创建 JSON APIs 。
- Flask
- flask-api:为 flask 开发的,可浏览 Web APIs 。
- flask-restful:为 flask 快速创建REST APIs 。
- flask-restless:为 SQLAlchemy 定义的数据库模型创建 RESTful APIs 。
- flask-api-utils:为 Flask 处理 API 表示和验证。
- eve:REST API 框架,由 Flask, MongoDB 等驱动。
- Pyramid
- cornice:一个Pyramid 的 REST 框架 。
- 与框架无关的
- falcon:一个用来建立云 API 和 web app 后端的高性能框架。
- sandman:为现存的数据库驱动系统自动创建 REST APIs 。
- restless:框架无关的 REST 框架 ,基于从 Tastypie 学到的知识。
- ripozo:快速创建 REST/HATEOAS/Hypermedia APIs。
HTTP状态码
HTTP定义了很多有意义的状态码,你可以在你的API中使用。这些状态码可以帮助API消费者用来路由它们获取到的响应内容。整理了一个你肯定会用到的状态码列表:
200 OK - 对成功的GET、PUT、PATCH或DELETE操作进行响应。也可以被用在不创建新资源的POST操作上
201 Created - 对创建新资源的POST操作进行响应。应该带着指向新资源地址的Location header)
204 No Content - 对不会返回响应体的成功请求进行响应(比如DELETE请求)
304 Not Modified - HTTP缓存header生效的时候用
400 Bad Request - 请求异常,比如请求中的body无法解析
401 Unauthorized - 没有进行认证或者认证非法。当API通过浏览器访问的时候,可以用来弹出一个认证对话框
403 Forbidden - 当认证成功,但是认证过的用户没有访问资源的权限
404 Not Found - 当一个不存在的资源被请求
405 Method Not Allowed - 所请求的HTTP方法不允许当前认证用户访问
410 Gone - 表示当前请求的资源不再可用。当调用老版本API的时候很有用
415 Unsupported Media Type - 如果请求中的内容类型是错误的
422 Unprocessable Entity - 用来表示校验错误
429 Too Many Requests - 由于请求频次达到上限而被拒绝访问
- 本文作者:So1n
- 本文链接:http://so1n.me/2017/12/26/25_how_to_design_RESTurl/index.html
- 版权声明:本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!