温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

web开发概述及基本框架书写

发布时间:2020-08-09 11:35:55 来源:网络 阅读:886 作者:长跑者1号 栏目:编程语言

一 基本概念

1 基本框架

web开发概述及基本框架书写

2 CS 开发

web 也叫CS开发
CS 及客户端,服务器端编程
客户端,服务器端之间需要socket,约定协议,版本(往往使用的协议是TCP或UDP),指定地址和端口,就可以通信了

客户端,服务端传输数据,数据可以有一定的格式,双方必须约定好

3 BS 编程

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称为wsgiref

flask: 基于WSGI ,微框架
Django:基于WSGI,开源的WEB框架

4 HTTP 和TCP 协议

1 短链接

在http1.1之前,都是一个请求一个连接,而TCP的链接创建销毁成本高,对服务器影响较大,因此自从http1.1开始,支持keep-alive,默认也开启,一个连接打开后,会保持一段时间,浏览器再访问该服务器资源就使用这个TCP链接,减轻了服务器的压力,提高了效率

所有的动态网页开发,都必须是有状态的协议

2 对于持续的TCP链接,一个TCP能否发送多个请求

如果是持续连接,一个TCP是可以发送多个HTTP请求的

3 一个TCP中的HTTP请求能否同时发送

HTTP/1.1存在一个问题,单个TCP连接在同一时刻只能处理一个请求,意思是说: 两个请求的生命周期不能重叠,任意两个HTTP请求从开始到结束的时间在同一个TCP连接里不能重叠


虽然HTTP/1.1规范中规定了Pipelining来试图解决这个问题,但此功能默认是关闭的

Pipelining 中
客户端可以在一个连接中发送多个请求(不需要等待任意请求的响应)。收到请求的服务器必须按照请求收到的顺序发送响应。

pipelining的缺点
1 一些代理服务器不能正确支持 HTTP pipelining
2 正确的流水线实现是复杂的
3 如果第一个请求的处理花费大量时间,则会导致后面的请求无法处理,造成阻塞。


Http2 提供了Multiplexing 多路传输特性,可以在一个TCP连接中同时完成多个HTTP请求

4 HTTP1.1中浏览器页面加载效率提高方式

1 维持和服务其已经建立的TCP连接,在同一个连接上顺序处理多个请求
2 和服务器建立多个TCP连接

浏览器对同一个Host 建立TCP连接数量有没限制

Chrome 最多允许对同一个Host建立6个TCP链接,不同浏览器有区别

5 收到的HTML如果包含图片文本等,通过什么协议下载的

如果图片都是HTTPS 连接并且在同一域名下,那么浏览器在SSL握手之后会和服务器协商能不能使用HTTP2,如果能的话就是用Multiplexing 功能在这个连接上进行多路传输,不过也未必会所有挂载在各个域名的资源都会使用一个TCP连接获取吗,但可以确定的是multiplexing 可能很被用到


如果发现不是使用HTTP2,或者不用HTTPS,(现实中的 HTTP2 都是在 HTTPS 上实现的,所以也就是只能使用 HTTP/1.1),那么浏览器就会在一个HOST上建立多个TCP连接,连接数量的最大限制取决于浏览器的设置,这些来凝结会在空闲的时候被浏览器用来发送新请求,如果所有连接都在发送请求,那么其只能等待了。

6无状态协议

同一个客户端的两次请求之间没有任何关系,从服务端的角度看,他不知道这两个请求来自同一个客户端

最早的设计是不需要知道两者之间的联系的,

HTTP协议是无状态协议

7 有链接

有链接,因为HTTP 是基于TCP 链接的,需要3次握手,4次断开

8 URL 和相关请求及报文信息

详情请看:https://blog.51cto.com/11233559/2093789

9 常见的传递信息的方式

1 GET 中使用 query string

http://127.0.0.1/login?user=zhangsan&password=123

登录窗口不能使用GET传输,GET头部的长度是有限的,不能多于200多个以外的传输
格式是 ? 后面加key1=value1&key2=value2

2 在POST 请求体中提交数据至服务器端

当使用POST 传输数据时,其相关的数据都被封装在请求体及body中,而不是get中的直接暴露。

大的数据传输,必须使用POST,而不能使用GET传输数据。

5 HTML 简介

HTML 是一种格式的约定,需要的数据是动态的,去数据库查的数据不是死的,是动态的,静态文本文件包括图片

HTML 是将文本原封不动的返回,若是一个登陆的用户名和密码的匹配问题的时候,就不是HTML能做的事情,此时便需要动态网页来完成。如python,只有脚本是不行的,这就需要类似的解释器来进行处理。Php,asp等动态的网页技术,server page 服务器端的页面。动态页面中的无状态带来很大的问题,再次登录将导致登录后的和登录的没关系。既然你链接到我,我可以发送一个唯一标识给你,你需要下次将这个标识带来,来保证是你,服务端需要发送和记录标识,此处需要写入到内存的数据结构中,当用户量很大时,记录的东西就不仅仅是这个用户标识了。

6 Cookie

1 简介

cookie:是一种客户端,服务端传递数据的技术 ,其保存的形式是键值对信息

浏览器发起每一个请求,都会把cookie信息给服务端,服务端可以通过判断这些信息,来确定这次请求是否和之前的请求有关联

2 cookie 的生成:

一般来说cookie信息是在服务器端生成,返回给客户端
客户端可以自己设置cookie信息
Cookie 一般是当你第一次链接服务器的时候服务器会查看是否有cookie带过来,若没有则推送一个标识,这个标识中会在HTTP的response包中存在,其会在浏览器中保存起来。如果再次对同样网站发起请求,如果cookie没过期时,其会继续处理此标识。若是同一个且有效,则若登录过,则不显示登录页面,若没登录,则强制跳转到登录页面。如果一个网站一直登录,其发现cookie快过期了,则会延长。

3 session ID

Cookie 是对不同的域名有区分的
cookie中加的ID 叫做session ID ,称为会话ID,当会话完结后,ID就消亡了,浏览器关闭,
Session 是存放在服务器端的,其会增加内存。后期则使用无session, token往往中间会使用redis和memcached进行处理
请求来的时候,其得带着是否是同一个会话标识
cookie可以伪造

二 WSGI简介

1 概述

1 请求图及相关概述

WSGI 主要规定了服务器端和应用程序之间的接口

web开发概述及基本框架书写

2 三个角色:

1 客户端工具:

浏览器

2 服务端工具:
1 http server

可以接受用户的socket请求并和客户端达成HTTP协议并识别解析,将数据交给后端的WSGI app 进行处理
Server 必须支持HTTP协议,在python中实现了WSGI的接口,HTTP server得支持WSGI协议,将数据传递给程序,(app返回)然后返回给客户端对应的状态情况(响应头),使得浏览器做好准备,然后再返回给server,再由server将其包装成HTTP的协议并解析处理。

2 WSGI app 应用程序

后端真实处理业务的函数对象

后端APP满足的条件

1 可通过前面的WGSI Server进行相关的调用操作
应用程序应该是一个可调用对象
调用其实是回调,调用的其实是APP的某个方法
python中应该是函数,类,实现了call方法的类的实例


2 这个可调用对象应该接受两个参数
满足了WSGI 的基本要求,必须再留一个空,协议的封装是需要在server端的,因此要将你写的东西交给 http server ,由http server对返回结果进行处理 其上述返回必须是一个可迭代对象(list,dict等)

两个参数就是入 request和出response
Handler 和 body都给了app
逻辑处理: 调用对应的方法给客户端。

2 相关参数详解

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拿到返回可迭代对象,返回给客户端。

3 WSGIREF

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()  # 删除 

web开发概述及基本框架书写

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()  # 删除

结果如下

web开发概述及基本框架书写

4 webob 简介

1 简介

环境变量数据很多,都是存储在字典中的,字典存取没有对象的属性使用方便,使用第三方webob,可以把环境数据的解析,封装成对象

pip install webob 

2 webob.Request 对象

将环境参数解析并封装成request对象

GET方法,发送的数据是URL中的request handler中
request.get 就是一个字典MultiDict,里面就封装着查询字符串

POST 方法,"提交"的数据是放在request body里面的,但是也同时可以使用Query String
request.POST可以获取request Body中的数据,也是个字典MultiDict

不关心什么方法提交,只关心数据,可以使用request.params,它里面是所有提交数据的封装

#!/usr/bin/poython3.6
#conding:utf-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

结果如下:

web开发概述及基本框架书写

3 MultiDict

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)) #只能有一个值,有多个值使用这个返回有问题

结果如下

web开发概述及基本框架书写

4 webob.Response 对象

#!/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()  # 删除

结果如下

web开发概述及基本框架书写

5 dec.wsdify

此装饰器传入一个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()  # 删除

结果如下
web开发概述及基本框架书写

三 web 框架开发

1 路由

1 简介

什么是路由,简单的说,就是路怎么走,就是按照不同的路径分发数据
URL 就是不同资源的路径,不同的路径应该对应不同的应用程序来处理,所以代码中需要增加对路径的处理和分析

2 路由功能的实现

1 需求
路径 内容
/ 返回欢迎内容
/python 返回hello python
其他路径 返回404
2 基本思路,利用request.path中对应的匹配值进行相关的处理
#!/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()  # 删除
3 将相关函数抽象到外边
#!/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()  # 删除
4 通过字典存储函数名的方式来进行相关的匹配操作
#!/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()  # 删除
5 配置注册函数功能
#!/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()  # 删除
6 将其封装成类并进行相关的调用

思想: 将需要用户自己编写的东西放置在类的外边,其他的相关事件放置在类中

#!/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()  # 删除
7 使用默认的exc 对其进行相关的处理
#!/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()  # 删除
8 修改注册函数为装饰器
#!/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中的应用程序。但是这个应用程序已经变成了一个路由程序,处理逻辑已移动到了应用程序外了,而这部分就是留给程序员的部分。

3 正则匹配路由功能

目前实现的路由匹配,路径匹配非常死板,使用正则表达式改造。导入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()  # 删除

4 Request Method过滤

1 概念

请求方法,一般来说,既是是同一个URL,因为请求方法不同,处理方式也是不同的
假设一个URL。GET方法希望返回网页内容,POST方法表示浏览器提交数据过来需要处理并存储进数据库,最终返回给客户端存储成功或者失败信息,
换句话说,需要根据请求方法和正则同时匹配才能决定执行什么样的处理函数

2 方法和含义

方法 含义
GET 请求指定的页面信息,并返回报头和正文
HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件),数据被包含在请求正文中,POST请求可能会导致新的资源建立或者已有的资源的修改
PUT 从客户端向服务器端传递的数据取代指定的文档的内容
DELETE 请求服务器删除指定的内容

3 基础版

#!/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()  # 删除

web开发概述及基本框架书写

3 改进版

一个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()  # 删除

结果如下:

web开发概述及基本框架书写

5 路由功能的实现,分组捕获

1 动态增加属性至 request中

支持正则表达式的捕获,
在框架回调__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()  # 删除

2 路由分组

所谓的路由分组,就是按照前缀分别映射
需求
URL 为 /product/123456
需要将产品ID提取出来
分析
这个URL可以看做是一级分组路由,生产环境中可以使用了

product=Router('/product') #匹配前缀 /product
product.get('/(?P<id>\d+)') # 匹配路径为/product/123456
常见的一级目录
/admin #后台管理
/product 产品
这些目录都是/跟目录的下一级目录,暂时称为前缀prefix


如何建立prefix和URL 之间的隶属关系

一个prefix下可以有若干个URL。这些URL都是属于这个prefix中的
建立一个Router类,里面保存Prefix,同时保存URL和handler的关系

以前。注册的方法都是application的类方法,也就是所有映射信息都保存在一个类属性中ROUTABLE中,但是现在要为不同的前缀就是不同的实例,因此所有注册方法,都是实例的方法,路由包实例自己管理

application 中现在只需要保存所有注册的Router对象就行了,__call__方法依然是回调入口,在其中遍历所有的Router,找到路径匹配的Router实例,让Router实例返回Response 对象即可
代码如下

#!/usr/bin/poython3.6
#conding:utf-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()  # 删除

3 字典转属性类

通过此类,可使得kwargs这个字典,不使用[]访问元素,使用.号访问元素,如同属性一样访问

1 基本代码
#!/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)

结果如下

web开发概述及基本框架书写

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  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()  # 删除

6 正则表达式简化

1 问题和分析

问题
目前路由匹配使用正则表达式定义,不友好,很多用户不会使用正则表达式,能否简化

分析
生产环境中。URL是规范的,不能随意书写,路径是有意义的,尤其是对restful风格,所以,要对URL规范
如 product/111102243454343 ,这就是一种规范,要求第一段是业务,第二段是ID。

设计

路径规范化,如下定义
/student/{name:str}/{id:int}
类型设计。支持str,word,int,float,any类型
通过这种定义,可以让用户定义简化了,也规范了,背后的转换是编程者实现的

2 相关匹配规则

类型 含义 对应正则
str 不包含/的任意字符 [^/]+
word 字母和数字 \w+
int 纯数字,正负数 [+-]?\d+
float 正负号,数字,包含. [+-]?\d+.\d+
any 包含/的任意字符 .+

保存类型

类型 对应类型
str str
word str
int int
float float
any str

3 基本模块实现

#!/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))

结果如下
web开发概述及基本框架书写

4 合并代码如下

#!/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()  # 删除

5 小结

处理流程

客户端发起请求,被容器调度给Application的call
Application 中便利所有注册的router,router通过match来判断是否是自己处理的,通过查看request的请求来匹配注册前缀,若符合条件,则匹配对应的请求方法,若方法符合,则匹配对应的URL二级目录,并返回对应的函数,handler处理后,返回response,applicationde拿着这个response数据,返回给原始的wsgi。

7 拦截器

1 概念

拦截器,就是要在请求处理环节的某处加入处理,有可能是中断手续的处理


根据拦截点不同,分为:

1 请求拦截
2 响应拦截


根据影响面分为:
1 全局拦截
在application中拦截
2 局部拦截
在Router中拦截

相关图形
web开发概述及基本框架书写

前面的是application层面的拦截,及全局拦截,。后面是TOUTER层面的拦截,及局部拦截,

拦截器可以是多个,多个拦截器是顺序的

数据response前执行的的命名为preinterceptor ,之后的命名为postinterceptor。

2 加入拦截器功能的方式

1 application 和Router 类直接加入
把拦截器的相关方法,属性分别调价到相关的类中

2 Mixin
Application 和Router类都需要这个拦截器功能,这两个类没什么关系,可以使用Mixin方式,将属性,方法组合起来
但是,application类拦截器适合使用第二种方式,DNA是Router的拦截器每个实例都是不同的,所以使用第一种方式实现
当出现多继承时,Mixin中MRO规则会直接使用第一个,而忽略其他的__init__方法。

3 被拦截函数fn的设计,透明

拦截器的函数是相对独立的,其相当于是相对透明的,用一个的输出和N的输出都应该能够和handler进行处理

引入app,是为了以后从application上获取一些全局信息,其application的实例资源。
来的输入和输出都是request

def  fn(app,request:Request)->Request:
    pass

去的输入和输出都是response

def  fn(app,request:Request,response:Response)-&gt; Response:
pass 

4 上下文支持

1 概念

为了把一些应数据,配置数据,数据库连接提供给全局共享数据提供所有对象使用,增加一个字典,存储共享数据。将环境变量传递下去。


为了方便访问,提供字典的属性化访问的类,因为这个字典是可写的,和前面的类不一样。


application最多的应该做的是单实例模式,及就是一个实例的处理模式,若果是要用多实例,则需要使用信号量或其他进行处理

2 存储共享数据基本实例
class  Context(dict):  #继承内部类,使得类能够提供一种能力能够直接的属性访问方式,读取配置文件的能力
    def __getattr__(self, item):  # 通过.的方式进行访问
        try:
            return  self[item]  # 自己的字典访问方式给请求端
        except KeyError:  # 属性访问的方式导致的问题
            raise ArithmeticError('Attribe  {} Not Found'.format(item))
    def __setattr__(self, key, value):
        self[key]=value  #处理修改和添加问题
3 Router实例的上下文属性支持

Router没一个实例中增加上下文属性,实例自己使用
但是Router实例如何使用全局上下文
使用新的处理方法,每一个Router实例的上下文字典内部关联一个全局字典的引用,如果自己的字典找不到,就去全局寻找
那Router实例什么时候关联全局字典比较合适
在路由注册的时候即可

基本代码如下

class NestedContext(Context):  #继承上述属性,什么逻辑不一样就覆盖那个
    def __init__(self,globalcontext:Context=None):
        super().__init__()
        self.relate(globalcontext)
    def relate(self,globalcontext:Context=None):
        self.globalcontext=globalcontext
    def __getattr__(self, item):
        if item  in self.keys():
            return  self[item]
        return  self.globalcontext[item]

5 全局代码如下

#!/usr/bin/poython3.6
#conding:utf-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()  # 删除

8 可扩展功能

作为一个框架,更多的功能应该是从外部加入
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()  # 删除

9 模块化

在pycharm中创建一个包,包名为testweb
init.py文件中,修改Application为TestWeb

通过此种方式暴露类

class  TestWeb:
    # 类属性方法把类暴露出去
    Router=_Router
    Request=Request
    Response=Response
    NestedContext=NestedContext
    Context=Context

以供别人调用

外层新建app,将需要调用的都创建在app中实现,及就是使用此模块的人

目录

web开发概述及基本框架书写

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('访问资源不存在')

10 支持JSON格式数据返回

此处属于模块的附加功能

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()  # 删除

11 总结

1 熟悉WSGI的编程接口
2 强化模块化,类封装思想
3 增加分析业务的能力

这个框架基本剧本了WSGI WEB 框架的基本功能,其他框架都类似。
权限验证,SQL注入检测的功能使用拦截器过滤。

12 模块发布

在 testweb包外创建setup.py 在 testweb包内创建web文件
结构如下

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

web开发概述及基本框架书写

复制到另一个环境安装查看

web开发概述及基本框架书写

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI