zoukankan      html  css  js  c++  java
  • RESTful API


    阅读目录

    一、RESTful API 设计

    二、RESTful API规范

    三、基于Django实现API

    四、Django REST framework


    RESTful API 设计

    RESTful & API定义

    • REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移
    • 对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture),对互联网上的任意东西都视为资源,他认为一个url就是一个资源 比如:http://www.baidu.com/
    • API (接口)就是一个url。接口用途:

      • 提供服务
      • 前后端分离设计,一个写vue,一个写Django,数据的交互通过ajax/axios发送请求

     REST

    一、起源

    REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。

    二、名称

    Fielding将他对互联网软件的架构原则,定名为REST,即Representational State Transfer的缩写。我对这个词组的翻译是"表现层状态转化"。

    如果一个架构符合REST原则,就称它为RESTful架构。

    要理解RESTful架构,最好的方法就是去理解Representational State Transfer这个词组到底是什么意思,它的每一个词代表了什么涵义。如果你把这个名称搞懂了,也就不难体会REST是一种什么样的设计

    三、资源(Resource)

    REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。

    所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。

    所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。

    四、表现层(Representation)

    "资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。

    比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。

    URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。

    五、状态转化(State Transfer)

     访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。

    互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。

    客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。

    六、综述

    综合上面的解释,我们总结一下什么是RESTful架构:

      (1)每一个URI代表一种资源;

      (2)客户端和服务器之间,传递这种资源的某种表现层;

      (3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。

    RESTful API规范

    1、协议

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

    2、域名

    有两种方式

    方式一: 尽量将API部署在专用域名(会存在跨域问题)

    https://api.example.com

    方式二:如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

    https://example.org/api/

    3、版本(Versioning)

    应该将API的版本号放入URL。

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

    另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github采用这种做法。

    4、路径(Endpoint)

    路径又称"终点"(endpoint),表示API的具体网址。

    在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数。

    举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

    https://api.example.com/v1/zoos
    https://api.example.com/v1/animals
    https://api.example.com/v1/employees

    5、HTTP动词

    对于资源的具体操作类型,由HTTP动词表示。

    常用的HTTP动词有下面五个(括号里是对应的SQL命令)。

    GET(SELECT):从服务器取出资源(一项或多项)。即获取数据
    POST(CREATE):在服务器新建一个资源。 即添加数据
    PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。即更新数据
    PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。即更新数据
    DELETE(DELETE):从服务器删除资源  。即删除数据

    还有两个不常用的HTTP动词。

    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:删除某个指定动物园的指定动物

    6、过滤信息(Filtering)

    如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。

    下面是一些常见的参数。

    ?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 的含义是相同的。

    7、状态码(status codes)

    服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

    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 - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

    状态码的完全列表参见这里

    8、错误处理(Error handling)

    如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

    {
        error: "Invalid API key"
    }

    9、返回结果

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

    GET /collection:返回资源对象的列表(数组)
    GET /collection/resource:返回单个资源对象
    POST /collection:返回新生成的资源对象
    PUT /collection/resource:返回完整的资源对象
    PATCH /collection/resource:返回完整的资源对象
    DELETE /collection/resource:返回一个空文档

    10、Hypermedia API  超媒体API

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

    比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

    {"link": {
      "rel":   "collection https://www.example.com/zoos",  #表示这个API与当前网址的关系(collection关系,并给出该collection的网址)
      "href":  "https://api.example.com/zoos",  #API路径
      "title": "List of zoos",  #API的标题
      "type":  "application/vnd.yourformat+json"  #返回类型
    }}

    Hypermedia API的设计被称为HATEOAS。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"
    }

    基于Django实现API

    方式一:FBV模式:

     1 from django.contrib import admin
     2 
     3 from django.conf.urls import url, include
     4 from app01 import views
     5 from app02 import views
     6 
     7 urlpatterns = [
     8     url('admin/', admin.site.urls),
     9     # path('hosts/',views.HostView.as_view()),
    10     url('app02/', include('app02.urls'))
    11 ]
    全局url
    1 from app02 import views
    2 from django.conf.urls import url
    3 urlpatterns = [
    4     url('^users/', views.users),
    5     url('^user/(d+)', views.user),
    6 
    7     url('^users/', views.UsersView.as_view()),
    8     url('^user/', views.UserView.as_view()),
    9 ]
    app02/url
     1 from django.shortcuts import render,HttpResponse
     2 
     3 # Create your views here.
     4 import json
     5 
     6 def users(request):
     7     response = {'code':1000,'data':None}  #code用来表示状态,比如1000代表成功,1001代表
     8     response['data'] = [
     9         {'name':'haiyan','age':22},
    10         {'name':'haidong','age':10},
    11         {'name':'haixiyu','age':11},
    12     ]
    13     return HttpResponse(json.dumps(response))  #返回多条数据
    14 
    15 def user(request,pk):
    16     if request.method =='GET':
    17         return HttpResponse(json.dumps({'name':'haiyan','age':11}))  #返回一条数据
    18     elif request.method =='POST':
    19         return HttpResponse(json.dumps({'code':1111}))  #返回一条数据
    20     elif request.method =='PUT':
    21         pass
    22     elif request.method =='DELETE':
    23         pass
    views

    方式二:CBV模式

    1 from app02 import views
    2 from django.conf.urls import url
    3 urlpatterns = [
    4     url('^users/', views.UsersView.as_view()),
    5     url('^user/', views.UserView.as_view()),
    6 ]
    app02/urls
     1 from django.views import View
     2 class UsersView(View):
     3     def get(self,request):
     4         response = {'code':1000,'data':None}
     5         response['data'] = [
     6             {'name': 'haiyan', 'age': 22},
     7             {'name': 'haidong', 'age': 10},
     8             {'name': 'haixiyu', 'age': 11},
     9         ]
    10         return HttpResponse(json.dumps(response),stutas=200)
    11 
    12 class UserView(View):
    13     def get(self,request,pk):
    14         return HttpResponse(json.dumps({'name':'haiyan','age':11}))  #返回一条数据
    15     def post(self,request,pk):
    16         return HttpResponse(json.dumps({'code':1111}))  #返回一条数据
    17     def put(self,request,pk):
    18         pass
    19     def delete(self,request,pk):
    20         pass
    views

    基于django实现的API许多功能都需要我们自己开发,这时候djangorestframework就给我们提供了方便,直接基于它来返回数据,总之原理都是一样的,就是给一个接口也就是url,让前端的人去请求这个url去获取数据,在页面上显示出来。这样也就达到了前后端分离的效果。下面我们来看看基于Django Rest Framework框架实现

    基于Django Rest Framework框架实现

    1、自定义认证规则  ,详见链接

    复制代码
    class MyAuthtication(BasicAuthentication):
        def authenticate(self, request):
            token = request.query_params.get('token')  #注意是没有GET的,用query_params表示
            if token == 'zxxzzxzc':
                return ('uuuuuu','afsdsgdf') #返回user,auth
            raise APIException('认证错误')
    
    class UserView(APIView):
        authentication_classes = [MyAuthtication,]
        def get(self,request,*args,**kwargs):
            print(request.user)
            print(request.auth)
            return Response('用户列表')
    复制代码

    2、应用:主要是做Token验证  url中as_view里面调用了dispatch方法。

    可以有两种方式

    局部使用

    1 from app01 import views
    2 from django.conf.urls import url
    3 urlpatterns = [
    4     # django rest framework
    5     url('^hosts/', views.HostView.as_view()),
    6     url(r'^auth/', views.AuthView.as_view()),
    7 ]
    urls.py
     1 from django.shortcuts import render,HttpResponse
     2 # Create your views here.
     3 from rest_framework.views import  APIView
     4 from rest_framework.views import Request
     5 from rest_framework.authentication import SessionAuthentication
     6 from rest_framework.authentication import BaseAuthentication, BasicAuthentication
     7 from rest_framework.parsers import JSONParser
     8 from rest_framework.negotiation import  DefaultContentNegotiation
     9 from rest_framework.exceptions import APIException
    10 from app01 import models
    11 from rest_framework.response import Response  #友好的显示返回结果
    12 
    13 
    14 
    15 class AuthView(APIView):
    16     #auth登录页面不需要验证,可设置
    17     authentication_classes = []  #登录页面不需要认证
    18     def get(self,request):
    19         '''
    20         接收用户名和密码
    21         :param request:
    22         :return:
    23         '''
    24         ret = {'code':1000,'msg':None}
    25         user = request.query_params.get('username')
    26 
    27         pwd = request.query_params.get('password')
    28         print(user,pwd)
    29         obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
    30         print(obj)
    31         if not obj :
    32             ret['code'] = 1001
    33             ret['msg'] = '用户名或者密码错误'
    34             return Response(ret)
    35         #创建随机字符串
    36         import time
    37         import hashlib
    38         ctime = time.time()
    39         key = '%s|%s'%(user,ctime)
    40         m = hashlib.md5()
    41         m.update(key.encode('utf-8'))
    42         token = m.hexdigest()
    43 
    44         #保存数据
    45         obj.token = token
    46         obj.save()
    47 
    48         ret['token'] = token
    49         return Response(ret)
    50 
    51 class HostView(APIView):
    52     def dispatch(self, request, *args, **kwargs):
    53         return super().dispatch(request, *args, **kwargs)
    54 
    55     # authentication_classes = [MyAuthtication]
    56 
    57     def get(self,request,*args,**kwargs):
    58         print(request.user,'dddddddddddffffff')
    59         print(request.auth,'dddddddddd')
    60         #原来的request,django.core.handlers.wsgi.WSGIRequest
    61         #现在的request ,rest_framework.request.Request
    62         # print(request)
    63         authentication_classes = [SessionAuthentication,BaseAuthentication]
    64         # print(self.authentication_classes)  # [<class 'rest_framework.authentication.SessionAuthentication'>,
    65                                             #  <class 'rest_framework.authentication.BasicAuthentication'>]
    66         return HttpResponse('GET请求的响应内容')
    67 
    68     def post(self,request,*args,**kwargs):
    69         pass
    70         # try:
    71         #     try :
    72         #         current_page = request.POST.get("page")
    73         #
    74         #         current_page = int(current_page)
    75         #         int("asd")
    76         #     except ValueError as e:
    77         #         print(e)
    78         #         raise #如果有raise说明自己处理不了了,就交给下面的一个去捕捉了
    79         # except Exception as e:
    80         #     print("OK")
    81 
    82 
    83         return  HttpResponse('post请求的响应内容')
    84 
    85     def put(self, request, *args, **kwargs):
    86         return HttpResponse('put请求的响应内容')
    Views.py

    全局使用

    1 #注册认证类
    2 REST_FRAMEWORK = {
    3     'UNAUTHENTICATED_USER': None,
    4     'UNAUTHENTICATED_TOKEN': None,  #将匿名用户设置为None
    5     "DEFAULT_AUTHENTICATION_CLASSES": [
    6         "app01.utils.MyAuthentication",
    7     ],
    8 }
    settings
     1 from  rest_framework.authentication import BaseAuthentication
     2 from rest_framework.exceptions import APIException
     3 from app02 import models
     4 
     5 
     6 class MyAuthentication(BaseAuthentication):
     7     def authenticate(self, request):
     8         token=request.query_params.get('token')
     9         print(token)
    10         obj=models.UserInfo.objects.filter(token=token).first()
    11         print(obj)
    12         if obj:
    13             return (obj.username,obj)
    14         raise  APIException('没有通过验证')
    15 
    16 全局验证
    全局验证

    注:rest_framework是一个app需要settings里面设置。

    本文参考:http://www.ruanyifeng.com/blog/2014/05/restful_api.html 

  • 相关阅读:
    Leetcode 15 3Sum
    Leetcode 383 Ransom Note
    用i个点组成高度为不超过j的二叉树的数量。
    配对问题 小于10 1.3.5
    字符矩阵的旋转 镜面对称 1.2.2
    字符串统计 连续的某个字符的数量 1.1.4
    USACO twofive 没理解
    1002 All Roads Lead to Rome
    USACO 5.5.1 求矩形并的周长
    USACO 5.5.2 字符串的最小表示法
  • 原文地址:https://www.cnblogs.com/zhangliang91/p/10924280.html
Copyright © 2011-2022 走看看