一、什么是restful

  • REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”
  • REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态
  • 所有的数据,不过是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性
  • 对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)
    总结就是:它就是一种规范,面向资源编程(把网络中所有东西,想象成资源)

1.1、规范(10条)

# 先定义好标准, 配置域名后, 根据请求类型,定义好路径,写好api版本,过滤掉非法字符串,返回结果需要定义好错误类型以及状态码,最后不管正常与否返回URL
  • API与用户的通信协议,总是使用HTTPs协议: https比http安全
  • 域名:
https://api.example.com   尽量将API部署在专用域名(会存在跨域问题)
https://example.org/api/  API很简单
    例如写一个查询所有图书的api接口:https://api.example.com/books
     						   https://127.0.0.1/api/books
  • 版本: 每个接口都应该有版本
https://api.example.com/v1/    
https://127.0.0.1/api/v2/books  (推荐用这种)
    如果写到请求头 跨域时,引发发送多次请求
  • **路径:**视网络上任何东西都是资源,均使用名词表示(可复数)
https://api.example.com/v1/books
https://api.example.com/v1/animals
https://api.example.com/v1/employees

不能这么写:
    - 获取所有图书:https://127.0.0.1/api/get_all_books
    - 新增一本书:  https://127.0.0.1/api/add_book

统一都用这个: https://api.example.com/v1/books
  • method: 请求方式

方法

说明

GET

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

POST

在服务器新建一个资源

PUT

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

PATCH

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

DELETE

从服务器删除资源

HEAD

获取资源的元数据

OPTIONS

获取资源, 关于资源的哪些属性是客户端可以改变的

# 例
    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:删除某个指定动物园的指定动物
  • 过滤: 通过在url上传参的形式传递搜索条件
https://api.example.com/v1/zoos?limit=10
# 常见参数
    ?limit=10:指定返回记录的数量
    ?offset=10:指定返回记录的开始位置
    ?page=2&per_page=100:指定第几页,以及每页的记录数
    ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
    ?animal_type_id=1:指定筛选条件

# 参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的
  • 状态码 , 状态码-rfc2616
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格式)。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
  • 错误处理
# 如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可, 比如: { error: "Invalid API key"}
  • 返回结果
# 针对不同操作,服务器向用户返回的结果应该符合以下规范。
	GET /books:返回资源对象的列表(数组)
	GET /books/1:返回单个资源对象
	POST /books:返回新生成的资源对象    -新增,传数据,一旦新增完成,把新的资源对象返回
	PUT /books/1:返回完整的资源对象
	PATCH /books/1:返回完整的资源对象
	DELETE /books/1:返回一个空文档
  • Hypermedia API
# RESTful API最好做到 Hypermedia, 即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
    {status:100, "msg": "成功" "url": "127.0.0.1/books/1"}
核心:返回结果中提供链接
  • 总结规范行为

HTTP 方法

行为

示例

GET

获取资源的信息

http://example.com/api/orders

GET

获取某个特定资源的信息

http://example.com/api/orders/123

POST

创建新资源

http://example.com/api/orders

PUT

更新资源

http://example.com/api/orders/123

DELETE

删除资源

http://example.com/api/orders/123

二、源码分析

2.1、CBV源码分析

  1. url
from django.urls import path
from app01.views import Cbv_view

urlpatterns = [
    path("cbv/", Cbv_view.as_view(), name="cbv"),
]

class Cbv_view(View):
    def get(self, request):
        # django 中 request的属性, 注意后续与rest中request的属性
        # django.core.handlers.wsgi.WSGIRequest
        print(type(request))
        return HttpResponse("get")
  1. View
class View:
    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)
  1. as_views
# 类属性
	@classonlymethod
    def as_view(cls, **initkwargs):
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            self.setup(request, *args, **kwargs)
            # 执行:dispatch:谁的dispatch方法?写的cbv的那个c,视图中的那个视图类
            # 比如访问时是get, 那么就直接返回GET值
            return self.dispatch(request, *args, **kwargs)
        view.view_class = cls
        view.view_initkwargs = initkwargs
  1. dispath
def dispatch(self, request, *args, **kwargs):
		# request.method 前台请求的方法,转成了小写
		# http_method_names View中定义的一个列表:是一堆请求方式
        if request.method.lower() in self.http_method_names:
            # getattr的第三个参数是默认值:self.http_method_not_allowed
			# 拿到get方法的内存地址
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)
  1. CBV 总结
url获取method请求,  到 as_view() --> view(闭包) --> dispatch --> 分发到不同的函数、执行函数、拿到结果、并返回

2.2、DRF

全称: djangorestframework,做为一个app,要使用就需要在sessing中注册app

  • 使用步骤
# 三步曲
1、安装drf,  pip install djangorestframework
2、app中注册, INSTALLED_APPS = ['rest_framework']
3、导入模块,  from rest_framework.views import APIView
4、继承使用,  class Test(APIView): pass

2.2.1、request源码分析

  • urls
from app01.views import Test

urlpatterns = [
    path("test/", Test.as_view(), name="cbv"),
]
  • views
class Test(APIView):
    def get(self, request):
        # <class 'rest_framework.request.Request'>
        return HttpResponse("ok")
  • ApiView分析
  • as_view函数
# 注意:
	@classmethod
    def as_view(cls, **initkwargs):  # 这里是ApiView的as_view
        # 这里继承是父类的as_view方法,又回到 View/as_view方法中
        view = super().as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs

        # 当继承ApiView之后会局部禁用CSRF方法
        return csrf_exempt(view)
    
# view/as_view
    @classonlymethod
    def as_view(cls, **initkwargs):
        def view(request, *args, **kwargs):
            return self.dispatch(request, *args, **kwargs)

# 根据mro继承关系, 此时的dispatch就应该是父类的dispatch,也就是ApiView的
  • ApiView/dispatch
def dispatch(self, request, *args, **kwargs):
 """
 `.dispatch()` is pretty much the same as Django's regular dispatch,
 but with extra hooks for startup, finalize, and exception handling.
 """
 self.args = args
 self.kwargs = kwargs
 # 往下看源码,看完在回到try中
 request = self.initialize_request(request, *args, **kwargs)
 self.request = request
 self.headers = self.default_response_headers  # deprecate?

 try:
     # 在 ApiView/initialize_request 下看 initial方法,这里处理了各种组件
     # 比如认证,权限,频率相关
     self.initial(request, *args, **kwargs)
# 注意看这, apiview的dispath其实与view的一样,都是分发获取头部信息
     # 处理完之后直接dispatch分发
     if request.method.lower() in self.http_method_names:
         handler = getattr(self, request.method.lower(),
                           self.http_method_not_allowed)
     else:
         handler = self.http_method_not_allowed

     response = handler(request, *args, **kwargs)
# 。。。 在这 dispatch方法 也就是 分发到不同的函数
 return self.response
  • ApiView/initialize_request
def initialize_request(self, request, *args, **kwargs):
     """
     Returns the initial request object.
     """
     parser_context = self.get_parser_context(request)

     # 返回的是包装了 django request的方法
     return Request(
         request,
         # 解析使用
         parsers=self.get_parsers(),
         # 认证组件
         authenticators=self.get_authenticators(),
         # 分页组件
         negotiator=self.get_content_negotiator(),
         # 解析组件
         parser_context=parser_context
     )
 # request头部包装好之后,回上一步
  • ApiView/initial
def initial(self, request, *args, **kwargs):
      # 认证组件
      self.perform_authentication(request)
      # 权限组件
      self.check_permissions(request)
      # 频率组件
      self.check_throttles(request)
  • 总结
url获取method请求,  到 apiview/as_view() -->  view/as_view  --> view --> apiview/dispatch --> self.initialize_request --> self.initial --> method 分发到不同的函数、执行函数、拿到结果、并返回

2.2.2、数据获取源码分析

  • 继承方法
class Test(APIView):
        def get(self, request):
            # http://127.0.0.1:8001/test/?name=xiong
            # <class 'rest_framework.request.Request'>
            print(request.data)     # {}
            print(request.GET)      # <QueryDict: {'name': ['xiong']}>
            return HttpResponse("ok")
  • 当使用点号获取实例属性时
from rest_framework.request import Request
# __getattr__:当使用点号获取实例属性时,如果属性不存在就自动调用__getattr__方法。 
	def __getattr__(self, attr):
        try:
            return getattr(self._request, attr)
        except AttributeError:
            return self.__getattribute__(attr)
  • request.GET
# 如果想使用 request.GET 那么可以使用 query_params方法来调用
	@property
    def query_params(self):
        """
        More semantically correct name for request.GET.
        """
        return self._request.GET
  • 总结
-request.data 是个方法,包装成了属性,前台传过来body体中数据的数据,放在里面
-request.query_params  这个是原来GET中的数据
-request把原来的request包装进去了