web 也叫CS开发
CS 及客户端,服务器端编程
客户端,服务器端之间需要socket,约定协议,版本(往往使用的协议是TCP或UDP),指定地址和端口,就可以通信了客户端,服务端传输数据,数据可以有一定的格式,双方必须约定好
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框架
在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次断开
详情请看:https://blog.51cto.com/11233559/2093789
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='<h2>Hello World</h2>'.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='<h2>Hello World</h2>'.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='<h2>Hello World</h2>'.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('<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) # 实例化一个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('<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() # 删除
结果如下
什么是路由,简单的说,就是路怎么走,就是按照不同的路径分发数据
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='<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 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 = '<h2>hello Python</h2>'.encode()
return res
def showdefault(request:Request):
res=Response()
res.body = '<h2>hello World</h2>'.encode()
return res
def show(request:Request):
res=Response()
res.status_code = 404
res.body = '<h2>Not Found</h2>'.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 = '<h2>hello Python</h2>'.encode()
return res
def showdefault(request:Request):
res=Response()
res.body = '<h2>hello World</h2>'.encode()
return res
def show(request:Request):
res=Response()
res.status_code = 404
res.body = '<h2>Not Found</h2>'.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 = '<h2>hello Python</h2>'.encode()
return res
def showdefault(request:Request):
res=Response()
res.body = '<h2>hello World</h2>'.encode()
return res
def show(request:Request):
res=Response()
res.status_code = 404
res.body = '<h2>Not Found</h2>'.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 = '<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 res
def showdefault(request:Request):
res=Response()
res.body = '<h2>hello World</h2>'.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 = '<h2>hello Python</h2>'.encode()
return res
def showdefault(request:Request):
res=Response()
res.body = '<h2>hello World</h2>'.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 = '<h2>hello World</h2>'.encode()
return res
@Application.register('/python')
def showpython(request:Request):
res=Response()
res.body = '<h2>hello Python</h2>'.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 = '<h2>hello World</h2>'.encode()
return res
@Application.register('^/python$')
def showpython(request:Request):
res=Response()
res.body = '<h2>hello Python</h2>'.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 = '<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 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 = '<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
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 = '<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
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 = '<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 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 = '<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 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 = '<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 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 = '<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 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 = '<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 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 = '<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 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 = '<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 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 = '<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 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
复制到另一个环境安装查看
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。