web 也叫CS开发
CS 及客户端,服务器端编程
客户端,服务器端之间须要socket,约定协议,版本(每每使用的协议是TCP或UDP),指定地址和端口,就能够通讯了html客户端,服务端传输数据,数据能够有必定的格式,双方必须约定好 前端
B=Browser,Browser是一种特殊的客户端,支持HTTP(S)协议,可以经过URL 向服务器端发起请求,等待服务端返回HTML等数据,并在浏览器内可视化展现程序 python
S=server,Server支持HTTP(S)协议,可以接受众多客户端发起的HTTP请求,通过处理,将HTML等数据返回给浏览器 mysql
本质上来讲,BS是一种特殊的CS,及客户端必须是一种支持HTTP协议且可以解析并渲染HTML的软件,服务端必须是可以接受客户端HTTP访问的服务器软件 程序员
HTTP 底层使用TCP传输,须要经过相应的规则来处理web
HTTP 超文本传输协议,在文本的基础上作一些突破,相关的渲染处理,断行等。使用标签的方式进行规定的处理,文本已经有了一个格式,浏览器中必须一种能力,支持这种文本的处理和渲染。正则表达式
浏览器必须支持HTTP协议,必须可以理解超文本并将其绘制出来 redis
BS 开发分为两端开发
1 客户端开发,或称为前端开发
2 服务端开发,python能够学习WSGI,Django,Flask,Tornado等sqlpython WEB 框架
WSGI, web Server Gateway interface,能够看作是一种底层协议,它规定了服务器程序和应用程序各自实现什么借口,python称为wsgiref 数据库flask: 基于WSGI ,微框架
Django:基于WSGI,开源的WEB框架
在http1.1以前,都是一个请求一个链接,而TCP的连接建立销毁成本高,对服务器影响较大,所以自从http1.1开始,支持keep-alive,默认也开启,一个链接打开后,会保持一段时间,浏览器再访问该服务器资源就使用这个TCP连接,减轻了服务器的压力,提升了效率
全部的动态网页开发,都必须是有状态的协议
若是是持续链接,一个TCP是能够发送多个HTTP请求的
HTTP/1.1存在一个问题,单个TCP链接在同一时刻只能处理一个请求,意思是说: 两个请求的生命周期不能重叠,任意两个HTTP请求从开始到结束的时间在同一个TCP链接里不能重叠
虽然HTTP/1.1规范中规定了Pipelining来试图解决这个问题,但此功能默认是关闭的
Pipelining 中
客户端能够在一个链接中发送多个请求(不须要等待任意请求的响应)。收到请求的服务器必须按照请求收到的顺序发送响应。pipelining的缺点
1 一些代理服务器不能正确支持 HTTP pipelining
2 正确的流水线实现是复杂的
3 若是第一个请求的处理花费大量时间,则会致使后面的请求没法处理,形成阻塞。
Http2 提供了Multiplexing 多路传输特性,能够在一个TCP链接中同时完成多个HTTP请求
1 维持和服务其已经创建的TCP链接,在同一个链接上顺序处理多个请求
2 和服务器创建多个TCP链接浏览器对同一个Host 创建TCP链接数量有没限制
Chrome 最多容许对同一个Host创建6个TCP连接,不一样浏览器有区别
若是图片都是HTTPS 链接而且在同一域名下,那么浏览器在SSL握手以后会和服务器协商能不能使用HTTP2,若是能的话就是用Multiplexing 功能在这个链接上进行多路传输,不过也未必会全部挂载在各个域名的资源都会使用一个TCP链接获取吗,但能够肯定的是multiplexing 可能很被用到
若是发现不是使用HTTP2,或者不用HTTPS,(现实中的 HTTP2 都是在 HTTPS 上实现的,因此也就是只能使用 HTTP/1.1),那么浏览器就会在一个HOST上创建多个TCP链接,链接数量的最大限制取决于浏览器的设置,这些来凝结会在空闲的时候被浏览器用来发送新请求,若是全部链接都在发送请求,那么其只能等待了。
同一个客户端的两次请求之间没有任何关系,从服务端的角度看,他不知道这两个请求来自同一个客户端
最先的设计是不须要知道二者之间的联系的,
HTTP协议是无状态协议
有连接,由于HTTP 是基于TCP 连接的,须要3次握手,4次断开
http://127.0.0.1/login?user=zhangsan&password=123
登陆窗口不能使用GET传输,GET头部的长度是有限的,不能多于200多个之外的传输
格式是 ? 后面加key1=value1&key2=value2
当使用POST 传输数据时,其相关的数据都被封装在请求体及body中,而不是get中的直接暴露。
大的数据传输,必须使用POST,而不能使用GET传输数据。
HTML 是一种格式的约定,须要的数据是动态的,去数据库查的数据不是死的,是动态的,静态文本文件包括图片
HTML 是将文本原封不动的返回,如果一个登录的用户名和密码的匹配问题的时候,就不是HTML能作的事情,此时便须要动态网页来完成。如python,只有脚本是不行的,这就须要相似的解释器来进行处理。Php,asp等动态的网页技术,server page 服务器端的页面。动态页面中的无状态带来很大的问题,再次登陆将致使登陆后的和登陆的不要紧。既然你连接到我,我能够发送一个惟一标识给你,你须要下次将这个标识带来,来保证是你,服务端须要发送和记录标识,此处须要写入到内存的数据结构中,当用户量很大时,记录的东西就不只仅是这个用户标识了。
cookie:是一种客户端,服务端传递数据的技术 ,其保存的形式是键值对信息
浏览器发起每个请求,都会把cookie信息给服务端,服务端能够经过判断这些信息,来肯定此次请求是否和以前的请求有关联
通常来讲cookie信息是在服务器端生成,返回给客户端
客户端能够本身设置cookie信息
Cookie 通常是当你第一次连接服务器的时候服务器会查看是否有cookie带过来,若没有则推送一个标识,这个标识中会在HTTP的response包中存在,其会在浏览器中保存起来。若是再次对一样网站发起请求,若是cookie没过时时,其会继续处理此标识。如果同一个且有效,则若登陆过,则不显示登陆页面,若没登陆,则强制跳转到登陆页面。若是一个网站一直登陆,其发现cookie快过时了,则会延长。
Cookie 是对不一样的域名有区分的
cookie中加的ID 叫作session ID ,称为会话ID,当会话完结后,ID就消亡了,浏览器关闭,
Session 是存放在服务器端的,其会增长内存。后期则使用无session, token每每中间会使用redis和memcached进行处理
请求来的时候,其得带着是不是同一个会话标识
cookie能够伪造
WSGI 主要规定了服务器端和应用程序之间的接口
浏览器
能够接受用户的socket请求并和客户端达成HTTP协议并识别解析,将数据交给后端的WSGI app 进行处理
Server 必须支持HTTP协议,在python中实现了WSGI的接口,HTTP server得支持WSGI协议,将数据传递给程序,(app返回)而后返回给客户端对应的状态状况(响应头),使得浏览器作好准备,而后再返回给server,再由server将其包装成HTTP的协议并解析处理。
后端真实处理业务的函数对象
后端APP知足的条件
1 可经过前面的WGSI Server进行相关的调用操做
应用程序应该是一个可调用对象
调用实际上是回调,调用的实际上是APP的某个方法
python中应该是函数,类,实现了call方法的类的实例
2 这个可调用对象应该接受两个参数
知足了WSGI 的基本要求,必须再留一个空,协议的封装是须要在server端的,所以要将你写的东西交给 http server ,由http server对返回结果进行处理 其上述返回必须是一个可迭代对象(list,dict等)两个参数就是入 request和出response
Handler 和 body都给了app
逻辑处理: 调用对应的方法给客户端。
http server 返回给app server 的参数
eviron和start_response 这两个参数能够是任意的合法名。但通常都是这两个名字
eviron 是包含HTTP请求信息的dict对象
名称 | 含义 |
---|---|
REQUEST_METHOD | 请求方法,GET,PSOT,HEAD等 |
PATH_INFO | URL 中路径部分信息 |
QUERY_STRING | 查询字符串 |
SERVER_NAME,SERVER_PORT | 服务器名,端口号 |
HTTP_POST | 地址和端口 |
SERVER_PROTOCOL | 协议 |
HTTP_USER_AGENT | User Agent信息 |
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
服务器端
服务器端程序须要调用符合上述定义的可调用对象,传入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-8 from wsgiref.simple_server import make_server,demo_app ip='192.168.1.200' port=80 server=make_server(ip,port,demo_app) # 实例化一个websever server.serve_forever() # 启动 server.server_close() # 关闭 server.shutdown() # 删除
General Request URL: http://192.168.1.200/ Request Method: GET Status Code: 200 OK Remote Address: 192.168.1.200:80 Referrer Policy: no-referrer-when-downgrade Response Headers Content-Length: 3302 # 响应报文总长度 Content-Type: text/plain; charset=utf-8 # 要求文本显示 字符串是UTF-8 Date: Sun, 08 Sep 2019 12:34:55 GMT Server: WSGIServer/0.2 CPython/3.6.4 #暴露服务器端信息 Request Headers Accept: 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.8 Cache-Control: max-age=0 Connection: keep-alive Cookie: csrftoken=Er5XLdEG211nWzgtJL1GFoxBgxFnnHbff2W7IiprrwTQbAAOzWWoHzihDrIxiK17 Host: 192.168.1.200 Upgrade-Insecure-Requests: 1 User-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-8 from wsgiref.simple_server import make_server ip='192.168.1.200' port=80 def app(environ,start_response): html='<h1>Hello World</h1>'.encode() start_response("200 OK", [('Content-Type','text/html; charset=utf-8')]) return [html] server=make_server(ip,port,app) # 实例化一个websever server.serve_forever() # 启动 server.server_close() # 关闭 server.shutdown() # 删除
结果以下
环境变量数据不少,都是存储在字典中的,字典存取没有对象的属性使用方便,使用第三方webob,能够把环境数据的解析,封装成对象
pip install webob
将环境参数解析并封装成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-8 from wsgiref.simple_server import make_server from webob import Request,Response ip='192.168.1.200' port=80 def 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='<h1>Hello World</h1>'.encode() start_response("200 OK", [('Content-Type','text/html; charset=utf-8')]) return [html] server=make_server(ip,port,app) # 实例化一个websever server.serve_forever() # 启动 server.server_close() # 关闭 server.shutdown() # 删除
请求URL: http://192.168.1.200/admin/?username=mysql&password=123456
结果以下:
MultiDict 容许一个key存储好几个值
#!/usr/bin/poython3.6 #conding:utf-8 from webob.multidict import MultiDict md=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)) #只能有一个值,有多个值使用这个返回有问题
结果以下
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response ip='192.168.1.200' port=80 def app(environ,start_response): res=Response() start_response(res.status,res.headerlist) # 返回可迭代对象 html='<h1>Hello World</h1>'.encode("utf-8") return [html] server=make_server(ip,port,app) # 实例化一个websever server.serve_forever() # 启动 server.server_close() # 关闭 server.shutdown() # 删除
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response ip='192.168.1.200' port=80 def app(environ,start_response): res=Response('<h1>Hello World</h1>') # 写法二 #res.body='<h1>Hello Python</h1>'.encode() #res.status_code=200 return res(environ,start_response) server=make_server(ip,port,app) # 实例化一个websever server.serve_forever() # 启动 server.server_close() # 关闭 server.shutdown() # 删除
结果以下
此装饰器传入一个request的参数,则返回一个Response 的返回值,实现了一进一出的状况
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec ip='192.168.1.200' port=80 @dec.wsgify def app(request:Request)->Response: return Response('<h1>hello python </h1>'.encode()) if __name__ == "__main__": server = make_server(ip, port, app) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
结果以下
什么是路由,简单的说,就是路怎么走,就是按照不一样的路径分发数据
URL 就是不一样资源的路径,不一样的路径应该对应不一样的应用程序来处理,因此代码中须要增长对路径的处理和分析
路径 | 内容 |
---|---|
/ | 返回欢迎内容 |
/python | 返回hello python |
其余路径 | 返回404 |
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec ip='192.168.1.200' port=80 @dec.wsgify def app(request:Request)->Response: res=Response() if request.path=="/": res.body='<h1>hello World</h1>'.encode() return res elif request.path=="/python": res.body='<h1>hello Python</h1>'.encode() return res else: res.status_code=404 res.body='<h1>Not Found</h1>'.encode() return res if __name__ == "__main__": server = make_server(ip, port, app) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec ip='192.168.1.200' port=80 def showpython(request:Request): res=Response() res.body = '<h1>hello Python</h1>'.encode() return res def showdefault(request:Request): res=Response() res.body = '<h1>hello World</h1>'.encode() return res def show(request:Request): res=Response() res.status_code = 404 res.body = '<h1>Not Found</h1>'.encode() return res @dec.wsgify def 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() # 删除
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec ip='192.168.1.200' port=80 def showpython(request:Request): res=Response() res.body = '<h1>hello Python</h1>'.encode() return res def showdefault(request:Request): res=Response() res.body = '<h1>hello World</h1>'.encode() return res def show(request:Request): res=Response() res.status_code = 404 res.body = '<h1>Not Found</h1>'.encode() return res ROUTABLE={ '/' :showdefault, '/python' :showpython } @dec.wsgify def 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() # 删除
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec ip='192.168.1.200' port=80 def showpython(request:Request): res=Response() res.body = '<h1>hello Python</h1>'.encode() return res def showdefault(request:Request): res=Response() res.body = '<h1>hello World</h1>'.encode() return res def show(request:Request): res=Response() res.status_code = 404 res.body = '<h1>Not Found</h1>'.encode() return res ROUTABLE={} def register(path,fn): ROUTABLE[path]=fn register('/',showdefault) register('/python',showpython) @dec.wsgify def 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() # 删除
思想: 将须要用户本身编写的东西放置在类的外边,其余的相关事件放置在类中
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec ip='192.168.1.200' port=80 class Application: ROUTABLE={} def show(self,request:Request): res=Response() res.status_code = 404 res.body = '<h1>Not Found</h1>'.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 = '<h1>hello Python</h1>'.encode() return res def showdefault(request:Request): res=Response() res.body = '<h1>hello World</h1>'.encode() return res Application.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() # 删除
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec,exc ip='192.168.1.200' port=80 class 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 = '<h1>hello Python</h1>'.encode() return res def showdefault(request:Request): res=Response() res.body = '<h1>hello World</h1>'.encode() return res Application.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() # 删除
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec,exc ip='192.168.1.200' port=80 class 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 = '<h1>hello World</h1>'.encode() return res @Application.register('/python') def showpython(request:Request): res=Response() res.body = '<h1>hello Python</h1>'.encode() return res if __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
到目前为止,一个框架的雏形基本完成了
application是WSGI中的应用程序。可是这个应用程序已经变成了一个路由程序,处理逻辑已移动到了应用程序外了,而这部分就是留给程序员的部分。
目前实现的路由匹配,路径匹配很是死板,使用正则表达式改造。导入re模块,注册时,存入的再也不是路径字符串,而是pattern。
__call__方法中实现模式和传入路径的比较
compile 方法,编译正则表达式
match 方法,必须从头开始匹配, 只匹配一次
search方法,只匹配一次
fullmath 方法,要彻底匹配
findall方法,从头开始找,找到全部匹配
字典的问题
若是使用字典,key若是是路径,不能保证其顺序,由于大多的匹配都是从严到宽,若是没有必定的顺序,则会致使问题正则表达式的预编译问题
第一次使用会影响到用户体验,因此仍是要在注册的时候编译的。综上,改用列表,元素使用二元祖(编译后的正则对象,handler)
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec,exc import re ip='192.168.1.200' port=80 class 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 = '<h1>hello World</h1>'.encode() return res @Application.register('^/python$') def showpython(request:Request): res=Response() res.body = '<h1>hello Python</h1>'.encode() return res if __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
请求方法,通常来讲,既是是同一个URL,由于请求方法不一样,处理方式也是不一样的
假设一个URL。GET方法但愿返回网页内容,POST方法表示浏览器提交数据过来须要处理并存储进数据库,最终返回给客户端存储成功或者失败信息,
换句话说,须要根据请求方法和正则同时匹配才能决定执行什么样的处理函数
方法 | 含义 |
---|---|
GET | 请求指定的页面信息,并返回报头和正文 |
HEAD | 相似于get请求,只不过返回的响应中没有具体的内容,用于获取报头 |
POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件),数据被包含在请求正文中,POST请求可能会致使新的资源创建或者已有的资源的修改 |
PUT | 从客户端向服务器端传递的数据取代指定的文档的内容 |
DELETE | 请求服务器删除指定的内容 |
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec,exc import re ip='192.168.1.200' port=80 class 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 = '<h1>hello World</h1>'.encode() return res @Application.get('^/python$') def showpython(request:Request): res=Response() res.body = '<h1>hello Python</h1>'.encode() return res @Application.post('^/python$') def showpython(request:Request): res=Response() res.body = '<h1>hello Python POST </h1>'.encode() return res @Application.post('^/') def showdefault(request:Request): res=Response() res.body = '<h1>hello World POST </h1>'.encode() return res if __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-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec,exc import re ip='192.168.1.200' port=80 class 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 = '<h1>hello World</h1>'.encode() return res @Application.get('^/python') def showpython(request:Request): res=Response() res.body = '<h1>hello Python</h1>'.encode() return res if __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
结果以下:
支持正则表达式的捕获,
在框架回调__call__时,拿到request.path和正则的模式匹配后,就能够提取分组了如何处理分组?
应用程序就是handler对应的不一样的函数,其参数request是同样的,将捕获的数据动态增长到request对象中便可。用动态增长属性,为request增长args,kwargs属性,在handler中使用的时候,就能够直接熊属性中,将args,kwargs拿出来就能够直接使用了
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec,exc import re ip='192.168.1.200' port=80 class 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 = '<h1>hello World</h1>'.encode() return res @Application.get('^/python') def showpython(request:Request): res=Response() res.body = '<h1>hello Python</h1>'.encode() return res if __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
所谓的路由分组,就是按照前缀分别映射
需求
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-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec,exc import re ip='192.168.1.200' port=80 class 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 = '<h1>hello World</h1>'.encode() return res @pyth.get('/(\d+)') def showpython(request:Request): res=Response() res.body = '<h1>hello Python</h1>'.encode() return res @admin.get('/(\d+)') def showadmin(request:Request): res=Response() res.body = '<h1>hello admin</h1>'.encode() return res if __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
经过此类,可以使得kwargs这个字典,不使用[]访问元素,使用.号访问元素,如同属性同样访问
#!/usr/bin/poython3.6 #conding:utf-8 class 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 NotImplemented d={ 'a':1, 'b':2, 'c':3 } x=DictOrd(d) print (x.__dict__) print (DictOrd.__dict__) print (x.a) print (x.b)
结果以下
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec,exc import re ip='192.168.1.200' port=80 class 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 NotImplemented class 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 = '<h1>hello World</h1>'.encode() return res @pyth.get('/(\d+)') def showpython(request:Request): res=Response() res.body = '<h1>hello Python</h1>'.encode() return res @admin.get('/(\d+)') def showadmin(request:Request): res=Response() res.body = '<h1>hello admin</h1>'.encode() return res if __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
问题
目前路由匹配使用正则表达式定义,不友好,不少用户不会使用正则表达式,可否简化分析
生产环境中。URL是规范的,不能随意书写,路径是有意义的,尤为是对restful风格,因此,要对URL规范
如 product/111102243454343 ,这就是一种规范,要求第一段是业务,第二段是ID。设计
路径规范化,以下定义
/student/{name:str}/{id:int}
类型设计。支持str,word,int,float,any类型
经过这种定义,可让用户定义简化了,也规范了,背后的转换是编程者实现的
类型 | 含义 | 对应正则 |
---|---|---|
str | 不包含/的任意字符 | [^/]+ |
word | 字母和数字 | \w+ |
int | 纯数字,正负数 | [+-]?\d+ |
float | 正负号,数字,包含. | [+-]?\d+.\d+ |
any | 包含/的任意字符 | .+ |
保存类型
类型 | 对应类型 |
---|---|
str | str |
word | str |
int | int |
float | float |
any | str |
#!/usr/local/bin/python3.6 #coding:utf-8 import re s='/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,translator print (parse(s)) print (parse(s1)) print (parse(s2)) print (parse(s3))
结果以下
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec,exc import re ip='192.168.1.200' port=80 class 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 NotImplemented class 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 = '<h1>hello World</h1>'.encode() return res @pyth.get('/\d+') def showpython(request:Request): res=Response() res.body = '<h1>hello Python</h1>'.encode() return res @admin.get('/\d+') def showadmin(request:Request): res=Response() res.body = '<h1>hello admin</h1>'.encode() return res if __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
处理流程
客户端发起请求,被容器调度给Application的call
Application 中便利全部注册的router,router经过match来判断是不是本身处理的,经过查看request的请求来匹配注册前缀,若符合条件,则匹配对应的请求方法,若方法符合,则匹配对应的URL二级目录,并返回对应的函数,handler处理后,返回response,applicationde拿着这个response数据,返回给原始的wsgi。
拦截器,就是要在请求处理环节的某处加入处理,有多是中断手续的处理
根据拦截点不一样,分为:
1 请求拦截
2 响应拦截
根据影响面分为:
1 全局拦截
在application中拦截
2 局部拦截
在Router中拦截
相关图形
前面的是application层面的拦截,及全局拦截,。后面是TOUTER层面的拦截,及局部拦截,
拦截器能够是多个,多个拦截器是顺序的
数据response前执行的的命名为preinterceptor ,以后的命名为postinterceptor。
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
去的输入和输出都是response
def fn(app,request:Request,response:Response)-> Response: pass
为了把一些应数据,配置数据,数据库链接提供给全局共享数据提供全部对象使用,增长一个字典,存储共享数据。将环境变量传递下去。
为了方便访问,提供字典的属性化访问的类,由于这个字典是可写的,和前面的类不同。
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 #处理修改和添加问题
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]
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec,exc import re ip='192.168.1.200' port=80 class 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 NotImplemented class 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 response class 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 = '<h1>hello World</h1>'.encode() return res @pyth.get('/\d+') def showpython(request:Request): res=Response() res.body = '<h1>hello Python</h1>'.encode() return res @admin.get('/\d+') def showadmin(request:Request): res=Response() res.body = '<h1>hello admin</h1>'.encode() return res if __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
做为一个框架,更多的功能应该是从外部加入
1 不可能些的很是完善
2 非必要的都应该动态加入
因此,提供一个扩展接口很是重要
#!/usr/bin/poython3.6 #conding:utf-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec,exc import re ip='192.168.1.200' port=80 class 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 NotImplemented class 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 response class 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 = '<h1>hello World</h1>'.encode() return res @pyth.get('/\d+') def showpython(request:Request): res=Response() res.body = '<h1>hello Python</h1>'.encode() return res @admin.get('/\d+') def showadmin(request:Request): res=Response() res.body = '<h1>hello admin</h1>'.encode() return res if __name__ == "__main__": server = make_server(ip, port, Application()) # 实例化一个websever try: server.serve_forever() # 启动 except KeyboardInterrupt: pass finally: server.server_close() # 关闭 server.shutdown() # 删除
在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_server from 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 = '<h1>hello World</h1>'.encode() return res @pyth.get('/\d+') def showpython(request:TestWeb.Request): res=TestWeb.Response() res.body = '<h1>hello Python</h1>'.encode() return res @admin.get('/\d+') def showadmin(request:TestWeb.Request): res=TestWeb.Response() res.body = '<h1>hello admin</h1>'.encode() return res if __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-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec,exc import re class 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 NotImplemented class 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 response class 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('访问资源不存在')
此处属于模块的附加功能
import json def 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-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec,exc import re class 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 NotImplemented class 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 response import json def 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_server from 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 = '<h1>hello World</h1>'.encode() return res @pyth.get('/\d+') def showpython(NestedContext,request:TestWeb.Request): res=TestWeb.Response() res.body = '<h1>hello Python</h1>'.encode() return res @admin.get('/\d+') def showadmin(NestedContext,request:TestWeb.Request): res=TestWeb.Response() res.body = '<h1>hello admin</h1>'.encode() return res if __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() # 删除
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-8 from wsgiref.simple_server import make_server from webob import Request,Response,dec,exc import re class 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 NotImplemented class 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 response class 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或Response import json def 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_server from 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 = '<h1>hello World</h1>'.encode() return res @pyth.get('/\d+') def showpython(NestedContext,request:TestWeb.Request): res=TestWeb.Response() res.body = '<h1>hello Python</h1>'.encode() return res @admin.get('/\d+') def showadmin(NestedContext,request:TestWeb.Request): res=TestWeb.Response() res.body = '<h1>hello admin</h1>'.encode() return res if __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-8 from distutils.core import setup setup( 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
复制到另外一个环境安装查看