python搭建web框架_python 构建一个简单的web框架(使用werkzeug)

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)

新手上路,还不太熟练。。

你可能感兴趣的:(python搭建web框架)