web开发概述及基本框架书写
3 BS 编程web 也叫CS开发
CS 及客户端,服务器端编程
客户端,服务器端之间需要socket,约定协议,版本(往往使用的协议是TCP或UDP),指定地址和端口,就可以通信了客户端,服务端传输数据,数据可以有一定的格式,双方必须约定好
4 HTTP 和TCP 协议1 短链接B=Browser,Browser是一种特殊的客户端,支持HTTP(S)协议,能够通过URL 向服务器端发起请求,等待服务端返回HTML等数据,并在浏览器内可视化展示程序
S=server,Server支持HTTP(S)协议,能够接受众多客户端发起的HTTP请求,经过处理,将HTML等数据返回给浏览器
本质上来说,BS是一种特殊的CS,及客户端必须是一种支持HTTP协议且能够解析并渲染HTML的软件,服务端必须是能够接受客户端HTTP访问的服务器软件
HTTP 底层使用TCP传输,需要通过相应的规则来处理
HTTP 超文本传输协议,在文本的基础上做一些突破,相关的渲染处理,断行等。使用标签的方式进行规定的处理,文本已经有了一个格式,浏览器中必须一种能力,支持这种文本的处理和渲染。
浏览器必须支持HTTP协议,必须能够理解超文本并将其绘制出来
BS 开发分为两端开发
1 客户端开发,或称为前端开发
2 服务端开发,python可以学习WSGI,Django,Flask,Tornado等python WEB 框架
WSGI, web Server Gateway interface,可以看做是一种底层协议,它规定了服务器程序和应用程序各自实现什么借口,python称为wsgirefflask: 基于WSGI ,微框架
Django:基于WSGI,开源的WEB框架
2 对于持续的TCP链接,一个TCP能否发送多个请求在http1.1之前,都是一个请求一个连接,而TCP的链接创建销毁成本高,对服务器影响较大,因此自从http1.1开始,支持keep-alive,默认也开启,一个连接打开后,会保持一段时间,浏览器再访问该服务器资源就使用这个TCP链接,减轻了服务器的压力,提高了效率
所有的动态网页开发,都必须是有状态的协议
3 一个TCP中的HTTP请求能否同时发送如果是持续连接,一个TCP是可以发送多个HTTP请求的
HTTP/1.1存在一个问题,单个TCP连接在同一时刻只能处理一个请求,意思是说: 两个请求的生命周期不能重叠,任意两个HTTP请求从开始到结束的时间在同一个TCP连接里不能重叠
虽然HTTP/1.1规范中规定了Pipelining来试图解决这个问题,但此功能默认是关闭的
Pipelining 中
客户端可以在一个连接中发送多个请求(不需要等待任意请求的响应)。收到请求的服务器必须按照请求收到的顺序发送响应。pipelining的缺点
1 一些代理服务器不能正确支持 HTTP pipelining
2 正确的流水线实现是复杂的
3 如果第一个请求的处理花费大量时间,则会导致后面的请求无法处理,造成阻塞。
4 HTTP1.1中浏览器页面加载效率提高方式Http2 提供了Multiplexing 多路传输特性,可以在一个TCP连接中同时完成多个HTTP请求
5 收到的HTML如果包含图片文本等,通过什么协议下载的1 维持和服务其已经建立的TCP连接,在同一个连接上顺序处理多个请求
2 和服务器建立多个TCP连接浏览器对同一个Host 建立TCP连接数量有没限制
Chrome 最多允许对同一个Host建立6个TCP链接,不同浏览器有区别
如果图片都是HTTPS 连接并且在同一域名下,那么浏览器在SSL握手之后会和服务器协商能不能使用HTTP2,如果能的话就是用Multiplexing 功能在这个连接上进行多路传输,不过也未必会所有挂载在各个域名的资源都会使用一个TCP连接获取吗,但可以确定的是multiplexing 可能很被用到
6无状态协议如果发现不是使用HTTP2,或者不用HTTPS,(现实中的 HTTP2 都是在 HTTPS 上实现的,所以也就是只能使用 HTTP/1.1),那么浏览器就会在一个HOST上建立多个TCP连接,连接数量的最大限制取决于浏览器的设置,这些来凝结会在空闲的时候被浏览器用来发送新请求,如果所有连接都在发送请求,那么其只能等待了。
7 有链接同一个客户端的两次请求之间没有任何关系,从服务端的角度看,他不知道这两个请求来自同一个客户端
最早的设计是不需要知道两者之间的联系的,
HTTP协议是无状态协议
8 URL 和相关请求及报文信息有链接,因为HTTP 是基于TCP 链接的,需要3次握手,4次断开
9 常见的传递信息的方式1 GET 中使用 query string详情请看:https://blog.51cto.com/11233559/2093789
http://127.0.0.1/login?user=zhangsan&password=123
2 在POST 请求体中提交数据至服务器端登录窗口不能使用GET传输,GET头部的长度是有限的,不能多于200多个以外的传输
格式是 ? 后面加key1=value1&key2=value2
5 HTML 简介当使用POST 传输数据时,其相关的数据都被封装在请求体及body中,而不是get中的直接暴露。
大的数据传输,必须使用POST,而不能使用GET传输数据。
6 Cookie1 简介HTML 是一种格式的约定,需要的数据是动态的,去数据库查的数据不是死的,是动态的,静态文本文件包括图片
HTML 是将文本原封不动的返回,若是一个登陆的用户名和密码的匹配问题的时候,就不是HTML能做的事情,此时便需要动态网页来完成。如python,只有脚本是不行的,这就需要类似的解释器来进行处理。Php,asp等动态的网页技术,server page 服务器端的页面。动态页面中的无状态带来很大的问题,再次登录将导致登录后的和登录的没关系。既然你链接到我,我可以发送一个唯一标识给你,你需要下次将这个标识带来,来保证是你,服务端需要发送和记录标识,此处需要写入到内存的数据结构中,当用户量很大时,记录的东西就不仅仅是这个用户标识了。
2 cookie 的生成:cookie:是一种客户端,服务端传递数据的技术 ,其保存的形式是键值对信息
浏览器发起每一个请求,都会把cookie信息给服务端,服务端可以通过判断这些信息,来确定这次请求是否和之前的请求有关联
3 session ID一般来说cookie信息是在服务器端生成,返回给客户端
客户端可以自己设置cookie信息
Cookie 一般是当你第一次链接服务器的时候服务器会查看是否有cookie带过来,若没有则推送一个标识,这个标识中会在HTTP的response包中存在,其会在浏览器中保存起来。如果再次对同样网站发起请求,如果cookie没过期时,其会继续处理此标识。若是同一个且有效,则若登录过,则不显示登录页面,若没登录,则强制跳转到登录页面。如果一个网站一直登录,其发现cookie快过期了,则会延长。
二 WSGI简介1 概述1 请求图及相关概述Cookie 是对不同的域名有区分的
cookie中加的ID 叫做session ID ,称为会话ID,当会话完结后,ID就消亡了,浏览器关闭,
Session 是存放在服务器端的,其会增加内存。后期则使用无session, token往往中间会使用redis和memcached进行处理
请求来的时候,其得带着是否是同一个会话标识
cookie可以伪造
2 三个角色:1 客户端工具:WSGI 主要规定了服务器端和应用程序之间的接口
2 服务端工具:1 http server浏览器
2 WSGI app 应用程序可以接受用户的socket请求并和客户端达成HTTP协议并识别解析,将数据交给后端的WSGI app 进行处理
Server 必须支持HTTP协议,在python中实现了WSGI的接口,HTTP server得支持WSGI协议,将数据传递给程序,(app返回)然后返回给客户端对应的状态情况(响应头),使得浏览器做好准备,然后再返回给server,再由server将其包装成HTTP的协议并解析处理。
后端真实处理业务的函数对象
后端APP满足的条件
1 可通过前面的WGSI Server进行相关的调用操作
应用程序应该是一个可调用对象
调用其实是回调,调用的其实是APP的某个方法
python中应该是函数,类,实现了call方法的类的实例
2 相关参数详解2 这个可调用对象应该接受两个参数
满足了WSGI 的基本要求,必须再留一个空,协议的封装是需要在server端的,因此要将你写的东西交给 http server ,由http server对返回结果进行处理 其上述返回必须是一个可迭代对象(list,dict等)两个参数就是入 request和出response
Handler 和 body都给了app
逻辑处理: 调用对应的方法给客户端。
http server 返回给app server 的参数
eviron和start_response 这两个参数可以是任意的合法名。但一般都是这两个名字
eviron 是包含HTTP请求信息的dict对象
start_response 是一个可调用对象,有3个参数,定义如下:
start_response(status,response_headers,exc_info=None)
status 是状态码。如200 ok
response_headers 是一个元素为二元祖的列表,如[('Content-Type','text/plain;charset=utf-8')]
exec_info 在错误处理的时候使用
start_response 应该在返回可迭代对象之前调用,因为他返回的是Response Header,返回的可迭代对象是Response Body。
先发头部,然后才是body
3 WSGIREF服务器端
服务器端程序需要调用符合上述定义的可调用对象,传入environ,start_response拿到返回可迭代对象,返回给客户端。
WSGIREF 是一个WSGI 的参考实现库
wsgiref.simple_server 实现了一个简单的WSGI HTTP服务器
相关参数如下
wsgiref.simple_server.make_server(
host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
)源码如下
def make_server( host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler): """Create a new WSGI server listening on `host` and `port` for `app`""" server = server_class((host, port), handler_class) server.set_app(app) return server
通过demo app 实现基本的展示页面
def demo_app(environ,start_response): from io import StringIO stdout = StringIO() print("Hello world!", file=stdout) print(file=stdout) h = sorted(environ.items()) for k,v in h: print(k,'=',repr(v), file=stdout) start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) return [stdout.getvalue().encode("utf-8")]
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_server,demo_appip='192.168.1.200'port=80server=make_server(ip,port,demo_app) # 实例化一个websever server.serve_forever() # 启动 server.server_close() # 关闭 server.shutdown() # 删除
GeneralRequest URL: http://192.168.1.200/Request Method: GETStatus Code: 200 OKRemote Address: 192.168.1.200:80Referrer Policy: no-referrer-when-downgradeResponse HeadersContent-Length: 3302 # 响应报文总长度 Content-Type: text/plain; charset=utf-8 # 要求文本显示 字符串是UTF-8Date: Sun, 08 Sep 2019 12:34:55 GMTServer: WSGIServer/0.2 CPython/3.6.4 #暴露服务器端信息Request HeadersAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 # 客户端浏览器可接受的类型和参数Accept-Encoding: gzip, deflate # 可接受压缩编码Accept-Language: zh-CN,zh;q=0.9,en;q=0.8Cache-Control: max-age=0Connection: keep-aliveCookie: csrftoken=Er5XLdEG211nWzgtJL1GFoxBgxFnnHbff2W7IiprrwTQbAAOzWWoHzihDrIxiK17Host: 192.168.1.200Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36 # 自己的user_agent
修改如下
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverip='192.168.1.200'port=80def app(environ,start_response): html='<h2>Hello World</h2>'.encode() start_response("200 OK", [('Content-Type','text/html; charset=utf-8')]) return [html]server=make_server(ip,port,app) # 实例化一个webseverserver.serve_forever() # 启动server.server_close() # 关闭server.shutdown() # 删除
4 webob 简介1 简介结果如下
环境变量数据很多,都是存储在字典中的,字典存取没有对象的属性使用方便,使用第三方webob,可以把环境数据的解析,封装成对象
pip install webob
2 webob.Request 对象
将环境参数解析并封装成request对象
GET方法,发送的数据是URL中的request handler中
request.get 就是一个字典MultiDict,里面就封装着查询字符串POST 方法,"提交"的数据是放在request body里面的,但是也同时可以使用Query String
request.POST可以获取request Body中的数据,也是个字典MultiDict不关心什么方法提交,只关心数据,可以使用request.params,它里面是所有提交数据的封装
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Responseip='192.168.1.200'port=80def app(environ,start_response): request=Request(environ) print ("params:",request.params) #获取传输的数据,query string 或者 POST 的body print ("method:",request.method) # 获取请求方法 print ("path:",request.path) #获取请求路径 print ("user_agent:",request.user_agent) #获取客户端信息 print ("get data:",request.GET) #获取get数据 print ("post data:",request.POST) # 获取post body数据 html='<h2>Hello World</h2>'.encode() start_response("200 OK", [('Content-Type','text/html; charset=utf-8')]) return [html]server=make_server(ip,port,app) # 实例化一个webseverserver.serve_forever() # 启动server.server_close() # 关闭server.shutdown() # 删除
请求URL: http://192.168.1.200/admin/?username=mysql&password=123456
3 MultiDict结果如下:
MultiDict 允许一个key存储好几个值
#!/usr/bin/poython3.6#conding:utf-8from webob.multidict import MultiDictmd=MultiDict()md.add(1,'aaaa')md.add(1,'cccc')md.add(1,'bbbb')md.add(2,'aaaa')md.add(2,'bbbb')md.add(2,'cccc')md.add(3,'aaaa')for x in md.items(): print (x)print ('get:',md.get(1)) # 此处默认取最后一个加入的print ('getall:',md.getall(1)) # 此处表示给据key取出所有print (md.getone(3)) #只能有一个值,有多个值使用这个返回有问题
4 webob.Response 对象结果如下
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Responseip='192.168.1.200'port=80def app(environ,start_response): res=Response() start_response(res.status,res.headerlist) # 返回可迭代对象 html='<h2>Hello World</h2>'.encode("utf-8") return [html]server=make_server(ip,port,app) # 实例化一个webseverserver.serve_forever() # 启动server.server_close() # 关闭server.shutdown() # 删除
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Responseip='192.168.1.200'port=80def app(environ,start_response): res=Response('<h2>Hello World</h2>') # 写法二 #res.body='<h2>Hello Python</h2>'.encode() #res.status_code=200 return res(environ,start_response)server=make_server(ip,port,app) # 实例化一个webseverserver.serve_forever() # 启动server.server_close() # 关闭server.shutdown() # 删除
5 dec.wsdify结果如下
此装饰器传入一个request的参数,则返回一个Response 的返回值,实现了一进一出的情况
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,decip='192.168.1.200'port=80@dec.wsgifydef app(request:Request)->Response: return Response('<h2>hello python </h2>'.encode())if __name__ == "__main__": server = make_server(ip, port, app) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
结果如下
2 路由功能的实现1 需求什么是路由,简单的说,就是路怎么走,就是按照不同的路径分发数据
URL 就是不同资源的路径,不同的路径应该对应不同的应用程序来处理,所以代码中需要增加对路径的处理和分析
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,decip='192.168.1.200'port=80@dec.wsgifydef app(request:Request)->Response: res=Response() if request.path=="/": res.body='<h2>hello World</h2>'.encode() return res elif request.path=="/python": res.body='<h2>hello Python</h2>'.encode() return res else: res.status_code=404 res.body='<h2>Not Found</h2>'.encode() return resif __name__ == "__main__": server = make_server(ip, port, app) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
3 将相关函数抽象到外边
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,decip='192.168.1.200'port=80def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return resdef showdefault(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return resdef show(request:Request): res=Response() res.status_code = 404 res.body = '<h2>Not Found</h2>'.encode() return res@dec.wsgifydef app(request:Request)->Response: if request.path=="/": return showdefault(request) elif request.path=="/python": return showpython(request) else: return show(request)if __name__ == "__main__": server = make_server(ip, port, app) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
4 通过字典存储函数名的方式来进行相关的匹配操作
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,decip='192.168.1.200'port=80def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return resdef showdefault(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return resdef show(request:Request): res=Response() res.status_code = 404 res.body = '<h2>Not Found</h2>'.encode() return resROUTABLE={ '/' :showdefault, '/python' :showpython}@dec.wsgifydef app(request:Request)->Response: return ROUTABLE.get(request.path,show)(request)if __name__ == "__main__": server = make_server(ip, port, app) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
5 配置注册函数功能
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,decip='192.168.1.200'port=80def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return resdef showdefault(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return resdef show(request:Request): res=Response() res.status_code = 404 res.body = '<h2>Not Found</h2>'.encode() return resROUTABLE={}def register(path,fn): ROUTABLE[path]=fnregister('/',showdefault)register('/python',showpython)@dec.wsgifydef app(request:Request)->Response: return ROUTABLE.get(request.path,show)(request)if __name__ == "__main__": server = make_server(ip, port, app) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
6 将其封装成类并进行相关的调用
思想: 将需要用户自己编写的东西放置在类的外边,其他的相关事件放置在类中
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,decip='192.168.1.200'port=80class Application: ROUTABLE={} def show(self,request:Request): res=Response() res.status_code = 404 res.body = '<h2>Not Found</h2>'.encode() return res @classmethod def register(cls,path,fn): cls.ROUTABLE[path]=fn @dec.wsgify def __call__(self,request: Request) -> Response: return self.ROUTABLE.get(request.path,self.show)(request)def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return resdef showdefault(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return resApplication.register('/',showdefault)Application.register('/python',showpython)if __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
7 使用默认的exc 对其进行相关的处理
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,dec,excip='192.168.1.200'port=80class Application: ROUTABLE={} @classmethod def register(cls,path,fn): cls.ROUTABLE[path]=fn @dec.wsgify def __call__(self,request: Request) -> Response: try: return self.ROUTABLE[request.path](request) except: raise exc.HTTPNotFound('访问的资源不存在')def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return resdef showdefault(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return resApplication.register('/',showdefault)Application.register('/python',showpython)if __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
8 修改注册函数为装饰器
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,dec,excip='192.168.1.200'port=80class Application: ROUTABLE={} @classmethod def register(cls,path): def _register(handle): cls.ROUTABLE[path]=handle return handle return _register @dec.wsgify def __call__(self,request: Request) -> Response: try: return self.ROUTABLE[request.path](request) except: raise exc.HTTPNotFound('访问的资源不存在')@Application.register('/')def showdefault(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return res@Application.register('/python')def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return resif __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
3 正则匹配路由功能到目前为止,一个框架的雏形基本完成了
application是WSGI中的应用程序。但是这个应用程序已经变成了一个路由程序,处理逻辑已移动到了应用程序外了,而这部分就是留给程序员的部分。
目前实现的路由匹配,路径匹配非常死板,使用正则表达式改造。导入re模块,注册时,存入的不再是路径字符串,而是pattern。
__call__方法中实现模式和传入路径的比较
compile 方法,编译正则表达式
match 方法,必须从头开始匹配, 只匹配一次
search方法,只匹配一次
fullmath 方法,要完全匹配
findall方法,从头开始找,找到所有匹配
字典的问题
如果使用字典,key如果是路径,不能保证其顺序,因为大多的匹配都是从严到宽,如果没有一定的顺序,则会导致问题正则表达式的预编译问题
第一次使用会影响到用户体验,所以还是要在注册的时候编译的。综上,改用列表,元素使用二元祖(编译后的正则对象,handler)
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,dec,excimport reip='192.168.1.200'port=80class Application: ROUTABLE=[] # 此处修改成列表的形式比较适合顺序匹配 @classmethod def register(cls,path): def _register(handle): cls.ROUTABLE.append((re.compile(path),handle)) return handle return _register @dec.wsgify def __call__(self,request: Request) -> Response: for pattern,hande in self.ROUTABLE: # 此处需要遍历 matcher=pattern.match(request.path) if matcher: # 此处若能匹配到,则为True,则可以进行下一步 return hande(request) raise exc.HTTPNotFound('访问资源不存在')@Application.register('^/$')def showdefault(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return res@Application.register('^/python$')def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return resif __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
4 Request Method过滤1 概念
2 方法和含义请求方法,一般来说,既是是同一个URL,因为请求方法不同,处理方式也是不同的
假设一个URL。GET方法希望返回网页内容,POST方法表示浏览器提交数据过来需要处理并存储进数据库,最终返回给客户端存储成功或者失败信息,
换句话说,需要根据请求方法和正则同时匹配才能决定执行什么样的处理函数
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,dec,excimport reip='192.168.1.200'port=80class Application: ROUTABLE=[] # 此处修改成列表的形式比较适合顺序匹配 @classmethod def register(cls,path,method): # 此处加入请求方法 def _register(handle): cls.ROUTABLE.append((re.compile(path),handle,method)) return handle return _register @classmethod # 通过构造方法来完成对函数的注册 def get(cls,path): return cls.register(path,'GET') @classmethod def post(cls,path): return cls.register(path,'POST') @classmethod def head(cls,path): return cls.register(path,'HEAD') @dec.wsgify def __call__(self,request: Request) -> Response: for pattern,hande,method in self.ROUTABLE: # 此处需要遍历 if request.method==method.upper(): # 如果请求方法和对应注册方法一致,则执行对应函数。 matcher=pattern.match(request.path) if matcher: # 此处若能匹配到,则为True,则可以进行下一步 return hande(request) raise exc.HTTPNotFound('访问资源不存在')@Application.get('^/$')def showdefault(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return res@Application.get('^/python$')def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return res@Application.post('^/python$')def showpython(request:Request): res=Response() res.body = '<h2>hello Python POST </h2>'.encode() return res@Application.post('^/')def showdefault(request:Request): res=Response() res.body = '<h2>hello World POST </h2>'.encode() return resif __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
一个URL 可以设定多种请求方法
要求:
1 如果什么方法都不写,相当于所有方法都支持
2 如果一个处理函数handler需要关联多个请求方法method,如下:
@Application.register('^/$',('GET','PUT','DELETE'))@Application.register('^/python$',('GET','PUT','DELETE'))@Application.register('^/$',())
思路:
将method变为methods,将位置参数变成可变参数即可代码如下
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,dec,excimport reip='192.168.1.200'port=80class Application: ROUTABLE=[] # 此处修改成列表的形式比较适合顺序匹配 @classmethod def register(cls,path,*methods): # 此处加入请求方法 def _register(handle): cls.ROUTABLE.append((re.compile(path),handle,methods)) return handle return _register @classmethod # 通过构造方法来完成对函数的注册 def get(cls,path): return cls.register(path,'GET','POST') @classmethod def post(cls,path): return cls.register(path,'POST') @classmethod def head(cls,path): return cls.register(path,'HEAD') @dec.wsgify def __call__(self,request: Request) -> Response: for pattern,hande,methods in self.ROUTABLE: # 此处需要遍历 if not methods or request.method in methods: matcher=pattern.match(request.path) if matcher: # 此处若能匹配到,则为True,则可以进行下一步 return hande(request) raise exc.HTTPNotFound('访问资源不存在')@Application.get('^/$')def showdefault(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return res@Application.get('^/python')def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return resif __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
5 路由功能的实现,分组捕获1 动态增加属性至 request中结果如下:
支持正则表达式的捕获,
在框架回调__call__时,拿到request.path和正则的模式匹配后,就可以提取分组了如何处理分组?
应用程序就是handler对应的不同的函数,其参数request是一样的,将捕获的数据动态增加到request对象中即可。用动态增加属性,为request增加args,kwargs属性,在handler中使用的时候,就可以直接熊属性中,将args,kwargs拿出来就可以直接使用了
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,dec,excimport reip='192.168.1.200'port=80class Application: ROUTABLE=[] # 此处修改成列表的形式比较适合顺序匹配 @classmethod def register(cls,path,*methods): # 此处加入请求方法 def _register(handle): cls.ROUTABLE.append((re.compile(path),handle,methods)) return handle return _register @classmethod # 通过构造方法来完成对函数的注册 def get(cls,path): return cls.register(path,'GET','POST') @classmethod def post(cls,path): return cls.register(path,'POST') @classmethod def head(cls,path): return cls.register(path,'HEAD') @dec.wsgify def __call__(self,request: Request) -> Response: for pattern,hande,methods in self.ROUTABLE: # 此处需要遍历 if not methods or request.method in methods: matcher=pattern.match(request.path) if matcher: # 此处若能匹配到,则为True,则可以进行下一步 request.args=matcher.group() # 此处获取元祖元素 request.kwargs=matcher.groupdict() # 此处获取字典元素进行处理 return hande(request) raise exc.HTTPNotFound('访问资源不存在')@Application.get('^/$')def showdefault(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return res@Application.get('^/python')def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return resif __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
2 路由分组
所谓的路由分组,就是按照前缀分别映射
需求
URL 为 /product/123456
需要将产品ID提取出来
分析
这个URL可以看做是一级分组路由,生产环境中可以使用了
如
product=Router('/product') #匹配前缀 /product
product.get('/(?P<id>\d+)') # 匹配路径为/product/123456
常见的一级目录
/admin #后台管理
/product 产品
这些目录都是/跟目录的下一级目录,暂时称为前缀prefix
如何建立prefix和URL 之间的隶属关系
一个prefix下可以有若干个URL。这些URL都是属于这个prefix中的
建立一个Router类,里面保存Prefix,同时保存URL和handler的关系以前。注册的方法都是application的类方法,也就是所有映射信息都保存在一个类属性中ROUTABLE中,但是现在要为不同的前缀就是不同的实例,因此所有注册方法,都是实例的方法,路由包实例自己管理
application 中现在只需要保存所有注册的Router对象就行了,__call__方法依然是回调入口,在其中遍历所有的Router,找到路径匹配的Router实例,让Router实例返回Response 对象即可
代码如下
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,dec,excimport reip='192.168.1.200'port=80class Router: def __init__(self,prefix:str): self.__prefix=prefix.rstrip('/\\') # 去除prefix及一级目录后面的\\和多余的/ self.__routertable=[] #此处用于保存handler,pattern,method的信息 print (self.__prefix) @property def prefix(self): return self.__prefix def register(self,path,*methods): # 此处用于注册二级目录对应的值 def _register(handle): self.__routertable.append((re.compile(path),handle,methods)) return handle return _register def get(self,path): return self.register(path,'GET') def post(self,path): return self.register(path,'POST') def head(self,path): return self.register(path,'HEAD') def match(self,request:Request): if not request.path.startswith(self.__prefix): #判断其是否URL一级目录匹配注册的prefix,若不匹配则返回为None return for pattern,hande,methods in self.__routertable: # 此处需要遍历 if not methods or request.method in methods: matcher=pattern.match(request.path.replace(self.prefix,"",1)) print ('prefix',self.prefix) print (request.path.replace(self.prefix,"",1)) if matcher: # 此处若能匹配到,则为True,则可以进行下一步 print(matcher) request.args=matcher.group() request.kwargs=matcher.groupdict() return hande(request)class Application: ROUTABLE=[] # 此处修改成列表的形式比较适合顺序匹配 @classmethod def register(cls,router:Router): cls.ROUTABLE.append(router) # 此处用于调用上述的函数,完成数据的初始化并传递相关的参数prefix参数 @dec.wsgify def __call__(self,request: Request) -> Response: for router in self.ROUTABLE: # 遍历router传输相关参数 response=router.match(request) # 此处返回为handler的函数值 if response: return response raise exc.HTTPNotFound('访问资源不存在')# 注册前缀index=Router('/')pyth=Router('/python')admin=Router('/admin')#将前缀加入对应列表中Application.register(pyth)Application.register(admin)Application.register(index)# 写handler@index.get('/(\w+)')def showpython(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return res@pyth.get('/(\d+)')def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return res@admin.get('/(\d+)')def showadmin(request:Request): res=Response() res.body = '<h2>hello admin</h2>'.encode() return resif __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
3 字典转属性类
1 基本代码通过此类,可使得kwargs这个字典,不使用[]访问元素,使用.号访问元素,如同属性一样访问
#!/usr/bin/poython3.6#conding:utf-8class DictObj: def __init__(self,d:dict): # 将属性中的元素添加到属性字典中去,可能会有冲突导致属性覆盖的问题 if not isinstance(d,dict): self.__dict__['_dict']={} # 此处不能是双下划綫,设置类属性字典 else: self.__dict__['_dict']=d #将字典加入到实例属性列表中 def __getattr__(self, item): #此处是通过点号访问的 try: return self._dict[item] # 通过d.x访问,若存在,则直接返回,若不存在,则抛出异常,此处的调用是两个步骤,第一个是self._dict 然后会调用各种方法最终形成死循环,如果专用的字典中有的话,则其中不会访问 except KeyError: #当其键不存在的时候 raise AttributeError('Attribute {} Not Found'.format(item)) def __setattr__(self, key, value): #此处是点号修改的 # 不允许设置属性,set表示未实现 raise NotImplementedd={ 'a':1, 'b':2, 'c':3}x=DictOrd(d)print (x.__dict__)print (DictOrd.__dict__)print (x.a)print (x.b)
2 修改代码如下结果如下
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,dec,excimport reip='192.168.1.200'port=80class DictObj: def __init__(self,d:dict): # 将属性中的元素添加到属性字典中去,可能会有冲突导致属性覆盖的问题 if not isinstance(d,dict): self.__dict__['_dict']={} # 此处不能是双下划綫,设置类属性字典 else: self.__dict__['_dict']=d #将字典加入到实例属性列表中 def __getattr__(self, item): #此处是通过点号访问的 try: return self._dict[item] # 通过d.x访问,若存在,则直接返回,若不存在,则抛出异常 except KeyError: #当其键不存在的时候 raise AttributeError('Attribute {} Not Found'.format(item)) def __setattr__(self, key, value): #此处是点号修改的 # 不允许设置属性,set表示未实现 raise NotImplementedclass Router: def __init__(self,prefix:str): self.__prefix=prefix.rstrip('/\\') # 去除prefix及一级目录后面的\\和多余的/ self.__routertable=[] #此处用于保存handler,pattern,method的信息 print (self.__prefix) @property def prefix(self): return self.__prefix def register(self,path,*methods): # 此处用于注册二级目录对应的值 def _register(handle): self.__routertable.append((re.compile(path),handle,methods)) return handle return _register def get(self,path): return self.register(path,'GET') def post(self,path): return self.register(path,'POST') def head(self,path): return self.register(path,'HEAD') def match(self,request:Request): if not request.path.startswith(self.__prefix): #判断其是否URL一级目录匹配注册的prefix,若不匹配则返回为None return for pattern,hande,methods in self.__routertable: # 此处需要遍历 if not methods or request.method in methods: matcher=pattern.match(request.path.replace(self.prefix,"",1)) print ('prefix',self.prefix) print (request.path.replace(self.prefix,"",1)) if matcher: # 此处若能匹配到,则为True,则可以进行下一步 print(matcher) request.args=matcher.group() request.kwargs=DictObj(matcher.groupdict()) # 此处通过修改后的字典,使得下面的访问可以直接使用.来进行访问而不是使用[key]的方式进行相关的访问操作 return hande(request)class Application: ROUTABLE=[] # 此处修改成列表的形式比较适合顺序匹配 @classmethod def register(cls,router:Router): cls.ROUTABLE.append(router) # 此处用于调用上述的函数,完成数据的初始化并传递相关的参数prefix参数 @dec.wsgify def __call__(self,request: Request) -> Response: for router in self.ROUTABLE: # 遍历router传输相关参数 response=router.match(request) # 此处返回为handler的函数值 if response: return response raise exc.HTTPNotFound('访问资源不存在')# 注册前缀#将前缀加入对应列表中index=Router('/')pyth=Router('/python')admin=Router('/admin')Application.register(pyth)Application.register(admin)Application.register(index)@index.get('/(\w+)')def showpython(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return res@pyth.get('/(\d+)')def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return res@admin.get('/(\d+)')def showadmin(request:Request): res=Response() res.body = '<h2>hello admin</h2>'.encode() return resif __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
6 正则表达式简化1 问题和分析
2 相关匹配规则问题
目前路由匹配使用正则表达式定义,不友好,很多用户不会使用正则表达式,能否简化分析
生产环境中。URL是规范的,不能随意书写,路径是有意义的,尤其是对restful风格,所以,要对URL规范
如 product/111102243454343 ,这就是一种规范,要求第一段是业务,第二段是ID。设计
路径规范化,如下定义
/student/{name:str}/{id:int}
类型设计。支持str,word,int,float,any类型
通过这种定义,可以让用户定义简化了,也规范了,背后的转换是编程者实现的
保存类型
#!/usr/local/bin/python3.6#coding:utf-8import res='/student/{name:abcded}/xxxx/{id:12345}'s1='/student/xxxx/{id:12345}'s2='/student/xxxx/12344's3='/student/{name:aaa}/xxxx/{id:1245}'TYPEPATTERNS= { 'str' :r'[^/]+', 'word' :r'\w+', 'int' :r'[+-]?\d+', 'float' : r'[+-]?\d+.\d+', 'any' : r'.+'}TYPECAST= { 'str' :str, 'word': str, 'int' :int, 'float' :float, 'any' :str}pattern=re.compile('/({[^{}:]+:?[^{}:]*})') # 此处是提取相关用户信息的情况,此处匹配到的只是一级目录的相关信息def transfrom(kv:str): name,_,type=kv.strip('/{}').partition(':') # 此处用于替换操做,此处返回一个列表,通过参数解构来收集,后面是找到第一个后进行分割操做 return '/(?P<{}>{})'.format(name,TYPEPATTERNS.get(type,'\w+')),name,TYPECAST.get(type,str) # 此处的format只是构造name和对应正则匹配的字典,此处返回的是一个三元组def parse(src:str): start=0 res='' translator= {} while True: matcher=pattern.search(src,start) # start表示偏移量 if matcher: res+=matcher.string[start:matcher.start()] #对匹配到的字符串进行切割处理 tmp=transfrom(matcher.string[matcher.start():matcher.end()]) # 此处返回的是下一次匹配的结果的集合,此出返回一个三元组 res+=tmp[0] # 此处保存的是名称和正则的元组 translator[tmp[1]]=tmp[2] # 此处保存的是名称和类型的字典 start=matcher.end() # 此处再次匹配,则需要进行初始化继续匹配的操做 else: # 若不能匹配,则返回 break if res: # 若存在,则返回 return res,translator else: # 若不存在,也返回 return res,translatorprint (parse(s))print (parse(s1))print (parse(s2))print (parse(s3))
结果如下
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,dec,excimport reip='192.168.1.200'port=80class DictObj: def __init__(self,d:dict): # 将属性中的元素添加到属性字典中去,可能会有冲突导致属性覆盖的问题 if not isinstance(d,dict): self.__dict__['_dict']={} # 此处不能是双下划綫,设置类属性字典 else: self.__dict__['_dict']=d #将字典加入到实例属性列表中 def __getattr__(self, item): #此处是通过点号访问的 try: return self._dict[item] # 通过d.x访问,若存在,则直接返回,若不存在,则抛出异常 except KeyError: #当其键不存在的时候 raise AttributeError('Attribute {} Not Found'.format(item)) def __setattr__(self, key, value): #此处是点号修改的 # 不允许设置属性,set表示未实现 raise NotImplementedclass Router: def __init__(self,prefix:str): self.__prefix=prefix.rstrip('/\\') # 去除prefix及一级目录后面的\\和多余的/ self.__routertable=[] #此处用于保存handler,pattern,method的信息 TYPEPATTERNS = { 'str': r'[^/]+', 'word': r'\w+', 'int': r'[+-]?\d+', 'float': r'[+-]?\d+.\d+', 'any': r'.+' } TYPECAST = { 'str': str, 'word': str, 'int': int, 'float': float, 'any': str } pattern = re.compile('/({[^{}:]+:?[^{}:]*})') # 此处是提取相关用户信息的情况,此处匹配到的只是一级目录的相关信息 def transfrom(self,kv: str): name, _, type = kv.strip('/{}').partition(':') # 此处用于替换操做,此处返回一个列表,通过参数解构来收集,后面是找到第一个后进行分割操做 return '/(?P<{}>{})'.format(name, self.TYPEPATTERNS.get(type, '\w+')), name, self.TYPECAST.get(type, str) # 此处的format只是构造name和对应正则匹配的字典,此处返回的是一个三元 def parse(self,src: str): start = 0 res = '' translator = {} while True: matcher = self.pattern.search(src, start) # start表示偏移量 if matcher: res += matcher.string[start:matcher.start()] # 对匹配到的字符串进行切割处理 tmp = self.transfrom(matcher.string[matcher.start():matcher.end()]) # 此处返回的是下一次匹配的结果的集合,此出返回一个三元组 res += tmp[0] # 此处保存的是名称和正则的元组 translator[tmp[1]] = tmp[2] # 此处保存的是名称和类型的字典 start = matcher.end() # 此处再次匹配,则需要进行初始化继续匹配的操做 else: # 若不能匹配,则返回 break if res: # 若存在,则返回 return res, translator # res中保存URL,translator中保存名称和类型的对应关系 else: # 若不存在,也返回 return res, translator @property def prefix(self): return self.__prefix def register(self,rule,*methods): # 此处用于注册二级目录对应的值 def _register(handle): pattern,translator=self.parse(rule) #此处通过对应的规则来处理相关配置,pattern中包含的是实际的URL路径,translator 中包含分组名称和对应类型的匹配 self.__routertable.append((re.compile(pattern),translator,handle,methods)) return handle return _register def get(self,path): return self.register(path,'GET') def post(self,path): return self.register(path,'POST') def head(self,path): return self.register(path,'HEAD') def match(self,request:Request): if not request.path.startswith(self.__prefix): #判断其是否URL一级目录匹配注册的prefix,若不匹配则返回为None return for pattern,translator,hande,methods in self.__routertable: # 此处需要遍历 if not methods or request.method in methods: matcher=pattern.match(request.path.replace(self.prefix,"",1)) if matcher: # 此处若能匹配到,则为True,则可以进行下一步 request.args=matcher.group() request.kwargs=DictObj(matcher.groupdict()) newdict={} for k,v in matcher.groupdict().items(): # 此处返回分组名称和匹配值的字典,K是分组名称,V是匹配的结果 newdict[k]=translator[k](v) #分组匹配结果,通过分组的名称获取对应的类型进行对其值进行操作并保存 request.vars=DictObj(newdict) return hande(request)class Application: ROUTABLE=[] # 此处修改成列表的形式比较适合顺序匹配 @classmethod def register(cls,router:Router): cls.ROUTABLE.append(router) # 此处用于调用上述的函数,完成数据的初始化并传递相关的参数prefix参数 @dec.wsgify def __call__(self,request: Request) -> Response: for router in self.ROUTABLE: # 遍历router传输相关参数 response=router.match(request) # 此处返回为handler的函数值 if response: return response raise exc.HTTPNotFound('访问资源不存在')# 注册前缀#将前缀加入对应列表中index=Router('/')pyth=Router('/python')admin=Router('/admin')Application.register(pyth)Application.register(admin)Application.register(index)@index.get('/\w+')def showpython(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return res@pyth.get('/\d+')def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return res@admin.get('/\d+')def showadmin(request:Request): res=Response() res.body = '<h2>hello admin</h2>'.encode() return resif __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
5 小结
7 拦截器1 概念处理流程
客户端发起请求,被容器调度给Application的call
Application 中便利所有注册的router,router通过match来判断是否是自己处理的,通过查看request的请求来匹配注册前缀,若符合条件,则匹配对应的请求方法,若方法符合,则匹配对应的URL二级目录,并返回对应的函数,handler处理后,返回response,applicationde拿着这个response数据,返回给原始的wsgi。
拦截器,就是要在请求处理环节的某处加入处理,有可能是中断手续的处理
根据拦截点不同,分为:
1 请求拦截
2 响应拦截
根据影响面分为:
1 全局拦截
在application中拦截
2 局部拦截
在Router中拦截
相关图形
2 加入拦截器功能的方式前面的是application层面的拦截,及全局拦截,。后面是TOUTER层面的拦截,及局部拦截,
拦截器可以是多个,多个拦截器是顺序的
数据response前执行的的命名为preinterceptor ,之后的命名为postinterceptor。
3 被拦截函数fn的设计,透明1 application 和Router 类直接加入
把拦截器的相关方法,属性分别调价到相关的类中2 Mixin
Application 和Router类都需要这个拦截器功能,这两个类没什么关系,可以使用Mixin方式,将属性,方法组合起来
但是,application类拦截器适合使用第二种方式,DNA是Router的拦截器每个实例都是不同的,所以使用第一种方式实现
当出现多继承时,Mixin中MRO规则会直接使用第一个,而忽略其他的__init__方法。
拦截器的函数是相对独立的,其相当于是相对透明的,用一个的输出和N的输出都应该能够和handler进行处理
引入app,是为了以后从application上获取一些全局信息,其application的实例资源。
来的输入和输出都是request
def fn(app,request:Request)->Request: pass
4 上下文支持1 概念去的输入和输出都是response
def fn(app,request:Request,response:Response)-> Response:pass
为了把一些应数据,配置数据,数据库连接提供给全局共享数据提供所有对象使用,增加一个字典,存储共享数据。将环境变量传递下去。
为了方便访问,提供字典的属性化访问的类,因为这个字典是可写的,和前面的类不一样。
2 存储共享数据基本实例application最多的应该做的是单实例模式,及就是一个实例的处理模式,若果是要用多实例,则需要使用信号量或其他进行处理
class Context(dict): #继承内部类,使得类能够提供一种能力能够直接的属性访问方式,读取配置文件的能力 def __getattr__(self, item): # 通过.的方式进行访问 try: return self[item] # 自己的字典访问方式给请求端 except KeyError: # 属性访问的方式导致的问题 raise ArithmeticError('Attribe {} Not Found'.format(item)) def __setattr__(self, key, value): self[key]=value #处理修改和添加问题
3 Router实例的上下文属性支持
Router没一个实例中增加上下文属性,实例自己使用
但是Router实例如何使用全局上下文
使用新的处理方法,每一个Router实例的上下文字典内部关联一个全局字典的引用,如果自己的字典找不到,就去全局寻找
那Router实例什么时候关联全局字典比较合适
在路由注册的时候即可基本代码如下
class NestedContext(Context): #继承上述属性,什么逻辑不一样就覆盖那个 def __init__(self,globalcontext:Context=None): super().__init__() self.relate(globalcontext) def relate(self,globalcontext:Context=None): self.globalcontext=globalcontext def __getattr__(self, item): if item in self.keys(): return self[item] return self.globalcontext[item]
5 全局代码如下
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,dec,excimport reip='192.168.1.200'port=80class DictObj: def __init__(self,d:dict): # 将属性中的元素添加到属性字典中去,可能会有冲突导致属性覆盖的问题 if not isinstance(d,dict): self.__dict__['_dict']={} # 此处不能是双下划綫,设置类属性字典 else: self.__dict__['_dict']=d #将字典加入到实例属性列表中 def __getattr__(self, item): #此处是通过点号访问的 try: return self._dict[item] # 通过d.x访问,若存在,则直接返回,若不存在,则抛出异常 except KeyError: #当其键不存在的时候 raise AttributeError('Attribute {} Not Found'.format(item)) def __setattr__(self, key, value): #此处是点号修改的 # 不允许设置属性,set表示未实现 raise NotImplementedclass Context(dict): # 用于存储共享数据,app使用 def __getattr__(self, item): try: return self[item] except KeyError: raise ArithmeticError('Attribe {} Not Found'.format(item)) def __setattr__(self, key, value): self[key]=value####################上述两种字典的不同实现方式处理###########################class NestedContext(Context): #继承上述属性,什么逻辑不一样就覆盖那个。Router实例使用 def __init__(self,globalcontext:Context=None): super().__init__() self.relate(globalcontext) def relate(self,globalcontext:Context=None): self.globalcontext=globalcontext def __getattr__(self, item): if item in self.keys(): return self[item] return self.globalcontext[item]class Router: def __init__(self,prefix:str): self.__prefix=prefix.rstrip('/\\') # 去除prefix及一级目录后面的\\和多余的/ self.__routertable=[] #此处用于保存handler,pattern,method的信息 self.ctx=NestedContext() # 未绑定全局的上下文,在注册的时候进行处理 #实例自己使用的拦截器。在match处进行拦截 self.preinterceptor=[] self.postinterceptor=[] # 装饰器需要有返回值 def reg_preinterceptor(self, fn): # fn前半段两个参数,后半段三个参数,装饰器需要返回值 self.preinterceptor.append(fn) return fn def reg_postinterceptor(self, fn): self.postinterceptor.append(fn) return fn # TYPEPATTERNS = { 'str': r'[^/]+', 'word': r'\w+', 'int': r'[+-]?\d+', 'float': r'[+-]?\d+.\d+', 'any': r'.+' } TYPECAST = { 'str': str, 'word': str, 'int': int, 'float': float, 'any': str } pattern = re.compile('/({[^{}:]+:?[^{}:]*})') # 此处是提取相关用户信息的情况,此处匹配到的只是一级目录的相关信息 def transfrom(self,kv: str): name, _, type = kv.strip('/{}').partition(':') # 此处用于替换操做,此处返回一个列表,通过参数解构来收集,后面是找到第一个后进行分割操做 return '/(?P<{}>{})'.format(name, self.TYPEPATTERNS.get(type, '\w+')), name, self.TYPECAST.get(type, str) # 此处的format只是构造name和对应正则匹配的字典,此处返回的是一个三元 def parse(self,src: str): start = 0 res = '' translator = {} while True: matcher = self.pattern.search(src, start) # start表示偏移量 if matcher: res += matcher.string[start:matcher.start()] # 对匹配到的字符串进行切割处理 tmp = self.transfrom(matcher.string[matcher.start():matcher.end()]) # 此处返回的是下一次匹配的结果的集合,此出返回一个三元组 res += tmp[0] # 此处保存的是名称和正则的元组 translator[tmp[1]] = tmp[2] # 此处保存的是名称和类型的字典 start = matcher.end() # 此处再次匹配,则需要进行初始化继续匹配的操做 else: # 若不能匹配,则返回 break if res: # 若存在,则返回 return res, translator # res中保存URL,translator中保存名称和类型的对应关系 else: # 若不存在,也返回 return res, translator @property def prefix(self): return self.__prefix def register(self,rule,*methods): # 此处用于注册二级目录对应的值 def _register(handle): pattern,translator=self.parse(rule) #此处通过对应的规则来处理相关配置,pattern中包含的是实际的URL路径,translator 中包含分组名称和对应类型的匹配 self.__routertable.append((re.compile(pattern),translator,handle,methods)) return handle return _register def get(self,path): return self.register(path,'GET') def post(self,path): return self.register(path,'POST') def head(self,path): return self.register(path,'HEAD') def match(self,request:Request): if not request.path.startswith(self.__prefix): #判断其是否URL一级目录匹配注册的prefix,若不匹配则返回为None return for fn in self.preinterceptor: # 拦截器处理 request=fn(self.ctx,request) for pattern,translator,hande,methods in self.__routertable: # 此处需要遍历 if not methods or request.method in methods: matcher=pattern.match(request.path.replace(self.prefix,"",1)) if matcher: # 此处若能匹配到,则为True,则可以进行下一步 request.args=matcher.group() request.kwargs=DictObj(matcher.groupdict()) newdict={} for k,v in matcher.groupdict().items(): # 此处返回分组名称和匹配值的字典,K是分组名称,V是匹配的结果 newdict[k]=translator[k](v) #分组匹配结果,通过分组的名称获取对应的类型进行对其值进行操作并保存 request.vars=DictObj(newdict) response=hande(self.ctx,request) #优先使用自己的属性 for fn in self.postinterceptor: response=fn(self.ctx,request,response) return responseclass Application: ROUTABLE=[] # 此处修改成列表的形式比较适合顺序匹配 ctx=Context() #实例的拦截器 PREINTERCEPTOR=[] POSTINTERCEPTOR=[] # 拦截器的注册 @classmethod def reg_preinterceptor(cls,fn): # fn前半段两个参数,后半段三个参数 cls.PREINTERCEPTOR.append(fn) return fn @classmethod def reg_postinterceptor(cls,fn): cls.POSTINTERCEPTOR.append(fn) return fn # 函数需要返回,其本身并没有变动 def __init__(self,**kwargs): self.ctx.app=self for k,v in kwargs.items(): self.ctx[k]=v #添加注册功能 @classmethod def register(cls,router:Router): router.ctx.relate(cls.ctx) #将上述的CTX添加进来,用于属性的访问控制及上述的NestedContext,将全局的上下文绑定给每一个router实例 # 其在router自己初始化时就自己创建 router.ctx.router=router #在自己的字典中中引用自己 cls.ROUTABLE.append(router) # 此处用于调用上述的函数,完成数据的初始化并传递相关的参数prefix参数 @dec.wsgify def __call__(self,request: Request) -> Response: for fn in self.PREINTERCEPTOR: # 注册函数, request=fn(self.ctx,request) #第一个是全局的,第二个是自己的,定义的,需要request不变透明化 #fn(self.ctx,request) 此处此种写法容易引起别人的误会 for router in self.ROUTABLE: # 遍历router传输相关参数 response=router.match(request) # 此处返回为handler的函数值 if response: #返回的函数进行处理 for fn in self.POSTINTERCEPTOR: # 此处处理response相关的方法 response=fn(self.ctx.request,response) return response raise exc.HTTPNotFound('访问资源不存在')# 注册前缀#将前缀加入对应列表中index=Router('/')pyth=Router('/python')admin=Router('/admin')Application.register(pyth)Application.register(admin)Application.register(index)#添加拦截器@Application.reg_preinterceptor #全局起始拦截器def showhandler(ctx:Context,request:Request)-> Request: print (request.path) print (request.user_agent) return request # 返回为request,只有request@pyth.reg_preinterceptor # Router 层面的拦截器 def showprefix(ctx:NestedContext,request:Request)->Request: print ('~~~~~~~~~~~~prefix = {}'.format(ctx.router.prefix)) # 此处是打印自己的前缀 return request@index.get('/\w+')def showpython(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return res@pyth.get('/\d+')def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return res@admin.get('/\d+')def showadmin(request:Request): res=Response() res.body = '<h2>hello admin</h2>'.encode() return resif __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
8 可扩展功能
作为一个框架,更多的功能应该是从外部加入
1 不可能些的非常完善
2 非必要的都应该动态加入
所以,提供一个扩展接口非常重要
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,dec,excimport reip='192.168.1.200'port=80class DictObj: def __init__(self,d:dict): # 将属性中的元素添加到属性字典中去,可能会有冲突导致属性覆盖的问题 if not isinstance(d,dict): self.__dict__['_dict']={} # 此处不能是双下划綫,设置类属性字典 else: self.__dict__['_dict']=d #将字典加入到实例属性列表中 def __getattr__(self, item): #此处是通过点号访问的 try: return self._dict[item] # 通过d.x访问,若存在,则直接返回,若不存在,则抛出异常 except KeyError: #当其键不存在的时候 raise AttributeError('Attribute {} Not Found'.format(item)) def __setattr__(self, key, value): #此处是点号修改的 # 不允许设置属性,set表示未实现 raise NotImplementedclass Context(dict): # 用于存储共享数据,app使用 def __getattr__(self, item): try: return self[item] except KeyError: raise ArithmeticError('Attribe {} Not Found'.format(item)) def __setattr__(self, key, value): self[key]=value####################上述两种字典的不同实现方式处理###########################class NestedContext(Context): #继承上述属性,什么逻辑不一样就覆盖那个。Router实例使用 def __init__(self,globalcontext:Context=None): super().__init__() self.relate(globalcontext) def relate(self,globalcontext:Context=None): self.globalcontext=globalcontext def __getattr__(self, item): if item in self.keys(): return self[item] return self.globalcontext[item]class Router: def __init__(self,prefix:str): self.__prefix=prefix.rstrip('/\\') # 去除prefix及一级目录后面的\\和多余的/ self.__routertable=[] #此处用于保存handler,pattern,method的信息 self.ctx=NestedContext() # 未绑定全局的上下文,在注册的时候进行处理 #实例自己使用的拦截器。在match处进行拦截 self.preinterceptor=[] self.postinterceptor=[] # 装饰器需要有返回值 def reg_preinterceptor(self, fn): # fn前半段两个参数,后半段三个参数,装饰器需要返回值 self.preinterceptor.append(fn) return fn def reg_postinterceptor(self, fn): self.postinterceptor.append(fn) return fn # TYPEPATTERNS = { 'str': r'[^/]+', 'word': r'\w+', 'int': r'[+-]?\d+', 'float': r'[+-]?\d+.\d+', 'any': r'.+' } TYPECAST = { 'str': str, 'word': str, 'int': int, 'float': float, 'any': str } pattern = re.compile('/({[^{}:]+:?[^{}:]*})') # 此处是提取相关用户信息的情况,此处匹配到的只是一级目录的相关信息 def transfrom(self,kv: str): name, _, type = kv.strip('/{}').partition(':') # 此处用于替换操做,此处返回一个列表,通过参数解构来收集,后面是找到第一个后进行分割操做 return '/(?P<{}>{})'.format(name, self.TYPEPATTERNS.get(type, '\w+')), name, self.TYPECAST.get(type, str) # 此处的format只是构造name和对应正则匹配的字典,此处返回的是一个三元 def parse(self,src: str): start = 0 res = '' translator = {} while True: matcher = self.pattern.search(src, start) # start表示偏移量 if matcher: res += matcher.string[start:matcher.start()] # 对匹配到的字符串进行切割处理 tmp = self.transfrom(matcher.string[matcher.start():matcher.end()]) # 此处返回的是下一次匹配的结果的集合,此出返回一个三元组 res += tmp[0] # 此处保存的是名称和正则的元组 translator[tmp[1]] = tmp[2] # 此处保存的是名称和类型的字典 start = matcher.end() # 此处再次匹配,则需要进行初始化继续匹配的操做 else: # 若不能匹配,则返回 break if res: # 若存在,则返回 return res, translator # res中保存URL,translator中保存名称和类型的对应关系 else: # 若不存在,也返回 return res, translator @property def prefix(self): return self.__prefix def register(self,rule,*methods): # 此处用于注册二级目录对应的值 def _register(handle): pattern,translator=self.parse(rule) #此处通过对应的规则来处理相关配置,pattern中包含的是实际的URL路径,translator 中包含分组名称和对应类型的匹配 self.__routertable.append((re.compile(pattern),translator,handle,methods)) return handle return _register def get(self,path): return self.register(path,'GET') def post(self,path): return self.register(path,'POST') def head(self,path): return self.register(path,'HEAD') def match(self,request:Request): if not request.path.startswith(self.__prefix): #判断其是否URL一级目录匹配注册的prefix,若不匹配则返回为None return for fn in self.preinterceptor: # 拦截器处理 request=fn(self.ctx,request) for pattern,translator,hande,methods in self.__routertable: # 此处需要遍历 if not methods or request.method in methods: matcher=pattern.match(request.path.replace(self.prefix,"",1)) if matcher: # 此处若能匹配到,则为True,则可以进行下一步 request.args=matcher.group() request.kwargs=DictObj(matcher.groupdict()) newdict={} for k,v in matcher.groupdict().items(): # 此处返回分组名称和匹配值的字典,K是分组名称,V是匹配的结果 newdict[k]=translator[k](v) #分组匹配结果,通过分组的名称获取对应的类型进行对其值进行操作并保存 request.vars=DictObj(newdict) response=hande(self.ctx,request) #优先使用自己的属性 for fn in self.postinterceptor: response=fn(self.ctx,request,response) return responseclass Application: ROUTABLE=[] # 此处修改成列表的形式比较适合顺序匹配 ctx=Context() #实例的拦截器 PREINTERCEPTOR=[] POSTINTERCEPTOR=[] @classmethod # 增加扩展功能模块,通过名字的方式加载进来 def extend(cls,name,ext): cls.ctx[name]=ext # 拦截器的注册 @classmethod def reg_preinterceptor(cls,fn): # fn前半段两个参数,后半段三个参数 cls.PREINTERCEPTOR.append(fn) return fn @classmethod def reg_postinterceptor(cls,fn): cls.POSTINTERCEPTOR.append(fn) return fn # 函数需要返回,其本身并没有变动 def __init__(self,**kwargs): self.ctx.app=self for k,v in kwargs.items(): self.ctx[k]=v #添加注册功能 @classmethod def register(cls,router:Router): router.ctx.relate(cls.ctx) #将上述的CTX添加进来,用于属性的访问控制及上述的NestedContext,将全局的上下文绑定给每一个router实例 # 其在router自己初始化时就自己创建 router.ctx.router=router #在自己的字典中中引用自己 cls.ROUTABLE.append(router) # 此处用于调用上述的函数,完成数据的初始化并传递相关的参数prefix参数 @dec.wsgify def __call__(self,request: Request) -> Response: for fn in self.PREINTERCEPTOR: # 注册函数, request=fn(self.ctx,request) #第一个是全局的,第二个是自己的,定义的,需要request不变透明化 #fn(self.ctx,request) 此处此种写法容易引起别人的误会 for router in self.ROUTABLE: # 遍历router传输相关参数 response=router.match(request) # 此处返回为handler的函数值 if response: #返回的函数进行处理 for fn in self.POSTINTERCEPTOR: # 此处处理response相关的方法 response=fn(self.ctx.request,response) return response raise exc.HTTPNotFound('访问资源不存在')# 注册前缀#将前缀加入对应列表中index=Router('/')pyth=Router('/python')admin=Router('/admin')Application.register(pyth)Application.register(admin)Application.register(index)#添加拦截器@Application.reg_preinterceptor #全局起始拦截器def showhandler(ctx:Context,request:Request)-> Request: print (request.path) print (request.user_agent) return request # 返回为request,只有request@pyth.reg_preinterceptor # Router 层面的拦截器def showprefix(ctx:NestedContext,request:Request)->Request: print ('~~~~~~~~~~~~prefix = {}'.format(ctx.router.prefix)) # 此处是打印自己的前缀 return request@index.get('/\w+')def showpython(request:Request): res=Response() res.body = '<h2>hello World</h2>'.encode() return res@pyth.get('/\d+')def showpython(request:Request): res=Response() res.body = '<h2>hello Python</h2>'.encode() return res@admin.get('/\d+')def showadmin(request:Request): res=Response() res.body = '<h2>hello admin</h2>'.encode() return resif __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
9 模块化
在pycharm中创建一个包,包名为testweb
在init.py文件中,修改Application为TestWeb
通过此种方式暴露类
class TestWeb: # 类属性方法把类暴露出去 Router=_Router Request=Request Response=Response NestedContext=NestedContext Context=Context
以供别人调用
外层新建app,将需要调用的都创建在app中实现,及就是使用此模块的人
目录
app.py 中实现的代码
from wsgiref.simple_server import make_serverfrom testweb import TestWeb# 注册前缀#将前缀加入对应列表中index=TestWeb.Router('/')pyth=TestWeb.Router('/python')admin=TestWeb.Router('/admin')TestWeb.register(pyth)TestWeb.register(admin)TestWeb.register(index)#添加拦截器@TestWeb.reg_preinterceptor #全局起始拦截器def showhandler(ctx:TestWeb.Context,request:TestWeb.Request)-> TestWeb.Request: print (request.path) print (request.user_agent) return request # 返回为request,只有request@pyth.reg_preinterceptor # Router 层面的拦截器def showprefix(ctx:TestWeb.NestedContext,request:TestWeb.Request)->TestWeb.Request: print ('~~~~~~~~~~~~prefix = {}'.format(ctx.router.prefix)) # 此处是打印自己的前缀 return request@index.get('/\w+')def showpython(request:TestWeb.Request): res=TestWeb.Response() res.body = '<h2>hello World</h2>'.encode() return res@pyth.get('/\d+')def showpython(request:TestWeb.Request): res=TestWeb.Response() res.body = '<h2>hello Python</h2>'.encode() return res@admin.get('/\d+')def showadmin(request:TestWeb.Request): res=TestWeb.Response() res.body = '<h2>hello admin</h2>'.encode() return resif __name__ == "__main__": ip = '192.168.1.200' port = 80 server = make_server(ip, port, TestWeb()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
testweb中_init_.py中的内容
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,dec,excimport reclass DictObj: def __init__(self,d:dict): # 将属性中的元素添加到属性字典中去,可能会有冲突导致属性覆盖的问题 if not isinstance(d,dict): self.__dict__['_dict']={} # 此处不能是双下划綫,设置类属性字典 else: self.__dict__['_dict']=d #将字典加入到实例属性列表中 def __getattr__(self, item): #此处是通过点号访问的 try: return self._dict[item] # 通过d.x访问,若存在,则直接返回,若不存在,则抛出异常 except KeyError: #当其键不存在的时候 raise AttributeError('Attribute {} Not Found'.format(item)) def __setattr__(self, key, value): #此处是点号修改的 # 不允许设置属性,set表示未实现 raise NotImplementedclass Context(dict): # 用于存储共享数据,app使用 def __getattr__(self, item): try: return self[item] except KeyError: raise ArithmeticError('Attribe {} Not Found'.format(item)) def __setattr__(self, key, value): self[key]=value####################上述两种字典的不同实现方式处理###########################class NestedContext(Context): #继承上述属性,什么逻辑不一样就覆盖那个。Router实例使用 def __init__(self,globalcontext:Context=None): super().__init__() self.relate(globalcontext) def relate(self,globalcontext:Context=None): self.globalcontext=globalcontext def __getattr__(self, item): if item in self.keys(): return self[item] return self.globalcontext[item]class _Router: def __init__(self,prefix:str): self.__prefix=prefix.rstrip('/\\') # 去除prefix及一级目录后面的\\和多余的/ self.__routertable=[] #此处用于保存handler,pattern,method的信息 self.ctx=NestedContext() # 未绑定全局的上下文,在注册的时候进行处理 #实例自己使用的拦截器。在match处进行拦截 self.preinterceptor=[] self.postinterceptor=[] # 装饰器需要有返回值 def reg_preinterceptor(self, fn): # fn前半段两个参数,后半段三个参数,装饰器需要返回值 self.preinterceptor.append(fn) return fn def reg_postinterceptor(self, fn): self.postinterceptor.append(fn) return fn # TYPEPATTERNS = { 'str': r'[^/]+', 'word': r'\w+', 'int': r'[+-]?\d+', 'float': r'[+-]?\d+.\d+', 'any': r'.+' } TYPECAST = { 'str': str, 'word': str, 'int': int, 'float': float, 'any': str } pattern = re.compile('/({[^{}:]+:?[^{}:]*})') # 此处是提取相关用户信息的情况,此处匹配到的只是一级目录的相关信息 def transfrom(self,kv: str): name, _, type = kv.strip('/{}').partition(':') # 此处用于替换操做,此处返回一个列表,通过参数解构来收集,后面是找到第一个后进行分割操做 return '/(?P<{}>{})'.format(name, self.TYPEPATTERNS.get(type, '\w+')), name, self.TYPECAST.get(type, str) # 此处的format只是构造name和对应正则匹配的字典,此处返回的是一个三元 def parse(self,src: str): start = 0 res = '' translator = {} while True: matcher = self.pattern.search(src, start) # start表示偏移量 if matcher: res += matcher.string[start:matcher.start()] # 对匹配到的字符串进行切割处理 tmp = self.transfrom(matcher.string[matcher.start():matcher.end()]) # 此处返回的是下一次匹配的结果的集合,此出返回一个三元组 res += tmp[0] # 此处保存的是名称和正则的元组 translator[tmp[1]] = tmp[2] # 此处保存的是名称和类型的字典 start = matcher.end() # 此处再次匹配,则需要进行初始化继续匹配的操做 else: # 若不能匹配,则返回 break if res: # 若存在,则返回 return res, translator # res中保存URL,translator中保存名称和类型的对应关系 else: # 若不存在,也返回 return res, translator @property def prefix(self): return self.__prefix def register(self,rule,*methods): # 此处用于注册二级目录对应的值 def _register(handle): pattern,translator=self.parse(rule) #此处通过对应的规则来处理相关配置,pattern中包含的是实际的URL路径,translator 中包含分组名称和对应类型的匹配 self.__routertable.append((re.compile(pattern),translator,handle,methods)) return handle return _register def get(self,path): return self.register(path,'GET') def post(self,path): return self.register(path,'POST') def head(self,path): return self.register(path,'HEAD') def match(self,request:Request): if not request.path.startswith(self.__prefix): #判断其是否URL一级目录匹配注册的prefix,若不匹配则返回为None return for fn in self.preinterceptor: # 拦截器处理 request=fn(self.ctx,request) for pattern,translator,hande,methods in self.__routertable: # 此处需要遍历 if not methods or request.method in methods: matcher=pattern.match(request.path.replace(self.prefix,"",1)) if matcher: # 此处若能匹配到,则为True,则可以进行下一步 request.args=matcher.group() request.kwargs=DictObj(matcher.groupdict()) newdict={} for k,v in matcher.groupdict().items(): # 此处返回分组名称和匹配值的字典,K是分组名称,V是匹配的结果 newdict[k]=translator[k](v) #分组匹配结果,通过分组的名称获取对应的类型进行对其值进行操作并保存 request.vars=DictObj(newdict) response=hande(self.ctx,request) #优先使用自己的属性 for fn in self.postinterceptor: response=fn(self.ctx,request,response) return responseclass TestWeb: # 类属性方法把类暴露出去 Router=_Router Request=Request Response=Response NestedContext=NestedContext Context=Context ROUTABLE=[] # 此处修改成列表的形式比较适合顺序匹配 ctx=Context() #实例的拦截器 PREINTERCEPTOR=[] POSTINTERCEPTOR=[] @classmethod # 增加扩展功能模块 def extend(cls,name,ext): cls.ctx[name]=ext # 拦截器的注册 @classmethod def reg_preinterceptor(cls,fn): # fn前半段两个参数,后半段三个参数 cls.PREINTERCEPTOR.append(fn) return fn @classmethod def reg_postinterceptor(cls,fn): cls.POSTINTERCEPTOR.append(fn) return fn # 函数需要返回,其本身并没有变动 def __init__(self,**kwargs): self.ctx.app=self for k,v in kwargs.items(): self.ctx[k]=v #添加注册功能 @classmethod def register(cls,router:Router): router.ctx.relate(cls.ctx) #将上述的CTX添加进来,用于属性的访问控制及上述的NestedContext,将全局的上下文绑定给每一个router实例 # 其在router自己初始化时就自己创建 router.ctx.router=router #在自己的字典中中引用自己 cls.ROUTABLE.append(router) # 此处用于调用上述的函数,完成数据的初始化并传递相关的参数prefix参数 @dec.wsgify def __call__(self,request: Request) -> Response: for fn in self.PREINTERCEPTOR: # 注册函数, request=fn(self.ctx,request) #第一个是全局的,第二个是自己的,定义的,需要request不变透明化 #fn(self.ctx,request) 此处此种写法容易引起别人的误会 for router in self.ROUTABLE: # 遍历router传输相关参数 response=router.match(request) # 此处返回为handler的函数值 if response: #返回的函数进行处理 for fn in self.POSTINTERCEPTOR: # 此处处理response相关的方法 response=fn(self.ctx.request,response) return response raise exc.HTTPNotFound('访问资源不存在')
10 支持JSON格式数据返回
此处属于模块的附加功能
import jsondef jsonify(**kwargs): content=json.dumps(kwargs) response=Response() response.content_type="application/json" # 规定返回结果 response.charset='utf-8' response.body="{}".format(content).encode() # 此处不能添加,添加了就不是json格式的数据了 return Response()
_init_.py中的配置
#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,dec,excimport reclass DictObj: def __init__(self,d:dict): # 将属性中的元素添加到属性字典中去,可能会有冲突导致属性覆盖的问题 if not isinstance(d,dict): self.__dict__['_dict']={} # 此处不能是双下划綫,设置类属性字典 else: self.__dict__['_dict']=d #将字典加入到实例属性列表中 def __getattr__(self, item): #此处是通过点号访问的 try: return self._dict[item] # 通过d.x访问,若存在,则直接返回,若不存在,则抛出异常 except KeyError: #当其键不存在的时候 raise AttributeError('Attribute {} Not Found'.format(item)) def __setattr__(self, key, value): #此处是点号修改的 # 不允许设置属性,set表示未实现 raise NotImplementedclass Context(dict): # 用于存储共享数据,app使用 def __getattr__(self, item): try: return self[item] except KeyError: raise ArithmeticError('Attribe {} Not Found'.format(item)) def __setattr__(self, key, value): self[key]=value####################上述两种字典的不同实现方式处理###########################class NestedContext(Context): #继承上述属性,什么逻辑不一样就覆盖那个。Router实例使用 def __init__(self,globalcontext:Context=None): super().__init__() self.relate(globalcontext) def relate(self,globalcontext:Context=None): self.globalcontext=globalcontext def __getattr__(self, item): if item in self.keys(): return self[item] return self.globalcontext[item]class _Router: def __init__(self,prefix:str): self.__prefix=prefix.rstrip('/\\') # 去除prefix及一级目录后面的\\和多余的/ self.__routertable=[] #此处用于保存handler,pattern,method的信息 self.ctx=NestedContext() # 未绑定全局的上下文,在注册的时候进行处理 #实例自己使用的拦截器。在match处进行拦截 self.preinterceptor=[] self.postinterceptor=[] # 装饰器需要有返回值 def reg_preinterceptor(self, fn): # fn前半段两个参数,后半段三个参数,装饰器需要返回值 self.preinterceptor.append(fn) return fn def reg_postinterceptor(self, fn): self.postinterceptor.append(fn) return fn # TYPEPATTERNS = { 'str': r'[^/]+', 'word': r'\w+', 'int': r'[+-]?\d+', 'float': r'[+-]?\d+.\d+', 'any': r'.+' } TYPECAST = { 'str': str, 'word': str, 'int': int, 'float': float, 'any': str } pattern = re.compile('/({[^{}:]+:?[^{}:]*})') # 此处是提取相关用户信息的情况,此处匹配到的只是一级目录的相关信息 def transfrom(self,kv: str): name, _, type = kv.strip('/{}').partition(':') # 此处用于替换操做,此处返回一个列表,通过参数解构来收集,后面是找到第一个后进行分割操做 return '/(?P<{}>{})'.format(name, self.TYPEPATTERNS.get(type, '\w+')), name, self.TYPECAST.get(type, str) # 此处的format只是构造name和对应正则匹配的字典,此处返回的是一个三元 def parse(self,src: str): start = 0 res = '' translator = {} while True: matcher = self.pattern.search(src, start) # start表示偏移量 if matcher: res += matcher.string[start:matcher.start()] # 对匹配到的字符串进行切割处理 tmp = self.transfrom(matcher.string[matcher.start():matcher.end()]) # 此处返回的是下一次匹配的结果的集合,此出返回一个三元组 res += tmp[0] # 此处保存的是名称和正则的元组 translator[tmp[1]] = tmp[2] # 此处保存的是名称和类型的字典 start = matcher.end() # 此处再次匹配,则需要进行初始化继续匹配的操做 else: # 若不能匹配,则返回 break if res: # 若存在,则返回 return res, translator # res中保存URL,translator中保存名称和类型的对应关系 else: # 若不存在,也返回 return res, translator @property def prefix(self): return self.__prefix def register(self,rule,*methods): # 此处用于注册二级目录对应的值 def _register(handle): pattern,translator=self.parse(rule) #此处通过对应的规则来处理相关配置,pattern中包含的是实际的URL路径,translator 中包含分组名称和对应类型的匹配 self.__routertable.append((re.compile(pattern),translator,handle,methods)) return handle return _register def get(self,path): return self.register(path,'GET') def post(self,path): return self.register(path,'POST') def head(self,path): return self.register(path,'HEAD') def match(self,request:Request): if not request.path.startswith(self.__prefix): #判断其是否URL一级目录匹配注册的prefix,若不匹配则返回为None return for fn in self.preinterceptor: # 拦截器处理 request=fn(self.ctx,request) for pattern,translator,hande,methods in self.__routertable: # 此处需要遍历 if not methods or request.method in methods: matcher=pattern.match(request.path.replace(self.prefix,"",1)) if matcher: # 此处若能匹配到,则为True,则可以进行下一步 request.args=matcher.group() request.kwargs=DictObj(matcher.groupdict()) newdict={} for k,v in matcher.groupdict().items(): # 此处返回分组名称和匹配值的字典,K是分组名称,V是匹配的结果 newdict[k]=translator[k](v) #分组匹配结果,通过分组的名称获取对应的类型进行对其值进行操作并保存 request.vars=DictObj(newdict) response=hande(self.ctx,request) #优先使用自己的属性 for fn in self.postinterceptor: response=fn(self.ctx,request,response) return responseimport jsondef jsonify(**kwargs): content=json.dumps(kwargs) response=Response() response.content_type="application/json" # 规定返回结果 response.charset='utf-8' response.body="{}".format(content).encode() # 此处不能添加,添加了就不是json格式的数据了 return Response()class TestWeb: # 类属性方法把类暴露出去 Router=_Router Request=Request Response=Response NestedContext=NestedContext Context=Context jsonify=jsonify ROUTABLE=[] # 此处修改成列表的形式比较适合顺序匹配 ctx=Context() #实例的拦截器 PREINTERCEPTOR=[] POSTINTERCEPTOR=[] @classmethod # 增加扩展功能模块 def extend(cls,name,ext): cls.ctx[name]=ext # 拦截器的注册 @classmethod def reg_preinterceptor(cls,fn): # fn前半段两个参数,后半段三个参数 cls.PREINTERCEPTOR.append(fn) return fn @classmethod def reg_postinterceptor(cls,fn): cls.POSTINTERCEPTOR.append(fn) return fn # 函数需要返回,其本身并没有变动 def __init__(self,**kwargs): self.ctx.app=self for k,v in kwargs.items(): self.ctx[k]=v #添加注册功能 @classmethod def register(cls,router:Router): router.ctx.relate(cls.ctx) #将上述的CTX添加进来,用于属性的访问控制及上述的NestedContext,将全局的上下文绑定给每一个router实例 # 其在router自己初始化时就自己创建 router.ctx.router=router #在自己的字典中中引用自己 cls.ROUTABLE.append(router) # 此处用于调用上述的函数,完成数据的初始化并传递相关的参数prefix参数 @dec.wsgify def __call__(self,request: Request) -> Response: for fn in self.PREINTERCEPTOR: # 注册函数, request=fn(self.ctx,request) #第一个是全局的,第二个是自己的,定义的,需要request不变透明化 #fn(self.ctx,request) 此处此种写法容易引起别人的误会 for router in self.ROUTABLE: # 遍历router传输相关参数 response=router.match(request) # 此处返回为handler的函数值 if response: #返回的函数进行处理 for fn in self.POSTINTERCEPTOR: # 此处处理response相关的方法 response=fn(self.ctx.request,response) return response raise exc.HTTPNotFound('访问资源不存在')
app.py中的值
from wsgiref.simple_server import make_serverfrom testweb import TestWeb# 注册前缀#将前缀加入对应列表中index=TestWeb.Router('/')pyth=TestWeb.Router('/python')admin=TestWeb.Router('/admin')TestWeb.register(pyth)TestWeb.register(admin)TestWeb.register(index)#添加拦截器@TestWeb.reg_preinterceptor #全局起始拦截器def showhandler(ctx:TestWeb.Context,request:TestWeb.Request)-> TestWeb.Request: print (request.path) print (request.user_agent) return request # 返回为request,只有request@pyth.reg_preinterceptor # Router 层面的拦截器def showprefix(ctx:TestWeb.NestedContext,request:TestWeb.Request)->TestWeb.Request: print ('~~~~~~~~~~~~prefix = {}'.format(ctx.router.prefix)) # 此处是打印自己的前缀 return request@admin.reg_postinterceptor # json的处理def showjson(NestedContext,request,response): body=response.body.decode() # 此处返回的是一个字节,需要解码 return TestWeb.jsonify(body=body) # 此处必须传入一个字典。否则会出问题,body是键,文本内容是值@index.get('/\w+')def showpython(NestedContext,request:TestWeb.Request): res=TestWeb.Response() res.body = '<h2>hello World</h2>'.encode() return res@pyth.get('/\d+')def showpython(NestedContext,request:TestWeb.Request): res=TestWeb.Response() res.body = '<h2>hello Python</h2>'.encode() return res@admin.get('/\d+')def showadmin(NestedContext,request:TestWeb.Request): res=TestWeb.Response() res.body = '<h2>hello admin</h2>'.encode() return resif __name__ == "__main__": ip = '192.168.1.200' port = 80 server = make_server(ip, port, TestWeb()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
11 总结
12 模块发布1 熟悉WSGI的编程接口
2 强化模块化,类封装思想
3 增加分析业务的能力这个框架基本剧本了WSGI WEB 框架的基本功能,其他框架都类似。
权限验证,SQL注入检测的功能使用拦截器过滤。
在 testweb包外创建setup.py 在 testweb包内创建web文件
结构如下
web文件内容如下
#!/usr/bin/poython3.6#conding:utf-8#!/usr/bin/poython3.6#conding:utf-8from wsgiref.simple_server import make_serverfrom webob import Request,Response,dec,excimport reclass DictObj: def __init__(self,d:dict): # 将属性中的元素添加到属性字典中去,可能会有冲突导致属性覆盖的问题 if not isinstance(d,dict): self.__dict__['_dict']={} # 此处不能是双下划綫,设置类属性字典 else: self.__dict__['_dict']=d #将字典加入到实例属性列表中 def __getattr__(self, item): #此处是通过点号访问的 try: return self._dict[item] # 通过d.x访问,若存在,则直接返回,若不存在,则抛出异常 except KeyError: #当其键不存在的时候 raise AttributeError('Attribute {} Not Found'.format(item)) def __setattr__(self, key, value): #此处是点号修改的 # 不允许设置属性,set表示未实现 raise NotImplementedclass Context(dict): # 用于存储共享数据,app使用 def __getattr__(self, item): try: return self[item] except KeyError: raise ArithmeticError('Attribe {} Not Found'.format(item)) def __setattr__(self, key, value): self[key]=value####################上述两种字典的不同实现方式处理###########################class NestedContext(Context): #继承上述属性,什么逻辑不一样就覆盖那个。Router实例使用 def __init__(self,globalcontext:Context=None): super().__init__() self.relate(globalcontext) def relate(self,globalcontext:Context=None): self.globalcontext=globalcontext def __getattr__(self, item): if item in self.keys(): return self[item] return self.globalcontext[item]class _Router: def __init__(self,prefix:str): self.__prefix=prefix.rstrip('/\\') # 去除prefix及一级目录后面的\\和多余的/ self.__routertable=[] #此处用于保存handler,pattern,method的信息 self.ctx=NestedContext() # 未绑定全局的上下文,在注册的时候进行处理 #实例自己使用的拦截器。在match处进行拦截 self.preinterceptor=[] self.postinterceptor=[] # 装饰器需要有返回值 def reg_preinterceptor(self, fn): # fn前半段两个参数,后半段三个参数,装饰器需要返回值 self.preinterceptor.append(fn) return fn def reg_postinterceptor(self, fn): self.postinterceptor.append(fn) return fn # TYPEPATTERNS = { 'str': r'[^/]+', 'word': r'\w+', 'int': r'[+-]?\d+', 'float': r'[+-]?\d+.\d+', 'any': r'.+' } TYPECAST = { 'str': str, 'word': str, 'int': int, 'float': float, 'any': str } pattern = re.compile('/({[^{}:]+:?[^{}:]*})') # 此处是提取相关用户信息的情况,此处匹配到的只是一级目录的相关信息 def transfrom(self,kv: str): name, _, type = kv.strip('/{}').partition(':') # 此处用于替换操做,此处返回一个列表,通过参数解构来收集,后面是找到第一个后进行分割操做 return '/(?P<{}>{})'.format(name, self.TYPEPATTERNS.get(type, '\w+')), name, self.TYPECAST.get(type, str) # 此处的format只是构造name和对应正则匹配的字典,此处返回的是一个三元 def parse(self,src: str): start = 0 res = '' translator = {} while True: matcher = self.pattern.search(src, start) # start表示偏移量 if matcher: res += matcher.string[start:matcher.start()] # 对匹配到的字符串进行切割处理 tmp = self.transfrom(matcher.string[matcher.start():matcher.end()]) # 此处返回的是下一次匹配的结果的集合,此出返回一个三元组 res += tmp[0] # 此处保存的是名称和正则的元组 translator[tmp[1]] = tmp[2] # 此处保存的是名称和类型的字典 start = matcher.end() # 此处再次匹配,则需要进行初始化继续匹配的操做 else: # 若不能匹配,则返回 break if res: # 若存在,则返回 return res, translator # res中保存URL,translator中保存名称和类型的对应关系 else: # 若不存在,也返回 return res, translator @property def prefix(self): return self.__prefix def register(self,rule,*methods): # 此处用于注册二级目录对应的值 def _register(handle): pattern,translator=self.parse(rule) #此处通过对应的规则来处理相关配置,pattern中包含的是实际的URL路径,translator 中包含分组名称和对应类型的匹配 self.__routertable.append((re.compile(pattern),translator,handle,methods)) return handle return _register def get(self,path): return self.register(path,'GET') def post(self,path): return self.register(path,'POST') def head(self,path): return self.register(path,'HEAD') def match(self,request:Request): if not request.path.startswith(self.__prefix): #判断其是否URL一级目录匹配注册的prefix,若不匹配则返回为None return for fn in self.preinterceptor: # 拦截器处理 request=fn(self.ctx,request) for pattern,translator,hande,methods in self.__routertable: # 此处需要遍历 if not methods or request.method in methods: matcher=pattern.match(request.path.replace(self.prefix,"",1)) if matcher: # 此处若能匹配到,则为True,则可以进行下一步 request.args=matcher.group() print (type(matcher.groupdict())) request.kwargs=DictObj(matcher.groupdict()) newdict={} for k,v in matcher.groupdict().items(): # 此处返回分组名称和匹配值的字典,K是分组名称,V是匹配的结果 newdict[k]=translator[k](v) #分组匹配结果,通过分组的名称获取对应的类型进行对其值进行操作并保存 request.vars=DictObj(newdict) response=hande(self.ctx,request) #优先使用自己的属性 for fn in self.postinterceptor: response=fn(self.ctx,request,response) return responseclass TestWeb: # 类属性方法把类暴露出去 Router=_Router Request=Request Response=Response NestedContext=NestedContext Context=Context ROUTABLE=[] # 此处修改成列表的形式比较适合顺序匹配 ctx=Context() #实例的拦截器 PREINTERCEPTOR=[] POSTINTERCEPTOR=[] @classmethod # 增加扩展功能模块 def extend(cls,name,ext): cls.ctx[name]=ext # 拦截器的注册 @classmethod def reg_preinterceptor(cls,fn): # fn前半段两个参数,后半段三个参数 cls.PREINTERCEPTOR.append(fn) return fn @classmethod def reg_postinterceptor(cls,fn): cls.POSTINTERCEPTOR.append(fn) return fn # 函数需要返回,其本身并没有变动 def __init__(self,**kwargs): self.ctx.app=self for k,v in kwargs.items(): self.ctx[k]=v #添加注册功能 @classmethod def register(cls,router:Router): router.ctx.relate(cls.ctx) #将上述的CTX添加进来,用于属性的访问控制及上述的NestedContext,将全局的上下文绑定给每一个router实例 # 其在router自己初始化时就自己创建 router.ctx.router=router #在自己的字典中中引用自己 cls.ROUTABLE.append(router) # 此处用于调用上述的函数,完成数据的初始化并传递相关的参数prefix参数 @dec.wsgify def __call__(self,request: Request) -> Response: for fn in self.PREINTERCEPTOR: # 注册函数, request=fn(self.ctx,request) #第一个是全局的,第二个是自己的,定义的,需要request不变透明化 #fn(self.ctx,request) 此处此种写法容易引起别人的误会 for router in self.ROUTABLE: # 遍历router传输相关参数 response=router.match(request) # 此处返回为handler的函数值 if response: #返回的函数进行处理 for fn in self.POSTINTERCEPTOR: # 此处处理response相关的方法 response=fn(self.ctx.request,response) return response raise exc.HTTPNotFound('访问资源不存在')if __name__ == "__main__": pass
_init_.py文件
from .web import TestWeb # 此处外部访问只能使用TestWeb进行各种处理,而能使用Request或Responseimport jsondef jsonify(**kwargs): content=json.dumps(kwargs) response=TestWeb.Response() response.content_type="application/json" # 规定返回结果 response.charset='utf-8' response.body="{}".format(content).encode() # 此处不能添加,添加了就不是json格式的数据了 return TestWeb.Response()
app文件内容
from wsgiref.simple_server import make_serverfrom testweb import TestWeb,jsonify# 注册前缀#将前缀加入对应列表中index=TestWeb.Router('/')pyth=TestWeb.Router('/python')admin=TestWeb.Router('/admin')TestWeb.register(pyth)TestWeb.register(admin)TestWeb.register(index)#添加拦截器@TestWeb.reg_preinterceptor #全局起始拦截器def showhandler(ctx:TestWeb.Context,request:TestWeb.Request)-> TestWeb.Request: print (request.path) print (request.user_agent) return request # 返回为request,只有request@pyth.reg_preinterceptor # Router 层面的拦截器def showprefix(ctx:TestWeb.NestedContext,request:TestWeb.Request)->TestWeb.Request: print ('~~~~~~~~~~~~prefix = {}'.format(ctx.router.prefix)) # 此处是打印自己的前缀 return request@admin.reg_postinterceptor # json的处理def showjson(NestedContext,request,response): body=response.body.decode() # 此处返回的是一个字节,需要解码 return jsonify(body=body) # 此处必须传入一个字典。否则会出问题,body是键,文本内容是值@index.get('/\w+')def showpython(NestedContext,request:TestWeb.Request): res=TestWeb.Response() res.body = '<h2>hello World</h2>'.encode() return res@pyth.get('/\d+')def showpython(NestedContext,request:TestWeb.Request): res=TestWeb.Response() res.body = '<h2>hello Python</h2>'.encode() return res@admin.get('/\d+')def showadmin(NestedContext,request:TestWeb.Request): res=TestWeb.Response() res.body = '<h2>hello admin</h2>'.encode() return resif __name__ == "__main__": ip = '192.168.1.200' port = 80 server = make_server(ip, port, TestWeb()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
setup.py 内容
#!/usr/bin/poython3.6#conding:utf-8from distutils.core import setupsetup( name='testweb', # 名字 version='0.1.0', #版本 description='testweb', #打包列表 author='zhang', # 作者 author_email='12345678910@163.com', # # url 表示包帮助文档路径 packages=['testweb'])
打包
python setup.py sdist
安装
pip install dist/test
复制到另一个环境安装查看
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。