xubzhlin 2019-10-24
网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备…)。因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信。这导致API
构架的流行,甚至出现APIFirst
的设计思想。RESTful API
是目前比较成熟的一套互联网应用程序的API
设计理论。
REST(Representational State Transfer)
表述性状态转换,REST
指的是一组架构约束条件和原则。 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful
架构。REST
本身并没有创造新的技术、组件或服务,而隐藏在RESTful
背后的理念就是使用Web
的现有特征和能力, 更好地使用现有Web
标准中的一些准则和约束。虽然REST
本身受Web
技术的影响很深, 但是理论上REST
架构风格并不是绑定在HTTP
上,只不过目前HTTP
是唯一与REST
相关的实例。
ur
l链接,通过向链接发生不同的类型请求与数据得到相应的响应数据API
: https://api.map.baidu.com/place/v2/searchak: 6E823f587c95f0148c19993539b99295 region: 上海 query: 肯德基 output: json
API
部署在专用域名之下。https://api.example.com
API
很简单,不会有进一步扩展,可以考虑放在主域名下。https://www.example.org/api/
应该将API
的版本号放入URL
。
http://www.example.com/app/1.0/foo http://www.example.com/app/1.1/foo http://www.example.com/app/2.0/foo
另一种做法是,将版本号放在HTTP
头信息中,但不如放入URL
方便和直观。Github就采用了这种做法。
因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URL
。版本号可以在HTTP
请求头信息的Accept
字段中进行区分(参见Versioning REST Services):
Accept: vnd.example-com.foo+json; version=1.0 Accept: vnd.example-com.foo+json; version=1.1 Accept: vnd.example-com.foo+json; version=2.0
路径又称“终点”(endpoint),表示API
的具体网址,每个网址代表一种资源(resource)
举例来说,以下是不好的例子:
/getProducts /listOrders /retreiveClientByOrder?orderId=1
对于一个简洁结构,你应该始终用名词。 此外,利用的HTTP方法可以分离网址中的资源名称的操作。
GET /products :将返回所有产品清单 POST /products :将产品新建到集合 GET /products/4 :将获取产品 4 PATCH(或)PUT /products/4 :将更新产品 4
API
中的名词应该使用复数。无论子资源或者所有资源。举例来说,获取产品的API
可以这样定义
获取单个产品:http://127.0.0.1:8080/AppName/rest/products/1 获取所有产品: http://127.0.0.1:8080/AppName/rest/products
对于资源的具体操作类型,由HTTP
动词表示。
常用的HTTP动词有下面四个(括号里是对应的SQL
命令)。
CURD Create、Update、Read、Delete 增删查改,这四个数据库的常用操作
还有三个不常用的HTTP动词。
下面是一些例子。
GET /zoos:列出所有动物园 POST /zoos:新建一个动物园(上传文件) GET /zoos/ID:获取某个指定动物园的信息 PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息) PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息) DELETE /zoos/ID:删除某个动物园 GET /zoos/ID/animals:列出某个指定动物园的所有动物 DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
如果记录数量很多,服务器不可能都将它们返回给用户。API
应该提供参数,过滤返回结果。
下面是一些常见的参数。query_string
查询字符串,地址栏后面问号后面的数据,格式: name=xx&sss=xxx
完整的URL地址格式: 协议://域名(IP):端口号/路径?查询字符串#锚点
?limit=10:指定返回记录的数量 ?offset=10:指定返回记录的开始位置。 ?page=2&per_page=100:指定第几页,以及每页的记录数。 ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。 ?animal_type_id=1:指定筛选条件
参数的设计允许存在冗余,即允许API
路径和URL
参数偶尔有重复。比如,GET /zoos/ID/animals
与GET /animals?zoo_id=ID
的含义是相同的。
1xx 表示当前本次请求还是持续,没结束 2xx 表示当前本次请求成功 3xx 表示当前本次请求成功,但是服务器进行代理操作/重定向 4xx 表示当前本次请求失败,主要是客户端发生了错误 5xx 表示当前本次请求失败,主要是服务器发生了错误
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。
200 OK - [GET]:服务器成功返回用户请求的数据 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务) 204 NO CONTENT - [DELETE]:用户删除数据成功。 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。 507 数据存储出错,往往数据库操作错误出错,服务器就返回这个
状态码的完全列表参见这里。
如果状态码是4xx
,服务器就应该向用户返回出错信息。一般来说,返回的信息中将error
作为键名,出错信息作为键值即可。
{ error: "Invalid API key" }
针对不同操作,服务器向用户返回的结果应该符合以下规范。
json
)json
)json
)RESTful API
最好做到`Hypermedia
(即返回结果中提供链接,连向其他API方法),使得用户不查文档,也知道下一步应该做什么。
比如,`Github
的API
就是这种设计,访问api.github.com会得到一个所有可用API
的网址列表。
{ "current_user_url": "https://api.github.com/user", "authorizations_url": "https://api.github.com/authorizations", // ... }
从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。
{ "message": "Requires authentication", "documentation_url": "https://developer.github.com/v3" }
上面代码表示,服务器给出了提示信息,以及文档的网址。
服务器返回的数据格式,应该尽量使用json
,避免使用XML
。
XML
是W3C
为了替换HTML
研发出来的,但是现在很明显失败了。
XML
配置文件<xml version="1.0" charset="utf-8"> <name>张三</name> <sex>男</sex> <age>18</age> </xml>
json
配置文件{ “name”:"张三", "sex":"男", "age":18 }
# 路由层url.py from app import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^cbv/', views.CBVTest.as_view()), url(r'^books/', views.books), url(r'^book/(?P<id>\d+)/', views.book), ]
# 视图层view.py from django.http import JsonResponse book_list = [{'id': 1, 'name': '红楼梦'}, {'id': 2, 'name': '水浒传'}] def books(request): if request.method == "GET": if 'ak' not in request.GET: return JsonResponse({ 'status': '101', 'msg': 'ak不存在' }, json_dumps_params={'ensure_ascii': False}) ak = request.GET.get('ak') if ak != '123abc': return JsonResponse({ 'status': '200', 'msg': 'ak非法' }, json_dumps_params={'ensure_ascii': False}) return JsonResponse({ 'status': '0', 'msg': 'ok', 'results': book_list }, json_dumps_params={'ensure_ascii': False}) if request.method == 'POST': name = request.POST.get('name') id = len(book_list) + 1 book = {'id': id, 'name': name} book_list.append(book) return JsonResponse({ 'status': '0', 'msg': 'ok', 'results': book }, json_dumps_params={'ensure_ascii': False})