零基础小白必看篇从0到1构建Python Web框架.docx
- 文档编号:23823047
- 上传时间:2023-05-21
- 格式:DOCX
- 页数:16
- 大小:22.52KB
零基础小白必看篇从0到1构建Python Web框架.docx
《零基础小白必看篇从0到1构建Python Web框架.docx》由会员分享,可在线阅读,更多相关《零基础小白必看篇从0到1构建Python Web框架.docx(16页珍藏版)》请在冰豆网上搜索。
零基础小白必看篇从0到1构建PythonWeb框架
零基础小白必看篇:
从0到1构建PythonWeb框架
造轮子是最好的一种学习方式,本文尝试从0开始造个PythonWeb框架的轮子,我称它为ToyWebF。
本文操作环境为:
MacOS,文中涉及的命令,请根据自己的系统进行替换。
ToyWebF的简单特性:
1.支持多种不同形式的路由注册方式
2.支持静态HTML、CSS、JavaScript
3.支持自定义错误
4.支持中间件
下面我们来实现这些特性。
最简单的web服务
首先,我们需要安装gunicorn,回忆一下Flask框架,该框架有内置的Web服务器,但不稳定,所以上线时通常会替换成uWSGI或gunicorn,这里不搞这个内置Web服务,直接使用gunicorn。
这里多说一句,小编是一名python开发工程师,这里有我自己整理的一套最新的python系统学习教程,包括从基础的python脚本到web开发、爬虫、数据分析、数据可视化、机器学习等。
想要这些资料的可以关注小编,并在后台私信小编即可领取。
我们创建新的目录与Python虚拟环境,在该虚拟环境中安装gunicorn
mkdirToyWebF
python3-mvenvvenv#创建虚拟环境
sourcevenv/bin/activate#激活虚拟环境
pipinstallgunicorn
复制代码
在啥都没有的情况下,构建最简单的Web服务,在ToyWebF目录下,创建app.py与api.py文件,写入下面代码。
api.py文件
classAPI:
def call(self,environ,start_response):
response_body=b"Hello,World!
"
status="200OK"
start_response(status,headers=[])
returniter([response_body])
app.py文件
fromapiimportAPI
app=API()
复制代码
运行gunicornapp:
app访问http:
//127.0.0.1:
8000,可以看见Hello,World!
,但现在请求体中的参数在environ变量中,难以解析,我们返回的response也是bytes形式。
我们可以使用webob库,将environ中的数据转为Request对象,将需要返回的数据转为Response对象,处理起来更加直观方便,直接通过pip安装一下。
pipinstallwebob
复制代码
然后修改一下API类的__call__方法,代码如下。
fromwebobimportRequest,Response
classAPI(object):
defwsgi_app(self,environ,start_response):
"""通过webob将请求的环境信息转为request对象"""
request=Request(environ)
response=self.handle_request(request)
returnresponse(environ,start_response)
def call(self,environ,start_response):
self.wsgi_app(environ,start_response)
复制代码
上述代码中,通过webob库的Request类将environ对象(请求的环境信息)转为容易处理的request,随后调用handle_request方法对request进行处理,处理的结果,通过response对象返回。
handle_request方法在ToyWebF中非常重要,它会匹配出某个路由对应的处理方法,然后调用该方法处理请求并将处理的结果返回,在解析handle_request前,需要先讨论路由注册实现,代码如下。
classAPI(object):
def init(self):
#url路由
self.routes={}
defroute(self,path):
#添加路由的装饰器
defwrapper(handler):
self.add_route(path,handler)
returnhandler
returnwrapper
defadd_route(self,path,handler):
#相同路径不可重复添加
assertpathnotinself.routes,"Suchroutealreadyexists"
self.routes[path]=handler
复制代码
其实就是将路由和方法存到self.routes字典中,可以通过route装饰器的形式将路由和方法关联,也可以通过add_route方法关联,在app.py中使用一下。
app=API()
通过装饰器关联路由和方法
@app.route("/home")
defhome(request,response):
response.text="ThisisHome"
路由中可以有变量,对应的方法也需要有对应的参数
@app.route("/hello/{name}")
defhello(requst,response,name):
response.text=f"Hello,{name}"
可以装饰类
@app.route("/book")
classBooksResource(object):
defget(self,req,resp):
resp.text="BooksPage"
defhandler1(req,resp):
resp.text="handler1"
可以直接通过add_route方法添加
app.add_route("/handler1",handler1)
复制代码
因为url中可以存在变量,如@app.route("/hello/{name}"),所以在匹配时,需要进行解析,可以使用正则匹配的方式进行匹配,parse这个第三方库已经帮我们实现了相应的正则匹配逻辑,pip安装使用一下则可。
pipinstallparse
In[1]:
fromparseimportparse
匹配
In[2]:
res=parse("/hello/{name}","/hello/二两")
In[3]:
res.named
Out[3]:
{'name':
'二两'}
复制代码
这里定义find_handler方法来实现对self.routes的遍历。
classAPI(object):
deffind_handler(self,request_path):
#遍历路由
forpath,handlerinself.routes.items():
#正则匹配路由
parse_result=parse(path,request_path)
ifparse_resultisnotNone:
#返回路由对应的方法和路由本身
returnhandler,parse_result.named
returnNone,None
复制代码
了解了路由与方法关联的原理后,就可以实现handle_request方法,该方法主要的路径就是根据路由调度对应的方法,代码如下。
importinspect
classAPI(object):
defhandle_request(self,request):
"""请求调度"""
response=Response()
handler,kwargs=self.find_handler(request.path)
try:
ifhandlerisnotNone:
ifinspect.isclass(handler):
#如果是类,则获取其中的方法
handler=getattr(handler(),request.method.lower(),None)
ifhandlerisNone:
#类中该方法不存在,则该类不支持该请求类型
raiseAttributeError("Methodnowallowed",request.method)
handler(request,response,**kwargs)
else:
#返回默认错误
self.defalut_response(response)
exceptExceptionase:
raisee
returnresponse
复制代码
在该方法中,首先实例化webob库的Response对象,然后通过self.find_handler方法获取此次请求路由对应的方法和对应的参数,比如。
@app.route("/hello/{name}")
defhello(requst,response,name):
response.text=f"Hello,{name}"
复制代码
它将返回hello方法对象和name参数,如果是/hello/二两,那么name就是二两。
因为route装饰器可能装饰器的类对象,比如。
可以装饰类
@app.route("/book")
classBooksResource(object):
defget(self,req,resp):
resp.text="BooksPage"
复制代码
此时self.find_handler方法返回的hanler就是个类,但我们希望调用的是类中的get、post、delete等方法,所以需要一个简单的判断逻辑,通过inspect.isclass方法判断handler如果是类对象,那么就通过getattr方法获取类对象实例的中对应的请求方法。
获取请求方法,request.method.lower()可为get、post、delete
handler=getattr(handler(),request.method.lower(),None)
复制代码
如果类对象中没有该方法属性,则抛出该请求类型不被允许的错误,如果不是类对象或类对象中存在该方法属性,则直接调用则可。
此外,如果方法的路由并没有注册到self.routes中,即404的情况,定义了defalut_response方法返回其中内容,代码如下。
classAPI(object):
defdefalut_response(self,response):
response.status_code=404
response.text="NotFound"
复制代码
如果handle_request方法中调度的过程出现问题,则直接raise将错误抛出。
至此,一个最简单的web服务就编写完成了。
支持静态文件
回顾Flask,Flask可以支持HTML、CSS、JavaScript等静态文件,利用模板语言,可以构建出简单但美观的Web应用,我们让TopWebF也支持这一功能,最终实现图中的网站,完美兼容静态文件。
Flask使用了jinja2作为其html模板引擎,ToyWebF同样使用jinja2,jinja2其实实现一种简单的DSL(领域内语言),让我们可以在HTML中通过特殊的语法改变HTML的结构,该项目非常值得研究学习。
首先pipinstalljinja2,然后就可以使用它了,在ToyWebF项目目录中创建templates目录,以该目录作为默认的HTML文件根目录,代码如下。
fromjinja2importEnvironment,FileSystemLoader
classAPI(object):
def init(self,templates_dir="templates"):
#html文件夹
self.templates_env=Environment(loader=FileSystemLoader(os.path.abspath(self.templates_dir)))
deftemplate(self,template_name,context=None):
"""返回模板内容"""
ifcontextisNone:
context={}
returnself.templates_env.get_template(template_name).render(**context)
复制代码
首先利用jinja2的FileSystemLoader类将filesystem中的某个文件夹作为loader,然后初始化Environment。
在使用的过程中(即调用template方法),通过get_template方法获得具体的某个模板并通过render方法将对应的内容传递给模板中的变量。
这里我们不写前端代码,直接去互联网中下载模板,这里下载了Bootstrap提供的免费模板,可以自行去
{{title}}
你好呀-{{name}}
复制代码
然后在app.py文件中为index.html定义路由以及需要的参数。
@app.route("/index")
defindex(req,resp):
template=app.template("index.html",context={"name":
"二两","title":
"ToyWebF"})
#resp.body需要bytes,template方法返回的是unicodestring,所以需要编码
resp.body=template.encode()
复制代码
至此html文件的支持就完成了,但此时的html无法正常载入css和js,导致页面布局非常丑陋且交互无法使用。
接着就让ToyWebF支持css、js,首先在ToyWebF目录下创建static文件夹用于存放css、js或img等静态文件,随后直接将前面下载的模板,其中的静态文件复制到static中则可。
通过whitenoise第三方库,可以通过简单的几行代码让web框架支持css和js,不需要依赖nginx等服务,首先pipinstallwhitenoise,随后修改API类的__init__方法,代码如下。
classAPI(object):
def__init__(self,templates_dir="templates",static_dir="static"):
#html文件夹
self.templates_env=Environment(loader=FileSystemLoader(os.path.abspath(self.templates_dir)))
#css、JavaScript文件夹
self.whitenoise=WhiteNoise(self.wsgi_app,root=static_dir)
复制代码
其实就是通过WhiteNoise将self.wsgi_app方法包裹起来,在调用API的__call__方法时,直接调用self.whitenoise。
classAPI(object):
def__call__(self,environ,start_response):
returnself.whitenoise(environ,start_response)
复制代码
此时,如果请求web服务获取css、js等静态资源,WhiteNoise会获取其内容并返回给client,它在背后会匹配静态资源在系统中对应的文件并将其读取返回。
至此,一开始的网页效果就实现好了。
自定义错误
web服务如果出现500时,默认会返回internalservererror,这显得比较丑,为了让框架使用者可以自定义500时返回的错误,需要添加一些代码。
首先API初始化时,初始self.exception_handler对象并定义对应的方法添加自定义的错误
classAPI(object):
def__init__(self,templates_dir="templates",static_dir="static"):
#自定义错误
self.exception_handler=None
defadd_exception_handler(self,exception_handler):
#添加自定义errorhandler
self.exception_handler=exception_handler
复制代码
在handler_request方法进行请求调度时,调度的方法执行逻辑时报500,此时不再默认将错误抛出,而是先判断是否有自定义错误处理。
classAPI(object):
defhandle_request(self,request):
"""请求调度"""
try:
#...省略
exceptExceptionase:
#为空,才返回internalservererror
ifself.exception_handlerisNone:
raisee
else:
#自定义错误返回形式
self.exception_handler(request,response,e)
returnresponse
复制代码
在app.py中,自定义错误返回方法,如下。
defcustom_exception_handler(request,response,exception_cls):
response.text="Oops!
Somethingwentwrong."
#自定义错误
app.add_exception_handler(custom_exception_handler)
复制代码
custom_exception_handler方法只返回自定义的一段话,你完全可以替换成美观的template。
我们可以实验性定义一个路由来看效果。
@app.route("/error")
defexception_throwing_handler(request,response):
raiseAssertionError("Thishandlershouldnotbeuser")
复制代码
支持中间件
Web服务的中间件也可以理解成钩子,即在请求前可以对请求做一些处理或者返回Response前对Response做一下处理。
为了支持中间件,在TopWebF目录下创建middleware.py文件,在编写代码前,思考一下如何实现?
回顾一下现在请求的调度逻辑。
1.通过routes装饰器关联路由和方法2.通过API.whitenoise处理3.如果是请求API接口,那么会将参数传递给API.wsgi_app4.API.wsgi_app最终会调用API.handle_request方法获取路由对应的方法并调用该方法执行相应的逻辑
如果希望在request前以及response后做相应的操作,那么其实就需要让逻辑在API.handle_request前后执行,看一下代码。
fromwebobimportRequest
classMiddleware(object):
def__init__(self,app):
self.app=app#API类实例
defadd(self,middleware_cls):
#实例化Middleware对象,包裹self.app
self.app=middleware_cls(self.app)
defprocess_request(self,req):
#request前要做的处理
pass
defprocess_response(self,req,resp):
#response后要做的处理
pass
defhandle_request(self,request):
self.process_request(request)
response=self.app.handle_request(request)
self.process_response(request,response)
returnresponse
def__call__(self,environ,start_response):
request=Request(environ)
response=self.app.handle_request(request)
returnresponse(environ,start_response)
复制代码
其中add方法会实例化Middleware对象,该对象会将当前的API类实例包裹起来。
Middleware.handle_request方法其实就是在self.app.handle_request前调用self.process_request方法处理request前的数据以及调用self.process_response处理response后的数据,而核心的调度逻辑,依旧交由API.handle_request方法进行处理。
这里的代码可能会让人感到疑惑,__call__方法和handle_request方法中都有self.app.handle_request(request),但其调用对象似乎不同?
这个问题暂时放一下,先继续完善代码,然后再回来解释。
接着在api.py中为API创建middleware属性以及添加新中间件的方法。
classAPI(object):
def__init__(self,templates_dir="templates",static_dir="static"):
#请求中间件,将api对象传入
self.middleware=Middleware(self)
defadd_middleware(self,middleware_cls):
#添加中间件
self.middleware.a
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 零基础小白必看篇从0到1构建Python Web框架 基础 小白必看篇 构建 Python Web 框架