手记

什么是restful?怎样用通俗的语言解释restful?

什么是RESTful

一. 什么是RESTful 

  • REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”

  • REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态

  • REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”

  • 所有的数据,不过是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性

  • 对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)

二. RESTful API设计

  • API与用户的通信协议,总是使用HTTPs协议。

  • 域名 

    • https://api.example.com                         尽量将API部署在专用域名(会存在跨域问题)

    • https://example.org/api/                        API很简单

  • 版本

    • URL,如:https://api.example.com/v1/

    • 请求头                                                  跨域时,引发发送多次请求

  • 路径,视网络上任何东西都是资源,均使用名词表示(可复数)

    • https://api.example.com/v1/zoos

    • https://api.example.com/v1/animals

    • https://api.example.com/v1/employees

  • method

    • GET      :从服务器取出资源(一项或多项)

    • POST    :在服务器新建一个资源

    • PUT      :在服务器更新资源(客户端提供改变后的完整资源)

    • PATCH  :在服务器更新资源(客户端提供改变的属性)

    • DELETE :从服务器删除资源

  • 过滤,通过在url上传参的形式传递搜索条件

    • https://api.example.com/v1/zoos?limit=10:指定返回记录的数量

    • https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置

    • https://api.example.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数

    • https://api.example.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序

    • https://api.example.com/v1/zoos?animal_type_id=1:指定筛选条件

  • 状态码

    200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。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 - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
    
    更多看这里:http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

  • 错误处理,状态码是4xx时,应返回错误信息,error当做key。

    1

    2

    3

    {

        error: "Invalid API key"

    }

  • 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范。

    1

    2

    3

    4

    5

    6

    GET /collection:返回资源对象的列表(数组)

    GET /collection/resource:返回单个资源对象

    POST /collection:返回新生成的资源对象

    PUT /collection/resource:返回完整的资源对象

    PATCH /collection/resource:返回完整的资源对象

    DELETE /collection/resource:返回一个空文档

  • Hypermedia API,RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。

    1

    2

    3

    4

    5

    6

    {"link": {

      "rel":   "collection https://www.example.com/zoos",

      "href""https://api.example.com/zoos",

      "title": "List of zoos",

      "type""application/vnd.yourformat+json"

    }}

  摘自:http://www.ruanyifeng.com/blog/2014/05/restful_api.html 

三. 基于Django实现

路由系统:

1

2

3

urlpatterns = [

    url(r'^users', Users.as_view()),

]

CBV视图:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

from django.views import View

from django.http import JsonResponse


class Users(View):

    def get(self, request, *args, **kwargs):

        result = {

            'status': True,

            'data': 'response data'

        }

        return JsonResponse(result, status=200)


    def post(self, request, *args, **kwargs):

        result = {

            'status': True,

            'data': 'response data'

        }

        return JsonResponse(result, status=200

四. 基于Django Rest Framework框架实现

1. 基本流程

url.py

1

2

3

4

5

6

from django.conf.urls import url, include

from web.views.s1_api import TestView


urlpatterns = [

    url(r'^test/', TestView.as_view()),

]

views.py

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

from rest_framework.views import APIView

from rest_framework.response import Response



class TestView(APIView):

    def dispatch(self, request, *args, **kwargs):

        """

        请求到来之后,都要执行dispatch方法,dispatch方法根据请求方式不同触发 get/post/put等方法

         

        注意:APIView中的dispatch方法有好多好多的功能

        """

        return super().dispatch(request, *args, **kwargs)


    def get(self, request, *args, **kwargs):

        return Response('GET请求,响应内容')


    def post(self, request, *args, **kwargs):

        return Response('POST请求,响应内容')


    def put(self, request, *args, **kwargs):

        return Response('PUT请求,响应内容')

上述是rest framework框架基本流程重要的功能是在APIView的dispatch中触发。

2.  认证和授权

a. 用户url传入的token认证

urls.py

views.py

b. 请求头认证

urls.py

views.py

c. 多个认证规则

urls.py

views.py

d. 认证和权限

urls.py

views.py

e. 全局使用

上述操作中均是对单独视图进行特殊配置,如果想要对全局进行配置,则需要再配置文件中写入即可。

settings.py

urls.py

views.py

3. 用户访问次数/频率限制

a. 基于用户IP限制访问频率

urls.py

views.py

b. 基于用户IP显示访问频率(利于Django缓存)

settings.py

urls.py

views.py

c. view中限制请求频率

settings.py

urls.py

views.py

d. 匿名时用IP限制+登录时用Token限制

settings.py

urls.py

views.py

e. 全局使用

settings

4. 版本

a. 基于url的get传参方式

如:/users?version=v1

settings.py

urls.py

views.py

b. 基于url的正则方式

如:/v1/users/

settings.py

urls.py

views.py

c. 基于 accept 请求头方式

如:Accept: application/json; version=1.0

settings.py

urls.py

views.py

d. 基于主机名方法

如:v1.example.com

settings.py

urls.py

views.py

e. 基于django路由系统的namespace

如:example.com/v1/users/

settings.py

urls.py

views.py

f. 全局使用

settings.py

5. 解析器(parser) 

根据请求头 content-type 选择对应的解析器就请求体内容进行处理。

a. 仅处理请求头content-type为application/json的请求体

urls.py

views.py

b. 仅处理请求头content-type为application/x-www-form-urlencoded 的请求体

urls.py

views.py

c. 仅处理请求头content-type为multipart/form-data的请求体

urls.py

views.py

upload.html

d. 仅上传文件

urls.py

views.py

upload.html

e. 同时多个Parser

当同时使用多个parser时,rest framework会根据请求头content-type自动进行比对,并使用对应parser

urls.py

views.py

f. 全局使用

settings.py

urls.py

views.py

注意:个别特殊的值可以通过Django的request对象 request._request 来进行获取

6. 序列化

序列化用于对用户请求数据进行验证和数据进行序列化。

a. 自定义字段

urls.py

views.py

b. 基于Model自动生成字段

urls.py

views.py

c. 生成URL

urls.py

views.py

d. 自动生成URL

urls.py

views.py

7. 分页

a. 根据页码进行分页

urs.py

views.py

b. 位置和个数进行分页

urls.py

views.py

c. 游标分页

urls.py

views.py

8. 路由系统

a. 自定义路由

urls.py

views.py

b. 半自动路由

urls.py

views.py

c. 全自动路由

urls.py

views.py

9. 视图

a. GenericViewSet

urls.py

views.py

b. ModelViewSet(自定义URL)

urls.py

views.py

c. ModelViewSet(rest framework路由)

urls.py

views.py

10. 渲染器

根据 用户请求URL 或 用户可接受的类型,筛选出合适的 渲染组件。
用户请求URL:

  • http://127.0.0.1:8000/test/?format=json

  • http://127.0.0.1:8000/test.json

用户请求头:

  • Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

a. json

访问URL:

  • http://127.0.0.1:8000/test/?format=json

  • http://127.0.0.1:8000/test.json

  • http://127.0.0.1:8000/test/ 

urls.py

views.py

b. 表格

访问URL:

  • http://127.0.0.1:8000/test/?format=admin

  • http://127.0.0.1:8000/test.admin

  • http://127.0.0.1:8000/test/ 

views.py

c. Form表单

访问URL:

  • http://127.0.0.1:8000/test/?format=form

  • http://127.0.0.1:8000/test.form

  • http://127.0.0.1:8000/test/ 

views.py

d. 自定义显示模板

访问URL:

  • http://127.0.0.1:8000/test/?format=html

  • http://127.0.0.1:8000/test.html

  • http://127.0.0.1:8000/test/ 

urls.py

views.py

userdetail.html

e. 浏览器格式API+JSON

访问URL:

  • http://127.0.0.1:8000/test/?format=api

  • http://127.0.0.1:8000/test.api

  • http://127.0.0.1:8000/test/ 

views.py

注意:如果同时多个存在时,自动根据URL后缀来选择渲染器。

面向前台

“刚才我们向前台点了一杯拿铁,这个过程可以用这段文字来描述”,说着,我在纸上写下了这段JSON,虽然她不知道什么叫JSON,但理解这段文字对于英语专业8级的她,实在再简单不过。

{
    "addOrder": {
        "orderName": "latte"
    }
}复制代码

“我们通过这段文字,告诉前台,新增一笔订单,订单是一杯拿铁咖啡”,接着,前台给我们返回这么一串回复:

{
    "orderId": "123456"
}复制代码

“订单ID?还是订单编号?”

“恩恩,就是订单编号”

“那我们就等着前台喊‘订单123456的客户可以取餐了’,然后就可以开吃了!”

“哈哈,你真聪明,不过,在这之前,假设我们有一张会员卡,我们想查询一下这张会员卡的余额,这时候,要向前台发起另一个询问”,我继续在纸上写着:

{
    "queryBalance": {
        "cardId": "886333"
    }
}复制代码

“查询卡号为886333的卡的余额?”

“真棒!接着,查询的结果返回来了”

{
    "balance": "0"
}复制代码

“切,没钱......”

“哈哈,没钱,现在我们要跟前台说,这杯咖啡不要了”,我在纸上写到:

{
    "deleteOrder": {
        "orderId": "123456"
    }
}复制代码

“哼,这就把订单取消啦?”

面向资源

“现在这家咖啡店越做越大,来喝咖啡的人越来越多,单靠前台显然是不行的,店主决定进行分工,每个资源都有专人负责,我们可以直接面向资源操作。”

"面向资源?”

“是的,比如还是下单,请求的内容不变,但是我们多了一条消息”,我在纸上画出这次的模型:

/orders

{
    "addOrder": {
        "orderName": "latte"
    }
}复制代码

“多了一个斜杠和orders?这是什么意思?”

“这个表示我们这个请求是发给哪个资源的,订单是一种资源,我们可以理解为是咖啡厅专门管理订单的人,他可以帮我们处理所有有关订单的操作,包括新增订单、修改订单、取消订单等操作”

“Soga...”

“接着还是会返回订单的编号给我们”

{
    "orderId": "123456"
}复制代码

“下面,我们还是要查询会员卡余额,这次请求的资源变成了cards”

/cards

{
    "queryBalance": {
        "cardId": "886333"
    }
}复制代码

“接下来是取消订单”

“这个我会”,说着,她抢走我手上的笔,在纸上写了起来:

/orders

{
    "deleteOrder": {
        "orderId": "123456"
    }
}复制代码

打上标签

“接下来,店主还想继续优化他的咖啡厅的服务流程,他发现负责处理订单的员工,每次都要去订单内容里面看是新增订单还是删除订单,还是其他的什么操作,十分不方便,于是规定,所有新增资源的请求,都在请求上面写上大大的‘POST’,表示这是一笔新增资源的请求”

“其他种类的请求,比如查询类的,用‘GET’表示,删除类的,用‘DELETE’表示”

“还有修改类的,修改分为两种,第一种,如果这个修改,无论发送多少次,最后一次修改后的资源,总是和第一次修改后的一样,比如将拿铁改为猫屎,那么用‘PUT’表示;第二种,如果这个修改,每次修改都会让这个资源和前一次的不一样,比如是加一杯咖啡,那么这种请求用‘PATCH’或者‘POST’表示”,一口气讲了这么多,发现她有点似懂非懂。

“来,我们再来重复上面那个过程,来一杯拿铁”,我边说边画着:

POST /orders

{
    "orderName": "latte"
}复制代码

"请求的内容简洁多啦,不用告诉店员是addOrder,看到POST就知道是新增",她听的很认真,理解的也很透彻。

"恩恩,返回的内容还是一样"

{
    "orderId": "123456"
}复制代码

“接着是查询会员卡余额,这次也简化了很多”

GET /cards

{
    "cardId": "886333"
}复制代码

“这个请求我们还可以进一步优化为这样”

GET /cards/886333复制代码

“Soga,直接把要查询的卡号写在后面了”

“没错,接着,取消订单”

DELETE /orders/123456复制代码

完美服务

“忽然有一天,有个顾客抱怨说,他买了咖啡后,不知道要怎么取消订单,咖啡厅一个店员回了一句,你不会看我们的宣传单吗,上面不写着:

DELETE /orders/{orderId}复制代码

顾客反问道,谁会去看那个啊,店员不服,又说到,你瞎了啊你......据说后面两人吵着吵着还打了起来...”

“噗,真是悲剧...”

“有了这次教训,店长决定,顾客下了单之后,不仅给他们返回订单的编号,还给顾客返回所有可以对这个订单做的操作,比如告诉用户如何删除订单。现在,我们还是发出请求,请求内容和上一次一样”

POST /orders

{
    "orderName": "latte"
}复制代码

“但是这次返回时多了些内容”

{
    "orderId": "123456",
    "link": {
        "rel": "cancel",
        "url": "/order/123456"
    }
}复制代码

“这次返回时多了一项link信息,里面包含了一个rel属性和url属性,rel是relationship的意思,这里的关系是cancel,url则告诉你如何执行这个cancel操作,接着你就可以这样子来取消订单啦”

DELETE /orders/123456复制代码

“哈哈,这服务真是贴心,以后再也不用担心店员和顾客打起来了”

“订单123456的客户可以取餐了”,伴随着咖啡厅的广播,我们吃起了下午茶,一杯拿铁,两支吸管......


0人推荐
随时随地看视频
慕课网APP