0%

Python-WEB开发

  • fastapi:相比于 flask,支持异步的 web 框架。
  • Flask:轻量级 python web 应用框架,小到称为“微框架”。组件丰富,按需选择,我选择Flask

概述

  • Flask 的模板是 MVC 架构,Model-View-Controller,中文名“模型 - 视图 - 控制器”,将 Python 与 HTML 代码一定程度地分离了
  • MVC:为了解决直接用脚本语言嵌入 HTML 导致的可维护性差的问题,Web 应用也引入了 Model-View-Controller 的模式,来简化 Web 开发。
  • 异步开发、新的 MVVM 前端技术层出不穷。
  • 一组 python 包,便于开发者专注于业务逻辑,而不必深入网络应用底层协议、线程、进程等方面,提高开发效率与程序质量

说明

wsgi

wsgi:Web Server Gateway Interface:web 服务器网关接口。Web 应用程序的入口,处理 web 请求、并响应。python 语言中定义的 web 服务器与 web 应用程序或框架之间的通用接口标准。
CS(Client/Server):客户端/服务器架构
BS 架构:Browser/Server 模式的架构:客户端只需要浏览器,应用程序的逻辑和数据都存储在服务器端。从浏览器请求服务器,获取 web 页面,在浏览器中展示给用户

CGI:Common Gateway Interface,用 C/C++ 编写。
ASGI:Asynchronous Server Gateway Interface:异步通信服务规范
WSGI:Web Server Gateway Interface:同步通信服务规范

  • web 服务器网关接口。python 语言中定义的 web 服务器与 web 应用程序或框架之间的通用接口标准。
  • Python 应用程序或框架和 Web 服务器之间的一种接口,WSGI 的其中一个目的就是让用户可以用统一的语言 (Python) 编写前后端。

REST:Representational State Transfer:表现层状态转移:一种软件架构风格

  • 设计风格,而不是标准。主要用于客户端和服务端的 API 交互,它的优势在于更简洁、清晰、可读性强。

选择

  • FastAPI:请求耗时较久时,请求所需时间大于处理时间,它会多开线程,然后疯狂吃资源,引发内存泄露

FastAPI

fastapi:相比于 flask,支持异步的 web 框架。

  • 优点:
    • 快速:
      • 异步:高性能,功能强大,说是可与 NodeJS 和 Go 相媲美
      • 直观:易于使用,最小化代码重复,快速编码
    • 自动:
      • 自动数据验证:使用 pydantic 模式和 python 类型提示的组合来允许数据验证
      • 自动文档
  • 特性:
    • API 标准:OpenAPI(以前称为 Swagger)
      • 路径操作、请求参数、请求体、安全体等声明
      • HTTP 基本认证
      • OAuth2
      • API 密钥:请求头、查询参数、Cookies 等
    • 数据模型文档:JSON Schema
    • Starlette 特性:
      • WebSockt,GraphQL,CORS、GZip
      • Session、Cookie
    • 类型提示
      • 编辑器支持
      • 类型检查
      • 定义参数要求:声明对请求路径参数、查询参数、请求头、请求体、依赖等的要求。
      • 转换数据:将来自请求的数据转换为需要的类型。
      • 校验数据:对于每一个请求:当数据校验失败时自动生成错误信息返回给客户端。
    • 使用 OpenAPI 记录 API:
      • 然后用于自动生成交互式文档的用户界面
    • 不用管 SQLAlchemy 系列

基本使用

1
2
3
# 安装
pip install fastapi
pip install uvicorn
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
# demo
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
return {"Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}

if __name__ == "__main__":
uvicorn.run("example:app", host="127.0.0.1", port=5000, log_level="info")



from fastapi import FastAPI

app = FastAPI(docs_url="/docs", redoc_url=None)
# app.add_middleware(
# CORSMiddleware,
# allow_origins=["*"], # 解决跨域问题
# allow_credentials=True,
# allow_methods=["*"],
# allow_headers=["*"],
# )


if __name__ == "__main__":
# uvicorn web_api:app --host {server_ip} --port 5000
pass

1
2
3
4
5
6
7
# 运行
uvicorn main:app --reload # main:app 所在文件名,app:FastAPI 实例,reload:debug 模式,可自动重启
uvicorn app:app --port 5000
uvicorn app:app --workers 2


uvicorn web_api:app --host 10.50.63.63 --port 5000 --reload --debug

相关工具

参考文档

官方文档

交互式文档

1
2
3
4
# 交互式 API 文档,Swagger UI,可交互式操作:调用、测试 API
http://127.0.0.1:8000/docs
# API 文档
http://127.0.0.1:8000/redoc
1
2
3
4
5
6
7
8
9
10
11
12
13
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class User(BaseModel):
name: str
age: int

@app.post('/users')
def save_user(user: User):
return {'name': user.name,
'age': user.age}
1
2
3
4
5
6
7
8
9
10
# 键值对
{
"name": "Isaac",
"age": 60
}
from pydantic import BaseModel

class User(BaseModel):
name: str
age: int
1
2
3
4
5
6
7
8
9
# 事物的集合
{
"series": ["GOT", "Dark", "Mr. Robot"]
}
from pydantic import BaseModel
from typing import List

class Metadata(BaseModel):
series: List[str]
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
# 嵌套对象
{
"users": [
{
"name": "xyz",
"age": 25
},
{
"name": "abc",
"age": 30
}
],
"group": "Group A"
}
from pydantic import BaseModel
from typing import List

class User(BaseModel):
name: str
age: int

class UserGroup(BaseModel):
users: List[User]
group: str

使用指南

参数

url 路径参数

1
2
3
@app.get('/user/{user_id}')
async def get_user_msg(user_id:str):
return {'id':user_id}

param 查询参数

  • 可选参数:设置查询参数的默认值
1
2
3
4
https://www.baidu.com/search?ie=UTF-8&wd=fastapi 参数汇总
@app.get("/search/")
async def getEchoApi(text:str): # fastapi 会自动识别它不是 url 参数,而是 param
return {"echo":text}

请求体参数

  • post、PUT 等请求时
  • 以 JSON 读取请求的正文
  • 验证数据,不对时会有清晰的错误返回
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
from fastapi import FastAPI
from pydantic import BaseModel #fastapi 的一个依赖,需要从 pydantic 中引入
app=FastAPI()
class EchoArgs(BaseModel): #继承 BaseModel
text:str #定义一个字符串型参数
app.post("/echo/") #get 不支持请求体,更换请求方法
async def postEchoApi(args:EchoArgs): #设置刚才定义的参数
return {"echo":item.text} #原样返回


from fastapi import FastAPI
import uvicorn
from pydantic import BaseModel

app = FastAPI()

class CommonItem(BaseModel):
token: str
message_id: str
to_id: str
from_info: str
strategy: int or str = 0 # 默认为0,可不传该参数,但是不能传空字符串
type: str or int # str 和 int 类型都支持
from_id: str
to_info: str
content: str = None

@app.post("/test")
async def tests(item: CommonItem):
return item

表单参数

1
2
3
4
5
from fastapi import FastAPI,Form    #引入 Form 用于定义表单参数
app=FastAPI()
app.post("/echo/")
async def postEchoApi(text:str=Form(None)): #通过 Form 设置参数
return {"echo":text} #原样返回

一般用于用户的标识,用户习惯的记录等.各大网站都在使用这种方式,后来的 session 和 token 也要用到 cookie 技术.cookie 本质是一种在客户端保存的数据,每次请求都会携带,你也可以在响应中进行设置.

1
2
3
4
5
from fastapi import FastAPI,Cookie
app=FastAPI()
app.post("/echo/")
async def postEchoApi(text:str=Cookie(None)):
return {"echo":text} #原样返回

file 参数

  • 可用 bytes 或 UploadFile,推荐使用 UploadFile
1
2
3
4
5
6
7
8
from fastapi import FastAPI,File,UploadFile    #引入文件相关操作
app=FastAPI()
app.post("/upload/2")
async def postUploadFile1Api(file:bytes=File(None)):
... #文件相关操作
app.post("/upload/1")
async def postUploadFile2Api(file:UploadFile=File(None)):
... #文件相关操作
  • url 参数:
  • param 参数:
  • 请求主体参数
  • cookie 参数
  • file 参数:

HTTP 方法

1
2
3
4
@app.get('/')
@app.put('/')
@app.patch('/')
@app.delete('/')

变量

  • 使用 Python 的类型提示来指定数据类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@app.get('/users/{user_id}')
def get_user_details(user_id: int):
return {'user_id': user_id}

@app.get('/search')
def search(q: str):
return {'query': q}

from typing import Dict

@app.post('/lowercase')
def lower_case(json_data: Dict):
text = json_data.get('text')
return {'text': text.lower()}

上传文件

1
2
3
4
5
6
7
8
# app.py
from fastapi import FastAPI, UploadFile, File

app = FastAPI()

@app.post('/upload')
def upload_file(file: UploadFile = File(...)):
return {'name': file.filename}

表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# app.py
from fastapi import FastAPI, Form
app = FastAPI()

@app.post('/submit')
def echo(city: str = Form(...)):
return {'city': city}

from typing import Optional
# 字段可选
@app.post('/submit')
def echo(city: Optional[str] = Form(None)):
return {'city': city}

# 默认值
@app.post('/submit')
def echo(city: Optional[str] = Form('Paris')):
return {'city': city}
1
2
3
4
5
6
7
# app.py
from fastapi import FastAPI, Cookie
app = FastAPI()

@app.get('/profile')
def profile(name = Cookie(None)):
return {'name': name}

蓝图

1
2
3
4
5
6
7
8
# app.py
from fastapi import FastAPI
from routers import user
from routers import index

app = FastAPI()
app.include_router(index.router)
app.include_router(user1.router,prefix="/user")
1
2
3
4
5
6
7
# routers/user.py
from fastapi import APIRouter
router = APIRouter()

@router.get('/users')
def list_users():
return {'users': ['a', 'b', 'c']}

生产环境

静态文档 -aiofiles

静态文档:提前编写到文档里,浏览器每次访问时,里面的内容不变。
动态文档:通过服务器上运行的程序动态产生的,每次访问时更新。

1
pip install aiofiles
1
2
3
4
5
6
7
8
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

app = FastAPI()

app.mount("/static", # 与网址相关,如 127.0.0.1:8000/static/aa.txt
StaticFiles(directory="/mnt/data/static"), # 相对路径或绝对路径
name="static") # 可省略
1
2
3
4
5
6
7
8
9
# with open(archieve_path,'rb') as f:
# file_data=f.read()

# fio = BytesIO(file_data)
archieve_dict = get_archieve_dict()
if archieve_path in archieve_path_set:
return FileResponse(archieve_path)
else:
return '不是日志文件路径'

遇到的问题

Flask

Flask:轻量级 python web 应用框架,小到称为“微框架”。组件丰富,按需选择,我选择Flask

Django:全家桶,包含大量组件,ORM、Session、Form、Admin、分页、中间件、信号、缓存、ContenType…
tornado:异步非阻塞、原生支持 websocket 协议。专注性能(大并发)上 Go、Java,专注效率上 Flask、Django。故,Tornado,Out。
webpy:
bottle:比较简单
cherrypy:
sanic:代码质量不好
pyramid:与 Django 同为面向大型应用的 web 框架,但是更加灵活,且性能优于 Flask。

概述

  • 基于 Python,依赖 jinja2 模权和 Werkzeug WSGI 服务
    • jinja2:模板引擎。
    • Werkzeug:Socket 服务端,提供路由、调试和 web 服务器网关接口
      • 路由:使用一个简单但有力的模式语法,映射 URLs 到代码。
      • WSGI(Web Server Gateway Interface)web 服务器网关接口、HTTP 开发服务器。python 语言中定义的 web 服务器与 web 应用程序或框架之间的通用接口标准。
  • 扩展一大堆,这个是优点也是缺点。
  • 语法简单,易用,部署方便。
  • 轻量、扩展丰富,写法自由,Pythonic
  • 简单、易上手、第三方开源组件丰富,

路由:路径映射

模板

学习资料

基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from flask import
# Flask 实例
# 客户端请求--wsgi 协议--web 服务器--Flask 对象--服务端
app=Flask(__name__)

# 路由 route,将 Flask 对象的 url 与视图函数对应
@app.route("/")
def index(): # 视图函数:处理业务逻辑,返回响应内容
return "<h1>helloworld !</h1>"

# 路由默认只支持 GET, 如果需要增加, 需要自行指定
@app.route("/<str:username") # 可变 url,尖括号内动态,定义路由的参数
def user(username):
return 'hello{}'.format(username)

@app.route('/request')
def hello_world():
user_agent =request.headers.get ('User-Agent')
return '<h2>your browser is {}</h2>'.format(user_agent) #激活请求上下文就可以使用 request 和 session 变量了
if __name__=='__main__': #服务器启动后,会进入轮询,等待并处理请求。
app.run(debug=True) #开启调试模式,不适合用在生产中

Flask 对象

1
2
3
4
5
6
7
8
9
10
def __init__(self,
import_name, # 即 __name__
static_path=None, #
static_url_path=None, # 静态文件的地址前缀
static_folder='static', # 静态文件所在文件的名字
template_folder='templates', # 模板所在文件夹名字
instance_path=None,
instance_relative_config=False,
root_path=None): # 当前执行文件,所在目录地址
# return render_template()会将 root_path 与 template_folder 拼接

变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# aa.html
{{ }} 双大括号占位符,表示里面的数据需从渲染模板时的数据传入
<ht> hello,{{ name }}</ht> 变量,
{{ dict['dd'],list[3],str.lower() }}
- 可用过滤器,但不用,用 python 的方法
- {% ... %} Jinja 语句,例如判断、循环语句
{{ ... }} 变量,会显示在浏览器中
- {# ... #} 注释,不会输出到浏览器中
{{ "helloworld"|reverse|upper}} #链式调用
{{"<h1>" | safe }} #safe 禁用转义
upper
title
reverse
format
striptage:渲染前把值中所有的 html 标签都删掉
truncate:字符串截断
{{[1,2,23] | first}} 列表取第一个元素
last
length
sum
sort

控制结构

1
2
3
4
5
6
7
8
9
10
# if 结构
{% if name %}
Hello{{name}}
{% else %}
Hello world!
{% endif %} #关闭控制语句
# for 结构
{% for i in names %}
<li> {{ i }}</li>
{% endfor %}

路由

路由:将 url 与视图函数封装成一个 Rule 对象,添加到 Flask 的 url_map 字段中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@app.route('/index.html',methods=['GET','POST'],endpoint='index') # endpoint:别名
def index():
return 'Hello WOrld'

app.route(
rule, # url
view_func, # 视图函数名称
defaults=None, # url 无参数,而函数需要参数时 defaults={'k':'v'}
enpoint=None, # 名称,用地反向生成 URL,即 url_for('名称')
methods=None, # 请求方法,如['GET','POST']
strict_slashes=None, #对 url 后的/符号是否严格要求
redirect_to=None, # 重定向
subdomain=None, 子域名访问

)

route

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@app.route('/user/<username>')   #常用的   不加参数的时候默认是字符串形式的
@app.route('/post/<int:post_id>') #常用的 #指定 int,说明是整型的
@app.route('/post/<float:post_id>')
@app.route('/post/<path:path>')
@app.route('/login', methods=['GET', 'POST'])

DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}

请求

  • request
    • request.from:POST 请求的数据
    • request.args:GET 请示的数据,可通过 .to_dict() 转换为字典
    • request.querystring:bytes 形式的 GET 请求
  • response
    • return render_template()
    • return redirect()
    • return “
    • v=make_response(返回值),.set_cookie 绑定 cookie
  • session
    • 存在浏览器,且为加密的
    • 依赖于:secret_key

Jinja2

Jinja2:一个广泛应用的模板引擎
模版引擎:渲染模板:用真实值替换占位变量,返回最终的响应字符串
模板语言:被设计来自动生成文档的简单文本格式,通常是把变量传给模板,替换模板的特定位置上预先定义好的占位变量名

渲染

  • 相关模块细节已剔除,决定采用前后端分离,去模版化
    • 语法套语法,不符合简洁性。

templates:模版:在 HTML 文档中嵌入占位变量,动态传入数据。

  • 模版继承:
1
{% extends 'base.html'%}
  • 路由 route
1
2
3
4
5
from flask import Flask, render_template
# flask 提供的 render_template 函数将 Jinjia2 模版引擎集成到程序中
@app.route('/user/<name>'
def user(name):
return render_template('user.html', name=name) # 动态变量传给模板,传入模版名及模板变量列表,返回一个所有变量被替换的渲染的模板传入模版名及模板变量列表,返回一个所有变量被替换的渲染的模板

static:静态文件

url_for("static",filename="style.css")

1
2
3
4
5
6
7
8
# 静态文件的放置路径,可根据实际情况设置,这里设置为默认路径:'./static/'
app = Flask(__name__, static_url_path='')
#设置根路由
@app.route('/')
def root():
return app.send_static_file('index.html')
url_for('index', page=2) = /?page=2
url_for("static",filename="style.css")

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def {{ function_name }}({{ arg_list }}):
{{ function_body }}


from jinja2 import Template

template_str = """
def {{ function_name }}({{ arg_list }}):
{{ function_body }}
"""

template = Template(template_str)

code = template.render(function_name='my_func', arg_list='x, y', function_body='return x + y')

print(code)


  • Jinja2 模版中的变量代码块可以是任意 Python 类型或者对象,只要它能够被 Python 的 str() 方法转换为一个字符串就可以,比如,可以通过下面的方式显示一个字典或者列表中的某个元素:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 注释
{# 这是注释 #}

# 变量
{{ post.title }}

# 用 {**%%**} 定义的**控制代码块**,可以实现一些语言层次的功能,比如循环或者if语句
{% if user %}
{{ user }}
{% else %}
hello!

{% for index in indexs %}
{{ index }}
{% endfor %}

重定向和错误

  • 用 redirect() 函数把用户重定向到其它地方。
  • 用 abort() 函数放弃请求并返回错误代码。例如:
1
2
3
4
5
6
7
8
9
10
@app.route('/redir/')
def redir():
return redirect(url_for('login')
@app.route('/login/')
def login():
abort(401)
from flask import render_template
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404
  • 用户会从/redir/重定向到一个不能访问的页面 (401 意味着禁止访问)。

request 全局对象

  • form 属性是一个字典
1
2
3
4
5
6
7
8
9
10
11
from flask imoprt request
@app.route('/login',methods=['POST','GET'])
def login():
if request.method=='POST':
if valid_login(request.form['username'],request.form['password']):
return login_ok(request.form['username'])
else:
error='incalid username/password'
return render_template('login.html',error=error)
#对于 GET 方法的
searchword=request.args.get('key','')
  • 文件上传:request 的 files 属性,werkzeug 的 secure_filename 获取文件名

Cookies

1
2
3
4
5
6
7
8
9
10
11
服务端获取 cookies
@app.route('/')
def index():
username=request.cookies.get('username')
发送 cookies 到客户端
from flask import make_response
@app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('username', 'the username')
return resp

Cookies 访问

  • cookies 设置在响应对象上
  • 若能够使用会话,不要直接使用 cookies,会话更安全
1
2
3
4
5
6
7
8
from flask import request
@app.route('/')
def index():
username = request.cookies.get('username') # 访问 cookies,使用 cookies.get(key) 来代替 cookies[key],避免当 cookie 不存在时引发 KeyError 。
resp=make_response(render_template(...))
resp.set_cookie('username','my_username') # 设置 cookies
return resp

  • 除了请求对象之外还有一种称为 session 的对象,允许你在不同请求 之间储存信息。这个对象相当于用密钥签名加密的 cookie ,即用户可以查看你的 cookie ,但是如果没有密钥就无法修改它。

扩展

  • Flask-SQLalchemy:操作数据库,ORM;
  • Flask-script:终端脚本工具,脚手架;
  • Flask-migrate:管理迁移数据库;(能回滚)
  • Flask-Session:Session 存储方式指定;
  • Flask-WTF:表单;
  • Flask-Mail:邮件;
  • Flask-Bable:提供国际化和本地化支持,翻译;
  • Flask-Login:认证用户状态;
  • Flask-OpenID:认证, OAuth;
  • Flask-RESTful:开发 REST API 的工具;
  • Flask JSON-RPC: 开发 rpc 远程服务 [过程] 调用
  • Flask-Bootstrap:集成前端 Twitter Bootstrap 框架
  • Flask-Moment:本地化日期和时间
  • Flask-Admin:简单而可扩展的管理接口的框架
  • Flask-OAuthlib:OAuth 1.0/a, 2.0 客户端实现,供 Flask 使用。
  • flask-msearch
  • blueprint:蓝图、模块化
  • flask-moment:本地化日期和时间,依赖于 moment.js 与 jquery.js
  • Flask-WTF:表单;
  • Flask-Bable:提供国际化和本地化支持,翻译;
  • Flask-script:插入脚本;
  • Flask-Login:认证用户状态;
  • Flask-OpenID:认证;
  • Flask-RESTful:开发 REST API 的工具;
  • Flask-Bootstrap:集成前端 Twitter Bootstrap 框架;
  • Flask-Moment:本地化日期和时间;
  • Flask-Admin:简单而可扩展的管理接口的框架
  • Flask-PageDown:for markdown
  • Flask-Admin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app=Flask(__name__)
# dialect+driver://username:password@host:port/database?charset=utf8
# 配置 sqlalchemy 数据库驱动://数据库用户名:密码@主机地址:端口/数据库?编码
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:@localhost:3306/sqlalchemy?charset=utf8'
# 初始化
db = SQLAlchemy(app)
class Stuent(db.Model):
id=db.Column(db.Integer,primary_key=True)
studentname=db.Column(db.String(80),unique=True)
stuclass=db.Column(db.String(120),unique=True)
def __init__(self,studentname,stuclass):
self.studentname=studentname
self.stuclass=stuclass
def __repr__(self):
return '<Student {}>'.format(studentname)

Flask-WTF

Flask-WTF:表单,封装了 WTForms,可验证表单数据

  • HTML 页面中负责数据采集的部件。
  • 对应 HTML 标准字段
    • StringField:文本字段
    • TextAreaField:多行文本字段
    • PasswordField:密码文本字段
    • HiddenField:隐藏文件字段
    • DateField:值为 datetime.date 的文本字段
    • DateTimeField:值为 datetime.datetime 的文本字段
    • IntegerField:整数的文本字段
    • DecimalField:值为 decimal.Decimal 的文本字段
    • FloatField:值为浮点数的文本字段
    • BooleanField:复选框,值为 True 或 False
    • RadioField:一组单选框 SelectField
    • SelectMutipleField:下拉列表,可选择多个值
    • FileField:文件上传字段
    • SubmitField:表单提交按钮
    • FormField:将表单作为字段嵌入另一个表单
    • FieldList:一组指定类型的字段
  • 验证函数:
    • DataRequired 确保字段中有数据
    • EqualTo 比较两个字段的值,常用于比较两次密码输入
    • Length 验证输入的字符串长度
    • NumberRange 验证输入的值在数字范围内
    • URL 验证 URL
    • AnyOf 验证输入值在可选列表中
    • NoneOf 验证输入值不在可选列表中

Falsk-Restful

Flask-Restful:开发 REST API 的工具

资源

  • 资源:Resource:在资源之上定义各个 HTTP 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask, request
from flask.ext.restful import Resource, Api

app = Flask(__name__)
api = Api(app)

todos = {}

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]}

api.add_resource(TodoSimple, '/<string:todo_id>')

if __name__ == '__main__':
app.run(debug=True)

端点

Endpoints:

1
2
3
api.add_resource(Todo,'/','todo')# 可将资源绑定到多个 url
api.add_resource(Todo,
'/todo/<int:todo_id>', endpoint='todo_ep') # 指定 endpoint 参数

验证数据

  • reqparse:验证数据,如果不对,返加一个 400 错误请求及高亮的错误信息回应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from flask.ext.restful import reqparse

parser = reqparse.RequestParser() # 默认从 flask.Request.values 与 flask.Request.json 解析值
# Look only in the POST body
parser.add_argument('name', type=int, location='form')
# Look only in the querystring
parser.add_argument('PageSize', type=int, location='args')
# From the request headers
parser.add_argument('User-Agent', type=str, location='headers')
# From http cookies
parser.add_argument('session_id', type=str, location='cookies')
# From file uploads
parser.add_argument('picture', type=werkzeug.datastructures.FileStorage, location='files')

parser.add_argument('rate', type=int, help='Rate to charge for this resource')
parser.add_argument('name', type=str)
# 必需参数
parser.add_argument('name', type=str, required=True,help= "Name cannot be blank!")
# 多个值/列表
parser.add_argument('name', type=str, action='append')
# 多个位置
parser.add_argument('text',location=['headers','values'])

args = parser.parse_args() # 返回 Python 字典

继承解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask.ext.restful import RequestParser

parser = RequestParser()
parser.add_argument('foo', type=int)

parser_copy = parser.copy()
parser_copy.add_argument('bar', type=int)

# parser_copy has both 'foo' and 'bar'

parser_copy.replace_argument('foo', type=str, required=True, location='json')
# 'foo' is now a required str located in json, not an int as defined
# by original parser

parser_copy.remove_argument('foo')
# parser_copy no longer has 'foo' argument

遇到的问题

flask 中的模型加载两次

  • 关闭 debug 即可 debug=False

ConnectionResetError: [Errno 104] Connection reset by peer

  • 描述:这是在对一个对端 socket 已经关闭的连接调用 write 或 send 方法,在这种情况下,调用 write 或 send 方法后,对端 socket 便会向本端 socket 发送一个 RESET 信号,在此之后如果继续执行 write 或 send 操作,就会得到 errno 为 104,错误描述为 connection reset by peer。

  • 具体的分析可以结合 TCP 的 “ 四次挥手 “ 关闭. TCP 是全双工的信道, 可以看作两条单工信道, TCP 连接两端的两个端点各负责一条. 当对端调用 close 时, 虽然本意是关闭整个两条信道, 但本端只是收到 FIN 包. 按照 TCP 协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据. 也就是说, 因为 TCP 协议的限制, 一个端点无法获知对端的 socket 是调用了 close 还是 shutdown。

    对于一个 TCP 连接,如果对端执行 close 操作,则会向本端发送一个 FIN 分节,这时候读本端 socket 会返回 0,我们就知道对方已经关闭了连接,通常这时候我们会在本地调用 close 来主动关闭本端连接。但如果对方 socket 已经执行了 close 的操作,本端 socket 还继续在这个连接上写数据,就会触发对端 socket 发送 RST 报文,按照 TCP 的四次挥手原理,这时候本端 socket 应该也要开始执行 close 的操作流程了,而不是接着发数据.

基本是网络的问题或服务器的问题,可能的原因如下:

  • 服务器的并发连接数超过了其承载量,服务器会将其中一些连接关闭;
  • 客户关掉了浏览器,而服务器还在给客户端发送数据;
  • 浏览器端按了 Stop ,如果 对异常信息没有特别处理,可能在服务器的日志文件中重复出现该异常,造成服务器日志文件过大,影响服务器的运行,可以对引起异常的部分,使用 try…catch 捕获该异常,然后不输出或者只输出一句提示信息,避免使用 e.printStackTrace(); 输出全部异常信息。
  • 防火墙的问题;如果网络连接通过防火墙,而防火墙一般都会有超时的机制,在网络连接长时间不传输数据时,会关闭这个 TCP 的会话,关闭后在读写,就会导致异常。 如果关闭防火墙,解决了问题,需要重新配置防火墙,或者自己编写程序实现 TCP 的长连接。实现 TCP 的长连接,需要自己定义心跳协议,每隔一段时间,发送一次心跳协议,双方维持连接。

给博主来一杯卡布奇诺