Web应用程序的本质:
User 通过 浏览器 访问 互联网上指定的 网页文件 展示到浏览器上。
技术角度,以下3个步骤:
- 浏览器,将要请求的内容按照HTTP协议发送服务端
- 服务端,根据请求内容找到指定的HTML页面
- 浏览器,解析请求到的HTML内容展示出来
WEB开发——Python WSGI协议详解
1. Web DEV
- 静态开发
- 动态开发
动态开发
- CGI
- WSGI
CGI 流程
WSGI 流程
2. What’s WSGI
WSGI全称是Web Server Gateway Interface,其主要作用是Web服务器与Python Web应用程序或框架之间的建议标准接口,以促进跨各种Web服务器的Web应用程序可移植性。
WSGI 协议 分成三个组件 Application、Server、Middleware 和 协议中传输的内容。
- Application:Django,Flask等
- Server:常用的有uWSGI,gunicorn等
- Middleware: Flask等框架中的装饰器
2.1 Application
应用程序,是一个可重复调用的可调用对象,在Python中可以是一个函数,也可以是一个类,如果是类的话要实现__call__方法,要求这个可调用对象接收2个参数,返回一个内容结果。
2.2 Server
Web服务器,主要是实现相应的信息转换,将网络请求中的信息,按照HTTP协议将内容拿出,同时按照WSGI协议组装成新的数据,同时将提供的start_response传递给Application。最后接收Application返回的内容,按照WSGI协议解析出。最终按照HTTP协议组织好内容返回就完成了一次请求。
2.3 Middleware
Middleware 中间件,可以理解为对应用程序的一组装饰器。
在 Application 端看来,它可以提供一个类start_response函数,可以像start_response函数一样接收HTTP STATU和Headers;和environ。
在 Server 看来,他可以接收2个参数,并且可以返回一个类 Application对象。
2.4 总结
WSGI 对于 application 对象有如下三点要求
- 必须是一个可调用的对象
- 接收两个必选参数 environ、start_response。
- 返回值必须是可迭代对象,用来表示 http body。
3. Python Web 应用是什么?
一个 Python Web 应用包含两个部分:
- 应用的开发:实现 Web 应用的逻辑,读数据库,拼装页面,用户登录
- 应用的部署:将 Web 应用跑起来,多线程,多进程,异步,监听端口,所有服务器该做的事情
我们希望用各种不同的技术来开发应用,用各种不同的服务器程序来跑应用.
这就要求 开发 和 部署 遵循统一的 通信接口,这接口叫 Web Server Gateway Interface,简称“WSGI”。
WSGI 接口 就是一个满足特定要求的函数
1 2 3 4 5 6 7
| def application(environ, start_response): “”“ environ:包含所有 HTTP 请求信息的 dict start_response:发送 HTTP 响应的函数 ”“” start_response('200 OK', [('Content-Type', 'text/html')]) return '<h1>Hello, web!</h1>'
|
WSGI 完整说明:PEP 333 : Python Web Server Gateway Interface v1.0.1
WSGI 简单说明: 廖雪峰的官方网站
Py Web 应用概览:The Hitchhiker’s Guide To Python
4. Flask Web 应用开发
基本形态:URL 路由,获取请求,构造响应
Flask 作为一个 Web 框架,它遵循 WSGI 接口,并且在其之上整合了 Werkzeug 的 URL 路由以及 WSGI 工具库,SQLAlchemy 的数据库访问,jinja2 的模版渲染等功能。
app 对象实现了 WSGI 接口
Flask,从简单开始
5. Flask-RESTPlus
Flask-RESTPlus 是对Flask的扩展,它增加了对快速开发REST API的支持.
Flask-RESTPlus 鼓励以最小的设置来实现功能的开发.
Flask-RESTPlus 既包含 Flask-Restful包 的功能,又包括 swagger 文档化功能(其实是封装了swagger).
5.1 quickstart hello
1 2 3 4 5 6 7 8 9
| from flask import Flask from flask_restplus import Api
app = Flask(__name__)
api = Api(app)
|
最简单的打开 Flask-RESTPlus, API 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
from flask import Flask from flask_restplus import Resource, Api
app = Flask(__name__) api = Api(app)
@api.route('/hello') class HelloWorld(Resource): def get(self): return {'hello': 'world'}
if __name__ == '__main__': app.run(debug=True)
|
run ➜ python simple_api.py
, then:
http://localhost:5000/hello:
5.2 Resourceful Routing
通过在资源上定义方法来很容易地访问多个 HTTP 方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| from flask import Flask, request from flask_restplus import Resource, Api
app = Flask(__name__) api = Api(app)
todos = {}
@api.route('/<string:todo_id>') class TodoSimple(Resource): def get(self, todo_id): return {todo_id: todos[todo_id]}
def put(self, todo_id): todos[todo_id] = request.form['data'] return {todo_id: todos[todo_id]}
if __name__ == '__main__': app.run(debug=True)
|
5.3 Endpoints
大多数情况下,某个资源都会有多个URL。
我们可以用 route()装饰器 中传入多个URL,这样每个URL都将会路由到该资源上.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| from flask import Flask from flask_restplus import fields, Api, Resource
app = Flask(__name__) api = Api(app)
model = api.model('Model', { 'task': fields.String, 'uri': fields.Url('todo_ep',absolute=True) })
class TodoDao(object): def __init__(self, todo_id, task): self.todo_id = todo_id self.task = task
self.status = 'active'
@api.route('/todo',endpoint='todo_ep') class Todo(Resource): @api.marshal_with(model) def get(self, **kwargs): return TodoDao(todo_id='my_todo', task='Remember the milk')
if __name__ == '__main__': app.run(debug=True)
|
marshal_with()装饰器就是用来对结果按照model的结构进行转换的.
flask-restplus quickstart
flask-restplus example.html
5.5 Order Preservation
- Api全局保留:api = Api(ordered = True)
- Namespace全局保留:ns = Namespace(ordered=True)
- marshal()局部保留:return marshal(data, fields, ordered=True)
5.6 Full example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| from flask import Flask from flask_restplus import Api, Resource, fields from werkzeug.contrib.fixers import ProxyFix
app = Flask(__name__) app.wsgi_app = ProxyFix(app.wsgi_app)
api = Api(app, version='1.0', title='TodoMVC API', description='A simple TodoMVC API', )
ns = api.namespace('todos', description='TODO operations')
todo = api.model('Todo', { 'id': fields.Integer(readOnly=True, description='The task unique identifier'), 'task': fields.String(required=True, description='The task details') })
class TodoDAO(object): def __init__(self): self.counter = 0 self.todos = []
def get(self, id): for todo in self.todos: if todo['id'] == id: return todo api.abort(404, "Todo {} doesn't exist".format(id))
def create(self, data): todo = data todo['id'] = self.counter = self.counter + 1 self.todos.append(todo) return todo
def update(self, id, data): todo = self.get(id) todo.update(data) return todo
def delete(self, id): todo = self.get(id) self.todos.remove(todo)
DAO = TodoDAO() DAO.create({'task': 'Build an API'}) DAO.create({'task': '?????'}) DAO.create({'task': 'profit!'})
@ns.route('/') class TodoList(Resource): '''获取所有todos元素,并允许通过POST来添加新的task''' @ns.doc('list_todos') @ns.marshal_list_with(todo) def get(self): '''返回所有task''' return DAO.todos
@ns.doc('create_todo') @ns.expect(todo) @ns.marshal_with(todo, code=201) def post(self): '''创建一个新的task''' return DAO.create(api.payload), 201
@ns.route('/<int:id>') @ns.response(404, 'Todo not found') @ns.param('id', 'The task identifier') class Todo(Resource): '''获取单个todo项,并允许删除操作''' @ns.doc('get_todo') @ns.marshal_with(todo) def get(self, id): '''获取id指定的todo项''' return DAO.get(id)
@ns.doc('delete_todo') @ns.response(204, 'Todo deleted') def delete(self, id): '''根据id删除对应的task''' DAO.delete(id) return '', 204
@ns.expect(todo) @ns.marshal_with(todo) def put(self, id): '''更新id指定的task''' return DAO.update(id, api.payload)
if __name__ == '__main__': app.run(debug=True)
|
get all
1
| curl "http://localhost:5000/todos/"
|
get id
1
| curl -X GET "http://localhost:5000/todos/1" -H "accept: application/json"
|
delete:
1
| curl -X DELETE "http://localhost:5000/todos/2" -H "accept: application/json"
|
Reference
Checking if Disqus is accessible...