Werkzeug版本:1.0.2
Werkzeug功能特性(转自百度百科):
HTTP头解析与封装
易于使用的request和response对象
基于浏览器的交互式JavaScript调试器
与 WSGI 1.0 规范100%兼容
支持Python 2.6,Python 2.7和Python3.3
支持URI和IRI的Unicode使用工具
内置支持兼容各种浏览器和WSGI服务器的实用工具
集成URL请求路由系统
实现功能:
创建一个简单的web服务器
添加模板功能
模仿(copy)flask的修饰器构建路由
redis模块
session模块
一、创建web服务器
构建Web类,方便参数或函数的调用,后面函数的构建大部分都是此类的子函数 (werkzeug.wrappers->Request, Response)
from werkzeug.wrappersimport Request
from werkzeug.wrappers import Response
classWeb:def __init__(self, config):#储存配置,保留
pass
defwsgi_app(self, environ, start_response):#返回响应
request =Request(environ)
response= Response("Hello %s!" % request.args.get('name', 'World!'))def __call__(self, environ, start_response):#将Web类变成一个可调用对象
return self.wsgi_app(environ, start_response)
定义创建对象的函数
def create_app(config):#创建app对象
app =Web(config)return app
二、模板功能
为web添加templates地址并使用模板引擎jinja2 (jinja2->Environment, FileSustemLoader) (werkzeug.urls->url_parse)
from jinja2 importEnvironmentfrom jinja2 importFileSystemLoaderfrom werkzeug.urls importurl_parsedefget_hostname(url):
# url解析returnurl_parse(url).netlocclassWeb:def __init__(self, config):
# template路径
template_path= os.path.join(os.path.dirname(__file__), "templates")
# 配置jinja2环境
self.jinja_env=Environment(
loader=FileSystemLoader(template_path), autoescape=True
)
self.jinja_env.filters["hostname"] = get_hostname
加载静态资源文件 (werkzeug.middleware.shared_data->SharedDataMiddleware)
from werkzeug.middleware.shared_data import SharedDataMiddleware
def create_app(config, with_static=True):
app.Web(config)#加载静态资源文件
ifwith_static:
app.wsgi_app=SharedDataMiddleware(
app.wsgi_app, {"/static": os.path.join(os.path.dirname(__file__), "static")}
)return app
添加模板渲染函数
classWeb:
...def render_template(self, template_name, **context):#渲染模板
t =self.jinja_env.get_template(template_name)return Response(t.render(context), mimetype="text/html")
三、构建路由
为Web类添加路由空间和规则 (werkzeug.routing->Map, Rule)
from werkzeug.routing importMapfrom werkzeug.routing importRuleclassWeb:
url_rule_class=Ruledef __init__(self, config):
...
# 路由空间
self.url_map= Map([])
# 存放自定义的函数
self.view_functions = {}
def _endpoint_from_view_func(view_func):
assert view_func is not None, "expected view func if endpoint is not provided."
return view_func.__name__
classWeb:
...
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):if endpoint isNone:#如果没有提供endpoint参数,则默认用view_func的名字
endpoint =_endpoint_from_view_func(view_func)#把endpoint参数添加到options里面
options['endpoint'] =endpoint#从options中pop出methods参数,并把值赋给methods变量,如果没有则置为None
methods = options.pop('methods', None)#moehods的值为None的情况下
if methods isNone:#如果view_func函数中有这个methods参数,则使用view_func中的。如果没有则赋一个列表('GET',)给methods
methods = getattr(view_func, 'methods', None) or ('GET',)#如果methods是字符串类型
ifisinstance(methods, string_types):#抛出一个异常:methods需要是一个可以迭代的字符串
raise TypeError('methods需要是一个可以迭代的字符串,'
'例: @app.route(..., methods=["POST"])')#把methods里面的item都改成大写
methods = set(item.upper() for item inmethods)#在view_func里面定义了一个属性required_methods = ()
#作用:用来定义一些必须的方法,配合provide_automatic_options使用
required_methods = set(getattr(view_func, 'required_methods', ()))
provide_automatic_options=getattr(view_func,'provide_automatic_options', None)#判断provide_automati_options是否为None
if provide_automatic_options isNone:#如果OPTIONS字符串没有在methods里面
if 'OPTIONS' not inmethods:#则把provude_automatic_options改为True,并把OPTIONS添加到required_methods里面
provide_automatic_options =True
required_methods.add('OPTIONS')#如果OPTIONS在methods里面,则把provide_automatic_options设置为False
else:
provide_automatic_options=False#合并required_methods和methods这两个集合到methods里面
methods |=required_methods#创建路由规则
#调用url_rule_class方法,由于在Flask类的全局变量中定义了:url_rule_class = Rule, Rule是werkzeug/routing.py里面的一个类
#也就是相当于实例化了Rule得到了rule对象,具体实例化后的结果请看Rule源码分析
rule = self.url_rule_class(rule, methods=methods, **options)#把provide_automatic_options属性添加到rule对象里面
rule.provide_automatic_options =provide_automatic_options#在Flask类的__init__里面定义了self.url_map = Map(),Map是werkzeug/routing.py里面的一个类
#self.url_map相当与实例化了Map,.add则是调用了Map类里面的add方法
#具体运行结果,请参考Map源码分析,以及Map源码中的add方法分析
self.url_map.add(rule)#如果提供了view_func
if view_func is notNone:#在flask类的__init__里面定义了self.view_functions = {},
#从字典里面取endpoint值并赋值为old_func,(endpoint是传递的参数,默认为视图函数名)
old_func =self.view_functions.get(endpoint)#如果old_func有值,并且不等于view_func
if old_func is not None and old_func !=view_func:#则抛出异常:视图函数映射被一个已经存在的函数名重写了
#也就是说已经存在了一个endpoint:old_func的映射,但是old_fun却不是view_func,也就是说endpoint重复了
raise AssertionError('视图函数映射被一个已经存在的函数名重写了:'
'%s' %endpoint)#添加视图函数与endpoint映射到view_functions字典里面
self.view_functions[endpoint] = view_func
构建路由的修饰器函数(route)
classWeb:
...def route(self, rule, **options):defdecorator(f):
endpoint= options.pop("endpoint", None)
# 调用上面的创建函数
self.add_url_rule(rule, endpoint, f,**options)returnfreturn decorator
调用生成的函数 (werkzeug.exceptions->HTTPException, NotFound)
classWeb:
...defdispatch_request(self, request):
adapter=self.url_map.bind_to_environ(request.environ)try:
endpoint, values=adapter.match()
response= self.view_functions.get(f"{endpoint}")(request, **values)ifisinstance(response, str):#字符串型的数据单独处理(直接输出)
response =Response(response)returnresponseexceptNotFound:#404错误处理
returnself.error_404()exceptHTTPException as e:#其他错误处理(不只有500,但模板只做了500)
print(e)return self.error_500()
错误处理
classWeb:
...
# 渲染相应的错误模板并返回deferror_500(self):
response= self.render_template("500.html")
response.status_code= 500
returnresponsedeferror_404(self):
response= self.render_template("404.html")
response.status_code= 404
return response
四、redis
配置redis (redis)
import redis
classWeb:def __init__(self, config, session_path=".session\\"):
...#配置redis
self.redis = redis.Redis(config.get("redis_host", "localhost"), config.get("redis_port", 6379))
五、session
session模块(不太完善,暂时只能以数字作为session的键) (base64, time, os, json)
importbase64importtimeimportosimportjsondefget_session_id(request):
# 通过id获取sessionreturn request.cookies.get('session_id', '')classSession:__instance =Nonedef __init__(self):
# 初始化,创建session路由空间
self.__session_map__ ={}
self.__storage_path__ =Nonedefset_storage_path(self, path):
# 设置仓库地址
self.__storage_path__ =pathdefstorage(self, session_id):
# 进行各种session操作(读写)
session_path= os.path.join(self.__storage_path__, session_id)if self.__storage_path__ is notNone:
with open(session_path,'wb') as f:
content= json.dumps(self.__session_map__[session_id])
f.write(base64.encodebytes(content.encode()))def __new__(cls, *args, **kwargs):
# 返回创建的实例if cls.__instance isNone:
cls.__instance = super(Session, cls).__new__(cls, *args, **kwargs)return cls.__instance
defpush(self, request, item, value):
# 添加session
session_id=get_session_id(request)if session in self.__session_map__:
self.__session_map__[get_session_id(request)][item] =valueelse:
self.__session_map__[session_id] ={}
self.__session_map__[session_id][item] =value
self.storage(session_id)def pop(self, request, item, value=True):
# 删除session
session_id=get_session_id(request)
current_session= self.__session_map__.get(get_session_id(request), {})if item incurrent_session:
current_session.pop(item, value)
self.storage(session_id)defload_local_session(self):
# 载入本地的session(创建app时使用)if self.__storage_path__ is notNone:
session_path_list= os.listdir(self.__storage_path__)for session_id insession_path_list:
path= os.path.join(self.__storage_path__, session_id)
with open(path,'rb') as f:
content=f.read()
content=base64.decodebytes(content)
self.__session_map__[session_id] =json.loads(content.decode())defmap(self, request):return self.__session_map__.get(get_session_id(request), {})defget(self, request, item):
# 获得sessionreturn self.__session_map__.get(get_session_id(request), {}).get(item, None)session= Session()
配置session
classWeb:def __init__(self, config, session_path=".session\\"):
...
# session存放地址
self.session_path=session_path...def create_app(config, with_static=True):
...if notos.path.exists(app.session_path):
os.mkdir(app.session_path)
# 设置地址
session.set_storage_path(app.session_path)
# 加载以前的session
session.load_local_session()return app
cookie保存
classWeb:
...defwsgi_app(self, environ, start_response):
...importhashlib
# 根据ip加密后的字符串,通过cookies的形式传递给用户来识别用户身份(有缺陷,需要完善)
m= hashlib.md5(request.remote_addr.encode()) #转成二进制,md5加密
value =m.hexdigest()
response.set_cookie(key='session_id', value=value)return response(environ, start_response)
新手上路,还不太熟练。。