本文为 Udacity FSND 第三章学习笔记。
RESTful API
REST 代表 Representational State Transfer(表象层状态转变),它是Roy Fielding在2000年提出的一种API设计风格。
五大特性
- Uniform Interface:REST架构对于访问和处理数据与资源有标准化的方法,包括URL和自描述信息。每个URL代表一种资源。自描述信息则描述了如何处理该类型的资源(例如JSON与XML)
- Stateless:Http请求本身就是无状态的。每次客户端请求都是独立的。便于debug。
- Client-Server
- Cacheable & Layered System:缓存和分层可提高网络效率
这个文推荐阅读。
设计URL的原则
- Should be intuitive 傻瓜一看都能猜出大概
- Organize by resource (nouns in the path, not verbs)
- Keep a consistent scheme (plural nouns for collections, parameters for a specific item) 使用复数名词描述集合,使用参数确定具体项目
- BAD: example.com/entree_five
- GOOD: example.com/entrees/5
- Don't make them too complex or lengthy
- avoid "collection/item/collection"
- 不要设计 /users/5/tasks/3/name 这样的url,想办法拆分
HTTP
RESTful API 使用 url 描述资源,使用 HTTP 动词描述对资源进行的操作。因此了解 HTTP 是必不可少的。RESTful API系列之HTTP基础 - 九净的文章 - 知乎 这个文章可以一看。
HTTP Features
- Connectionless: 一次request,一次response,然后关闭连接。下一次request和response需要再建立一次新的TCP连接。但后期人们引入了 keep_alive 字段允许保持连接。
- Stateless: 无状态性。连续的 requests 之间没有相互作用关系。但这不代表HTTP协议没有session。Session 是对利用headers和cookies使得不同的 HTTP 能够共享上下文。达到同一个网站内跳转另一个页面不需要重新登录的目的。
- Media Independent: 只要 client 和 server 都知道如何处理,HTTP可以传输任何格式的数据。
Request元素
Http使用client-server架构。客户端通过url向服务器请求资源发出的消息叫Request,服务器返回的消息叫Response。Request和Response的格式有一定不同。
- Method: GET,POST,DELETE 等等
- Path: 所请求资源的URL。
- Version: 使用的HTTP协议版本
- Headers(optional)
- Body(optional)
GET http://www.example.com/tasks?term=homework HTTP/2.0
Accept-Language: en
Request动词方法的含义
REST 使用 request 方法来描述一个请求想要完成的动作。常用的有这五种:
- GET: 单纯获取资源信息。集合/单个项目均可作用。看语义。
- POST: 创建一个新资源。需要施加在一个集合上,而非尚未存在的项目。
- PUT: 替代性更新原资源。集合/单个项目均可作用。看语义。
- PATCH: 部分更新原资源。集合/单个项目均可作用。看语义。
- DELETE: 删除资源。集合/单个项目均可作用。看语义。
Response元素
Http使用client-server架构。客户端通过url向服务器请求资源发出的消息叫Request,服务器返回的消息叫Response。Request和Response的格式有一定不同。
- Status Code & Status Message:状态码是Response独有的部分。Response 没有 Path 和 Method 元素。
- Version: 使用的HTTP协议版本
- Headers(optional)
- Body(optional)
POST /stores/1/pastries HTTP/2.0
Host: http://www.coffee-example.com
Accept-Language: en
Body: { 'name': 'croissant', 'price':3.99 }
Response状态码
- 1xx Informational;
- 100 continue:用于回复 OPTIONS request 可以继续发送信息。
- 2xx Success;
- 200 OK: 正常响应
- 3xx Redirection;
- 4xx Client Error;
- 5xx Server Error
API开发调试工具
Chrome Dev Tools (network tab)
显示请求的各个资源的名称,返回的状态码,以及消耗时间。 点击一个特定请求可以查看其 request header, response header。部分数据类型,如图像,可以预览。
CURL
Curl 是一个能够根据 URL 发送 Http 请求的命令行工具。多用于API测试。
同源策略和跨域
本节内容删改自——
作者:laixiangran 链接:juejin.cn/post/684490… 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
—— CORS并非唯一的跨域方法,阅读原文查看更多方法。
- 同源策略 (same-origin policy)是一种约定,它是浏览器最核心也最基本的安全功能。所谓同源是指:域名、协议、端口相同。
- 另外,同源策略又分为 DOM 同源策略(避免恶意 iframe)和 XMLHttpRequest 同源策略(避免恶意Ajax)。
CORS
CORS(Cross-origin resource sharing,跨域资源共享)是一个 W3C 标准,定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。CORS 需要浏览器和服务器同时支持。整个CORS通信过程由浏览器自动完成。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。
简单请求
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
如果一个请求类型为 HEAD/GET/POST 之一且 header中的信息较为基础,这就是一个 CORS 简单请求。
- 在请求中需要附加一个额外的 Origin 头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。例如:Origin: www.laixiangran.cn。如果需要包含 cookie 信息,ajax 请求需要设置 xhr 的属性 withCredentials 为 true。
- 如果服务器认为这个请求可以接受,就在 Access-Control-Allow-Origin 头部中回发相同的源信息(如果是公共资源,可以回发 * )。例如:Access-Control-Allow-Origin:www.laixiangran.cn
- 如果服务器认为这个请求不能接受,则在返回的响应中没有这个头部或者有这个头部但源信息不匹配,驳回请求。
非简单请求
- 浏览器在发送真正的请求之前,会先发送一个 Preflight 请求给服务器,这种请求使用 OPTIONS 方法,发送下列头部:
- Origin:与简单的请求相同。
- Access-Control-Request-Method: 请求自身使用的Http方法。
- Access-Control-Request-Headers: (可选)自定义的头部信息,多个头部以逗号分隔。
服务器对 Preflight 的回复 header | 解释 |
---|---|
Access-Control-Allow-Origin | 哪些域名可以访问该资源,星号代表全部 |
Access-Control-Allow-Credentials | 若为 true 则允许使用cookie |
Access-Control-Allow-Methods | 允许的Http请求方法列表 |
Access-Control-Allow-Headers | 服务器允许的Http请求Header类型和取值。可以使用自定义Header。 |
Flask-CORS
CORS 需要浏览器和服务器同时支持。浏览器可以自动完成这个过程,因此关键就在于在服务器端支持CORS。Flask使用flask-cors插件方便地支持CORS.
CORS option
- CORS option包括 origins,methods,supports_credentials等。
- CORS()的参数,CORS(resources)中每个resource的参数,@cross_origin()的参数,都是 CORS option格式的。
- Flask-CORS 可以以不同粒度设置规则。对于每一个具体的url,所遵循的CORS规则可能各不相同。优先级:
- Resource level settings (e.g when passed as a dictionary)
- Keyword argument settings
- App level configuration settings (e.g. CORS_*)
- Default settings
示例
# Import Dependencies
from flask import Flask
from flask_cors import CORS
def create_app(test_config=None):
# 初始化
app = Flask(__name__)
# CORS(app) 最简单的形式,一般会增加其他参数
cors = CORS(app, resources={
r"/api/*": {"origins": "*"}
}
)
# resources使用正则表达式描述资源和描述访问这些资源的规则。这里表明所有/api/目录下的资源,任何origin都可以访问。其他资源不能访问。默认值为:任何origin可访问任何资源。
@app.after_request # 对于每一个Request都调用该函数修改Response
def after_request(response):
# 在Response中加入这些CORS信息,则preflight可以收到预期回应。
# 其实这些也可以在CORS()的参数中设置,直接在@app.after_request里写,蛮暴力的。
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization,true')
response.headers.add('Access-Control-Allow-Methods', 'GET,PATCH,POST,DELETE,OPTIONS')
return response
@app.route('/messages')
@cross_origin() # 注册该endpoint使用CORS。即虽然它不在 /api/ 下,但是仍可跨域访问。且默认允许任何站点访问。
def get_messages():
return 'GETTING MESSAGES'
# 一个普通endpoint函数
@app.route('/books/<int:book_id>', method=['GET'])
def retrieve_book():
return 'Book %d' % book_id
Flask Error Handling
默认情况下,当客户端调用API出现错误时,服务器会返回默认的展示诸如404,500错误的HTML。相比杂乱无章的HTML, JSON是更易于客户端理解和debug的格式。
@app.errorhandler装饰器可以覆盖默认行为,自定义对各类错误的响应。
@app.errorhandler(404) # 传入错误码作为参数
def not_found(error):
return jsonify({ # 第一个返回值是想返回的数据,success & error & message 并非唯一固定的格式
"success": False,
"error": 404,
"message": "Not found"
}), 404 # 第二个返回值是错误码
@app.errorhandler(werkzeug.exceptions.BadRequest) # 或者传入错误类。BadRequest.code == 400
def handle_bad_request(e):
return 'bad request!', 400
# or, without the decorator
app.register_error_handler(400, handle_bad_request)
对于非标准的HTTP错误码,需要自定义HTTPException的子类。详见:flask.palletsprojects.com/en/1.1.x/er…
class InsufficientStorage(werkzeug.exceptions.HTTPException):
code = 507
description = 'Not enough storage space.'
app.register_error_handler(InsufficientStorage, handle_507)
raise InsufficientStorage()
UnitTest
UnitTest是python标准库。所有的测试用例都是unittest.TestCase的子类。 其中方法包括 setUp、tearDown 和任意个测试用例。unittest.main()会一次性执行所有测试用例。
import unittest
class AppNameTestCase(unittest.TestCase):
"""This class represents the ___ test case"""
def setUp(self):
"""Executed before each test. Define test variables and initialize app."""
self.app = create_app()
self.client = self.app.test_client
self.database_name = "db_for_test_only"
self.database_path = "postgres://{}:{}@{}/{}".format('student', 'student','localhost:5432', self.database_name)
setup_db(self.app, self.database_path)
def tearDown(self):
"""Executed after reach test"""
pass
def test_given_behavior(self): # 单元测试需要各自独立,互不影响
"""Test _____________ """
res = self.client().get('/')
self.assertEqual(res.status_code, 200)
# Make the tests conveniently executable
if __name__ == "__main__":
unittest.main()
先写测试用例,再编写代码,就是 test-driven 的开发方法。其优点是一能促进明确所要实现的功能,二是便于量化也有成就感,三是对程序的可测性进行约束,提升代码质量。
文档编写
API文档
一个好的API文档包括:
- Introduction
- Getting Started 使用该API的基本方法
- Base URL
- API Keys /Authentication (if applicable)
- Errors (Response codes, Messages, Types)
- Resource endpoint library
- 根据资源结构化地组织,便于查找
- 要包括所有endpoint (http方法+url),每个endpoint的输入参数和返回值,返回值包括状态码和返回的数据
- 给出示例request及其response
一个好的范例是Strip的API文档。
项目文档
一般就是项目的ReadMe。它是为了方便其他想为这个项目做贡献的人而出现的。
- 项目介绍(目标动机,运行截图等)
- 使用的 code style(如PEP8,在后续开发中须保持统一)
- Getting Started
- Prerequisites 及其安装
- 如何在本地运行该项目
- 测试用例,确保用户是否正确安装并运行该项目
- API文档链接
- Deployment (if applicable)
- Authors
- Acknowledgements