RESTful API学习笔记
系统学习 Web开发 0 313

参考资料:

REST理论

基础概念

REST(Resource Representational State Transfer),中文译作"表现层状态转移",核心思想是:

  1. 用URL定位资源
  2. 用HTTP动词(GET, POST, DELETE, PUT)描述操作
  3. 用HTTP Status Code传递状态信息
  4. 用JSON、XML传递结果

概念辨析:

  • REST: 它是一种软件设计模式、风格和架构,用于构建分布式客户端-服务器应用程序和 Web API。其指导原则包括使用基于资源的URL,使用标准HTTP方法(GET、POST、PUT、PATCH、OPTIONS、DELETE等)实现对资源的操作,使用无状态请求执行操作等等。
  • RESTful: 这个词用来描述遵循REST原则的Web API。它使用HTTP方法(GET、POST、PUT、DELETE等)并提供一个基于URL的数据API,使用JSON、XML或其他格式作为数据传输格式。

设计指南

  • 域名: 推荐使用api.example.com
  • 版本: API的版本号放入URL或者HTTP头中
  • 路径(Endpoint): 因为"资源"表示一种实体,所以一般是名词的复数
  • HTTP动词:
    • GET(SELECT): 从服务器取出资源(一项或多项)
    • POST(CREATE): 在服务器新建一个资源。
    • PUT(UPDATE): 在服务器更新资源(客户端提供改变后的完整资源)
    • PATCH(UPDATE): 在服务器更新资源(客户端提供改变的属性)
    • DELETE(DELETE): 从服务器删除资源
    • HEAD: 获取资源的元数据
    • OPTIONS: 获取信息,关于资源的哪些属性是客户端可以改变的
  • 过滤信息(Filtering):
    • ?limit=10: 指定返回记录的数量
    • ?offset=10: 指定返回记录的开始位置。
    • ?page=2&per_page=100: 指定第几页,以及每页的记录数。
    • ?sortby=name&order=asc: 指定返回结果按照哪个属性排序,以及排序顺序。
    • ?animal_type_id=1: 指定筛选条件
  • 状态码(Status Codes):
    • 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 - [*]: 服务器发生错误,用户将无法判断发出的请求是否成功
  • 错误处理(Error handling): 如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可
  • 返回结果
    • GET /collection: 返回资源对象的列表(数组)
    • GET /collection/resource: 返回单个资源对象
    • POST /collection: 返回新生成的资源对象
    • PUT /collection/resource: 返回完整的资源对象
    • PATCH /collection/resource: 返回完整的资源对象
    • DELETE /collection/resource: 返回一个空文档
  • Hypermedia API: 返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么【以访问api.github.com为例】
    {
      "current_user_url": "https://api.github.com/user",
      "authorizations_url": "https://api.github.com/authorizations",
      // ...
    }
  • 其他
    • API的身份认证应该使用OAuth 2.0框架。
    • 服务器返回的数据格式,应该尽量使用JSON,避免使用XML。

举例说明

  • 获取文章列表,使用GET请求,并返回一个JSON数组
    GET /api/posts
  • 获取特定文章的详细信息,使用GET请求,并返回一个JSON对象
    GET /api/posts/123
  • 创建一篇文章,使用POST请求,并返回HTTP状态码201,以及新创建文章的JSON对象
    POST /api/posts
  • 更新一篇文章,使用PUT请求,并返回HTTP状态码200,以及更新后文章的JSON对象
    PUT /api/posts/123
  • 删除一篇文章,使用DELETE请求,并返回HTTP状态码204,表示没有返回内容
    DELETE /api/posts/123
  • 获取某篇文章的评论列表,使用GET请求,并返回一个JSON数组
    GET /api/posts/123/comments
  • 创建一条评论,使用POST请求,并返回HTTP状态码201,以及新创建评论的JSON对象
    POST /api/posts/123/comments
  • 删除一条评论,使用DELETE请求,并返回HTTP状态码204,表示没有返回内容
    DELETE /api/posts/123/comments/456

DRF实战

序列化

  • 功能(相对django自带的)
    • 验证前端传来的request.data(需要设置验证参数)
    • 同时序列化多个对象
    • 在序列化的过程中添加上下文
    • 实现对无效数据的异常处理

视图

  • 函数式视图(Function Based View)
  • 类视图(Classed Based View)
  • 通用类视图(Generic Classed Based View)
    class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, GenericAPIView):
        pass
  • DRF的视图集(viewsets)
    class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet):
        pass

路由

  • 函数视图路由
    urlpatterns = [
        path("fbv/", view.index, name="index"),
    ]
  • 类视图路由
    urlpatterns = [
        path("cbv/", views.CourseList.as_view(), name="cby-list"),
    ]
  • 视图集路由
    urlpatterns = [
        path("viewsets/", views.CourseViewSet.as_view({"get": "list", "post": "create"}, name="viewsets-list"),
    ]
  • 自动化路由
    from django.urls import path, include
    from rest_framework.routers import DefaultRouter
    from course import views
    router = DefaultRouter()
    router.register(prefix="viewsets", viewset=views.CourseViewSet)
    
    urlpatterns = [
        path("", include(router.urls)),
    ]

认证

  • 身份验证早于权限验证等
  • 主要依赖于django.contrib.auth来设置request.userrequest.auth
  • 顺序执行DEFAULT_AUTHENTICATION_CLASSES,直至成功
  • 可以在不同的视图中设置各自的认证【通过authentication_classes类属性或者装饰器】

Basic Authentication

  • 使用:
    • 请求: 客户端发送请求时需要在headers中包含Authorization字段,其中包含用Base64编码加密后的用户名和密码
      Authorization: Basic base64_encode(username:password)
    • 响应:
      • 成功:
        • request.user变为AUTH_USER_MODEL的实例
        • request.auth变为None
      • 失败: 响应头中多出WWW-Authenticate: Basic realm="api"字段【realm代表用户访问的受保护资源所属的实体或环境,如公司名称、应用程序名称、网站名称等】
  • 评价:
    • 优点:
      • 相对简单,易于客户端使用,也易于服务端实现
      • 无状态,即可免除了记录会话信息的义务,所以它自然对于事务处理量巨大的网站负载均衡起到了积极的作用
    • 缺点:
      • 明文传输,不太安全(即便使用SSL/TLS加密,弱密码也会被攻破)

Session Authentication

  • 原理: 客户端与应用程序之间的通信是通过cookie实现的,每个cookie包含一个session ID,它与服务器端相应的session数据相关联【有状态认证】
  • 使用:
    • 请求: 当用户进行身份验证后,app会返回一个cookie,该cookie包含session ID,客户端会在随后的每个请求中带上这个cookie,从而实现身份验证
    • 响应:
      • 成功: 同Basic Authentication
      • 失败: 只通过状态码反馈,响应头无相关信息
  • 评价:
    • 优点:
      • 用户凭据仅在登录时发送一次,减少了敏感信息的暴露
      • 可以通过CSRF Token防止CSRF攻击
    • 缺点:
      • 存储数据在服务端,资源占用大【可以通过redis解决】
      • 分布式系统需要考虑session共享问题【可以通过redis解决】
      • 浏览器可能会禁用cookie【尤其是移动端】

Token Authentication

  • 原理:
    • 每创建一个用户,都通过信号创建对应的Token
    • 客户端需要在每个请求的headers中添加一个Token来进行身份验证
    • 为了方便起见,许多开发人员使用SessionStoragelocalStorage来存储访问令牌,以便在每个请求中发送这些标记
  • 实现:
    • 前提: 需要在INSTALLED_APPS内添加rest_framework.authtoken
    • Token生成机制:
      • 使用manage.py生成Token:
        python manage.py drf_create_token [username]
      • 通过Django的信号机制生成Token
        from diango.db.models.signals import post_save
        @receiver(post_save, sender=settings.AUTH_USER_MODEL)
        # Django的信号机制
        def generate_token(sender, instance=None, created=False, **kwargs):
            """
            创建用户时自动生成Token
            :param sender:
            :param instance:
            :param created:
            :param kwargs:
            :return:
            """
            if created:
                Token.objects.create(user=instance)
    • Token获取方法:
      from rest_framework.authtoken import views
      urlpatterns = [
          path("api-token-auth", views.obtain_auth_token),
      ]
  • 调用:
    • 请求: 客户端发送请求时需要在headers中包含Authorization字段,其中包含Token
      Authorization: Token xxxxx
    • 响应:
      • 成功:
        • request.user变为AUTH_USER_MODEL的实例
        • request.auth变为rest_framework.authtoken.models. Token的实例
      • 失败: 响应头中多出WWW-Authenticate字段
  • 评价:
    • 优点:
      • 服务器上不需要存储会话数据,适用于分布式架构和负载均衡
      • 更加适合API授权,可以降低CSRF风险【Token通常存储在客户端的localStoragesessionStorage中,可以避免像Cookies那样自动被浏览器在每个请求中发送】
      • Authorization头部字段传递token的方式符合OAuth 2.0的规范,这使得它成为一个广泛接受的标准

权限

  • 原理: DRF的权限检查与认证类似,都按照指定顺序检查:
    REST_FRAMEWORK = {
        "DEFAULT_PERMISSION_CLASSES": [
            "rest_framework.permissions.IsAuthenticated",
        ], 
    }
  • 自带权限类:
    • IsAuthenticatedOrReadOnly
    • IsAuthenticated
    • IsAdminUser
    • AllowAny
  • 设置: 通过设置permission_classes装饰器或者类属性
  • 自定义权限类:【在permissions.py中】
    class IsOwnerReadOnly(permissions.BasePermission):
        """只允许对象的所有者能够编辑"""
        def has_object_permission(self, request, view, obj):
            """
            所有的request请求都有读仅限,因此一律允许GET/HEAD/OPTIONS方法
            :param request:
            :param View:
            :param obj:
            :return: bool
            """
            if request.method in ("GET", "HEAD", "OPTIONS"): # 这里可以直接用SAFE_METHODS代替
                return True
            return request.user == obj.teacher

文档

  • 使用简易的:
    # settings.py
    REST_FRAMEWORK = {
        "DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.AutoSchema",
    }
    # urls.py
    from rest_framework.schemas import get_schema_view
    schema_view = get_schema_view(title="DRF API文档", description="xxx")
    urlpatterns = [
        path("schema/", schema_view),
    ]
  • 使用CoreAPI的:【CoreAPI提供动态生成客户端、描述和调用API的能力】
    # settings.py
    REST_FRAMEWORK = {
        "DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema",
    }
    # urls.py
    from rest_framework.documentation import include_docs_urls
    urlpatterns = [
        path("docs/", include_docs_urls(title="DRF API文档", description="xxx")),
    ]
编写
预览