参考资料:
REST理论
基础概念
REST(Resource Representational State Transfer),中文译作"表现层状态转移",核心思想是:
- 用URL定位资源
- 用HTTP动词(GET, POST, DELETE, PUT)描述操作
- 用HTTP Status Code传递状态信息
- 用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.user
和request.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
- 失败: 只通过状态码反馈,响应头无相关信息
- 请求: 当用户进行身份验证后,app会返回一个
- 评价:
- 优点:
- 用户凭据仅在登录时发送一次,减少了敏感信息的暴露
- 可以通过CSRF Token防止CSRF攻击
- 缺点:
- 存储数据在服务端,资源占用大【可以通过redis解决】
- 分布式系统需要考虑session共享问题【可以通过redis解决】
- 浏览器可能会禁用cookie【尤其是移动端】
- 优点:
Token Authentication
- 原理:
- 每创建一个用户,都通过信号创建对应的Token
- 客户端需要在每个请求的
headers
中添加一个Token
来进行身份验证 - 为了方便起见,许多开发人员使用
SessionStorage
或localStorage
来存储访问令牌,以便在每个请求中发送这些标记
- 实现:
- 前提: 需要在
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
字段,其中包含TokenAuthorization: Token xxxxx
- 响应:
- 成功:
request.user
变为AUTH_USER_MODEL
的实例request.auth
变为rest_framework.authtoken.models. Token
的实例
- 失败: 响应头中多出
WWW-Authenticate
字段
- 成功:
- 请求: 客户端发送请求时需要在
- 评价:
- 优点:
- 服务器上不需要存储会话数据,适用于分布式架构和负载均衡
- 更加适合API授权,可以降低CSRF风险【Token通常存储在客户端的
localStorage
或sessionStorage
中,可以避免像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")), ]