python 构建一个简单的web框架(使用werkzeug)
Werkzeug版本:1.0.2
Werkzeug功能特性(转自百度百科):
-
HTTP头解析与封装
-
易于使用的request和response对象
-
基于浏览器的交互式JavaScript调试器
-
与 WSGI 1.0 规范100%兼容
-
支持Python 2.6,Python 2.7和Python3.3
-
支持Unicode
-
支持URI和IRI的Unicode使用工具
-
内置支持兼容各种浏览器和WSGI服务器的实用工具
-
集成URL请求路由系统
实现功能:
-
创建一个简单的web服务器
- 添加模板功能
- 模仿(copy)flask的修饰器构建路由
- redis模块
- session模块
一、创建web服务器
构建Web类,方便参数或函数的调用,后面函数的构建大部分都是此类的子函数 (werkzeug.wrappers->Request, Response)
from werkzeug.wrappers import Request
from werkzeug.wrappers import Response
class Web: def __init__(self, config): # 储存配置,保留 pass def wsgi_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 import Environment from jinja2 import FileSystemLoader from werkzeug.urls import url_parse def get_hostname(url):
# url解析 return url_parse(url).netloc class Web: 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) # 加载静态资源文件 if with_static: app.wsgi_app = SharedDataMiddleware( app.wsgi_app, {"/static": os.path.join(os.path.dirname(__file__), "static")} ) return app
添加模板渲染函数
class Web: ... 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 import Map from werkzeug.routing import Rule class Web: url_rule_class = Rule def __init__(self, config): ...
# 路由空间
self.url_map = Map([])
# 存放自定义的函数
self.view_functions = {}
仿照flask构建路由规则生成函数(参考:https://www.cnblogs.com/eric_yi/p/8325044.html)
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__
class Web: ...
def add_url_rule(self, rule, endpoint=None, view_func=None, **options): if endpoint is None: # 如果没有提供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 is None: # 如果view_func函数中有这个methods参数,则使用view_func中的。如果没有则赋一个列表('GET',)给methods methods = getattr(view_func, 'methods', None) or ('GET',) # 如果methods是字符串类型 if isinstance(methods, string_types): # 抛出一个异常:methods需要是一个可以迭代的字符串 raise TypeError('methods需要是一个可以迭代的字符串, ' '例: @app.route(..., methods=["POST"])') # 把methods里面的item都改成大写 methods = set(item.upper() for item in methods) # 在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 is None: # 如果OPTIONS字符串没有在methods里面 if 'OPTIONS' not in methods: # 则把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 not None: # 在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)
class Web: ... def route(self, rule, **options): def decorator(f): endpoint = options.pop("endpoint", None)
# 调用上面的创建函数 self.add_url_rule(rule, endpoint, f, **options) return f return decorator
调用生成的函数 (werkzeug.exceptions->HTTPException, NotFound)
class Web: ... def dispatch_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) if isinstance(response, str): # 字符串型的数据单独处理(直接输出) response = Response(response) return response except NotFound: # 404错误处理 return self.error_404() except HTTPException as e: # 其他错误处理(不只有500,但模板只做了500) print(e) return self.error_500()
错误处理
class Web: ...
# 渲染相应的错误模板并返回 def error_500(self): response = self.render_template("500.html") response.status_code = 500 return response def error_404(self): response = self.render_template("404.html") response.status_code = 404 return response
四、redis
配置redis (redis)
import redis
class Web: 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)
import base64 import time import os import json def get_session_id(request):
# 通过id获取session return request.cookies.get('session_id', '') class Session: __instance = None def __init__(self):
# 初始化,创建session路由空间 self.__session_map__ = {} self.__storage_path__ = None def set_storage_path(self, path):
# 设置仓库地址 self.__storage_path__ = path def storage(self, session_id):
# 进行各种session操作(读写) session_path = os.path.join(self.__storage_path__, session_id) if self.__storage_path__ is not None: 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 is None: cls.__instance = super(Session, cls).__new__(cls, *args, **kwargs) return cls.__instance def push(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] = value else: 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 in current_session: current_session.pop(item, value) self.storage(session_id) def load_local_session(self):
# 载入本地的session(创建app时使用) if self.__storage_path__ is not None: session_path_list = os.listdir(self.__storage_path__) for session_id in session_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()) def map(self, request): return self.__session_map__.get(get_session_id(request), {}) def get(self, request, item):
# 获得session return self.__session_map__.get(get_session_id(request), {}).get(item, None) session = Session()
配置session
class Web: def __init__(self, config, session_path=".session\\"):
...
# session存放地址 self.session_path = session_path ... def create_app(config, with_static=True): ... if not os.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保存
class Web: ... def wsgi_app(self, environ, start_response): ...import hashlib
# 根据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)
新手上路,还不太熟练。。

浙公网安备 33010602011771号