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

一 基本概念

1 基本框架

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

2 CS 开发

web 也叫CS开发
CS 及客户端,服务器端编程
客户端,服务器端之间须要socket,约定协议,版本(每每使用的协议是TCP或UDP),指定地址和端口,就能够通讯了html

客户端,服务端传输数据,数据能够有必定的格式,双方必须约定好 前端

3 BS 编程

B=Browser,Browser是一种特殊的客户端,支持HTTP(S)协议,可以经过URL 向服务器端发起请求,等待服务端返回HTML等数据,并在浏览器内可视化展现程序 python

S=server,Server支持HTTP(S)协议,可以接受众多客户端发起的HTTP请求,通过处理,将HTML等数据返回给浏览器 mysql

本质上来讲,BS是一种特殊的CS,及客户端必须是一种支持HTTP协议且可以解析并渲染HTML的软件,服务端必须是可以接受客户端HTTP访问的服务器软件 程序员

HTTP 底层使用TCP传输,须要经过相应的规则来处理web

HTTP 超文本传输协议,在文本的基础上作一些突破,相关的渲染处理,断行等。使用标签的方式进行规定的处理,文本已经有了一个格式,浏览器中必须一种能力,支持这种文本的处理和渲染。正则表达式

浏览器必须支持HTTP协议,必须可以理解超文本并将其绘制出来 redis

BS 开发分为两端开发
1 客户端开发,或称为前端开发
2 服务端开发,python能够学习WSGI,Django,Flask,Tornado等sql

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 和相关请求及报文信息

详情请看:http://www.javashuo.com/article/p-cjqcyvrk-co.html

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='<h1>Hello  World</h1>'.encode()
    start_response("200 OK", [('Content-Type','text/html; charset=utf-8')])
    return [html]

server=make_server(ip,port,app) # 实例化一个websever
server.serve_forever()  # 启动
server.server_close() # 关闭
server.shutdown()  # 删除

结果以下

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='<h1>Hello  World</h1>'.encode()
    start_response("200 OK", [('Content-Type','text/html; charset=utf-8')])
    return [html]

server=make_server(ip,port,app) # 实例化一个websever
server.serve_forever()  # 启动
server.server_close() # 关闭
server.shutdown()  # 删除

请求URL: http://192.168.1.200/admin/?username=mysql&password=123456

结果以下:

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='<h1>Hello  World</h1>'.encode("utf-8")
    return  [html]

server=make_server(ip,port,app) # 实例化一个websever
server.serve_forever()  # 启动
server.server_close() # 关闭
server.shutdown()  # 删除
#!/usr/bin/poython3.6
#conding:utf-8
from   wsgiref.simple_server import  make_server
from  webob import  Request,Response

ip='192.168.1.200'
port=80

def app(environ,start_response):
    res=Response('<h1>Hello  World</h1>')
        # 写法二
    #res.body='<h1>Hello  Python</h1>'.encode()
    #res.status_code=200
    return  res(environ,start_response)

server=make_server(ip,port,app) # 实例化一个websever
server.serve_forever()  # 启动
server.server_close() # 关闭
server.shutdown()  # 删除

结果以下

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('<h1>hello  python  </h1>'.encode())

if __name__ == "__main__":
    server = make_server(ip, port, app)  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

结果以下
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='<h1>hello  World</h1>'.encode()
        return res
    elif  request.path=="/python":
        res.body='<h1>hello    Python</h1>'.encode()
        return res
    else:
        res.status_code=404
        res.body='<h1>Not Found</h1>'.encode()
        return res

if __name__ == "__main__":
    server = make_server(ip, port, app)  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>hello    Python</h1>'.encode()
    return res
def showdefault(request:Request):
    res=Response()
    res.body = '<h1>hello    World</h1>'.encode()
    return res

def show(request:Request):
    res=Response()
    res.status_code = 404
    res.body = '<h1>Not Found</h1>'.encode()
    return res

@dec.wsgify
def app(request:Request)->Response:
    if  request.path=="/":
        return showdefault(request)
    elif  request.path=="/python":
        return  showpython(request)
    else:
        return show(request)
if __name__ == "__main__":
    server = make_server(ip, port, app)  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>hello    Python</h1>'.encode()
    return res
def showdefault(request:Request):
    res=Response()
    res.body = '<h1>hello    World</h1>'.encode()
    return res

def show(request:Request):
    res=Response()
    res.status_code = 404
    res.body = '<h1>Not Found</h1>'.encode()
    return res

ROUTABLE={
    '/' :showdefault,
    '/python' :showpython
}

@dec.wsgify
def app(request:Request)->Response:
    return  ROUTABLE.get(request.path,show)(request)

if __name__ == "__main__":
    server = make_server(ip, port, app)  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>hello    Python</h1>'.encode()
    return res
def showdefault(request:Request):
    res=Response()
    res.body = '<h1>hello    World</h1>'.encode()
    return res

def show(request:Request):
    res=Response()
    res.status_code = 404
    res.body = '<h1>Not Found</h1>'.encode()
    return res

ROUTABLE={}

def  register(path,fn):
    ROUTABLE[path]=fn

register('/',showdefault)
register('/python',showpython)

@dec.wsgify
def app(request:Request)->Response:
    return  ROUTABLE.get(request.path,show)(request)

if __name__ == "__main__":
    server = make_server(ip, port, app)  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>Not Found</h1>'.encode()
        return res
    @classmethod
    def  register(cls,path,fn):
        cls.ROUTABLE[path]=fn
    @dec.wsgify
    def __call__(self,request: Request) -> Response:
        return self.ROUTABLE.get(request.path,self.show)(request)
def showpython(request:Request):
    res=Response()
    res.body = '<h1>hello    Python</h1>'.encode()
    return res
def showdefault(request:Request):
    res=Response()
    res.body = '<h1>hello    World</h1>'.encode()
    return res

Application.register('/',showdefault)
Application.register('/python',showpython)

if __name__ == "__main__":
    server = make_server(ip, port, Application())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>hello    Python</h1>'.encode()
    return res
def showdefault(request:Request):
    res=Response()
    res.body = '<h1>hello    World</h1>'.encode()
    return res

Application.register('/',showdefault)
Application.register('/python',showpython)

if __name__ == "__main__":
    server = make_server(ip, port, Application())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>hello    World</h1>'.encode()
    return res
@Application.register('/python')
def showpython(request:Request):
    res=Response()
    res.body = '<h1>hello    Python</h1>'.encode()
    return res
if __name__ == "__main__":
    server = make_server(ip, port, Application())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

到目前为止,一个框架的雏形基本完成了
application是WSGI中的应用程序。可是这个应用程序已经变成了一个路由程序,处理逻辑已移动到了应用程序外了,而这部分就是留给程序员的部分。

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 = '<h1>hello    World</h1>'.encode()
    return res
@Application.register('^/python$')
def showpython(request:Request):
    res=Response()
    res.body = '<h1>hello    Python</h1>'.encode()
    return res
if __name__ == "__main__":
    server = make_server(ip, port, Application())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>hello    World</h1>'.encode()
    return res
@Application.get('^/python$')
def showpython(request:Request):
    res=Response()
    res.body = '<h1>hello    Python</h1>'.encode()
    return res
@Application.post('^/python$')
def showpython(request:Request):
    res=Response()
    res.body = '<h1>hello    Python  POST </h1>'.encode()
    return res
@Application.post('^/')
def showdefault(request:Request):
    res=Response()
    res.body = '<h1>hello    World  POST </h1>'.encode()
    return res

if __name__ == "__main__":
    server = make_server(ip, port, Application())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>hello    World</h1>'.encode()
    return res
@Application.get('^/python')
def showpython(request:Request):
    res=Response()
    res.body = '<h1>hello    Python</h1>'.encode()
    return res
if __name__ == "__main__":
    server = make_server(ip, port, Application())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

结果以下:

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 = '<h1>hello    World</h1>'.encode()
    return res
@Application.get('^/python')
def showpython(request:Request):
    res=Response()
    res.body = '<h1>hello    Python</h1>'.encode()
    return res
if __name__ == "__main__":
    server = make_server(ip, port, Application())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>hello  World</h1>'.encode()
    return res

@pyth.get('/(\d+)')
def showpython(request:Request):
    res=Response()
    res.body = '<h1>hello  Python</h1>'.encode()
    return res

@admin.get('/(\d+)')
def showadmin(request:Request):
    res=Response()
    res.body = '<h1>hello  admin</h1>'.encode()
    return res
if __name__ == "__main__":
    server = make_server(ip, port, Application())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>hello  World</h1>'.encode()
    return res

@pyth.get('/(\d+)')
def showpython(request:Request):
    res=Response()
    res.body = '<h1>hello  Python</h1>'.encode()
    return res

@admin.get('/(\d+)')
def showadmin(request:Request):
    res=Response()
    res.body = '<h1>hello  admin</h1>'.encode()
    return res
if __name__ == "__main__":
    server = make_server(ip, port, Application())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>hello  World</h1>'.encode()
    return res

@pyth.get('/\d+')
def showpython(request:Request):
    res=Response()
    res.body = '<h1>hello  Python</h1>'.encode()
    return res

@admin.get('/\d+')
def showadmin(request:Request):
    res=Response()
    res.body = '<h1>hello  admin</h1>'.encode()
    return res
if __name__ == "__main__":
    server = make_server(ip, port, Application())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>hello  World</h1>'.encode()
    return res

@pyth.get('/\d+')
def showpython(request:Request):
    res=Response()
    res.body = '<h1>hello  Python</h1>'.encode()
    return res

@admin.get('/\d+')
def showadmin(request:Request):
    res=Response()
    res.body = '<h1>hello  admin</h1>'.encode()
    return res
if __name__ == "__main__":
    server = make_server(ip, port, Application())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>hello  World</h1>'.encode()
    return res

@pyth.get('/\d+')
def showpython(request:Request):
    res=Response()
    res.body = '<h1>hello  Python</h1>'.encode()
    return res

@admin.get('/\d+')
def showadmin(request:Request):
    res=Response()
    res.body = '<h1>hello  admin</h1>'.encode()
    return res
if __name__ == "__main__":
    server = make_server(ip, port, Application())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>hello  World</h1>'.encode()
    return res

@pyth.get('/\d+')
def showpython(request:TestWeb.Request):
    res=TestWeb.Response()
    res.body = '<h1>hello  Python</h1>'.encode()
    return res

@admin.get('/\d+')
def showadmin(request:TestWeb.Request):
    res=TestWeb.Response()
    res.body = '<h1>hello  admin</h1>'.encode()
    return res
if __name__ == "__main__":
    ip = '192.168.1.200'
    port = 80
    server = make_server(ip, port, TestWeb())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

testweb中_init_.py中的内容

#!/usr/bin/poython3.6
#conding:utf-8
from   wsgiref.simple_server import  make_server
from  webob import  Request,Response,dec,exc
import  re

class  DictObj:
    def __init__(self,d:dict): # 将属性中的元素添加到属性字典中去,可能会有冲突致使属性覆盖的问题
        if not  isinstance(d,dict):
            self.__dict__['_dict']={}  # 此处不能是双下划綫,设置类属性字典
        else:
            self.__dict__['_dict']=d  #将字典加入到实例属性列表中

    def __getattr__(self, item):  #此处是经过点号访问的
        try:
            return  self._dict[item]  # 经过d.x访问,若存在,则直接返回,若不存在,则抛出异常
        except KeyError: #当其键不存在的时候
            raise  AttributeError('Attribute  {}  Not Found'.format(item))
    def __setattr__(self, key, value):  #此处是点号修改的
        # 不容许设置属性,set表示未实现
        raise NotImplemented
class  Context(dict):  # 用于存储共享数据,app使用
    def __getattr__(self, item):
        try:
            return  self[item]
        except KeyError:
            raise ArithmeticError('Attribe  {} Not Found'.format(item))
    def __setattr__(self, key, value):
        self[key]=value
####################上述两种字典的不一样实现方式处理###########################

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

class  _Router:
    def __init__(self,prefix:str):
        self.__prefix=prefix.rstrip('/\\')  # 去除prefix及一级目录后面的\\和多余的/
        self.__routertable=[] #此处用于保存handler,pattern,method的信息

        self.ctx=NestedContext()  # 未绑定全局的上下文,在注册的时候进行处理

        #实例本身使用的拦截器。在match处进行拦截
        self.preinterceptor=[]
        self.postinterceptor=[]

    # 装饰器须要有返回值
    def reg_preinterceptor(self, fn):  # fn前半段两个参数,后半段三个参数,装饰器须要返回值
        self.preinterceptor.append(fn)
        return fn
    def reg_postinterceptor(self, fn):
        self.postinterceptor.append(fn)
        return  fn  #
    TYPEPATTERNS = {
        'str': r'[^/]+',
        'word': r'\w+',
        'int': r'[+-]?\d+',
        'float': r'[+-]?\d+.\d+',
        'any': r'.+'
    }
    TYPECAST = {
        'str': str,
        'word': str,
        'int': int,
        'float': float,
        'any': str
    }

    pattern = re.compile('/({[^{}:]+:?[^{}:]*})')  # 此处是提取相关用户信息的状况,此处匹配到的只是一级目录的相关信息
    def transfrom(self,kv: str):
        name, _, type = kv.strip('/{}').partition(':')  # 此处用于替换操作,此处返回一个列表,经过参数解构来收集,后面是找到第一个后进行分割操作
        return '/(?P<{}>{})'.format(name, self.TYPEPATTERNS.get(type, '\w+')), name, self.TYPECAST.get(type,
                                                                                             str)  # 此处的format只是构造name和对应正则匹配的字典,此处返回的是一个三元
    def parse(self,src: str):
        start = 0
        res = ''
        translator = {}
        while True:
            matcher = self.pattern.search(src, start)  # start表示偏移量
            if matcher:
                res += matcher.string[start:matcher.start()]  # 对匹配到的字符串进行切割处理
                tmp = self.transfrom(matcher.string[matcher.start():matcher.end()])  # 此处返回的是下一次匹配的结果的集合,此出返回一个三元组
                res += tmp[0]  # 此处保存的是名称和正则的元组
                translator[tmp[1]] = tmp[2]  # 此处保存的是名称和类型的字典
                start = matcher.end()  # 此处再次匹配,则须要进行初始化继续匹配的操作
            else:  # 若不能匹配,则返回
                break
        if res:  # 若存在,则返回
            return res, translator  # res中保存URL,translator中保存名称和类型的对应关系
        else:  # 若不存在,也返回
            return res, translator
    @property
    def prefix(self):
        return self.__prefix
    def  register(self,rule,*methods):  # 此处用于注册二级目录对应的值
        def  _register(handle):
            pattern,translator=self.parse(rule)  #此处经过对应的规则来处理相关配置,pattern中包含的是实际的URL路径,translator 中包含分组名称和对应类型的匹配
            self.__routertable.append((re.compile(pattern),translator,handle,methods))
            return  handle
        return  _register
    def get(self,path):
        return  self.register(path,'GET')
    def post(self,path):
        return  self.register(path,'POST')
    def head(self,path):
        return self.register(path,'HEAD')
    def match(self,request:Request):
        if not  request.path.startswith(self.__prefix):  #判断其是否URL一级目录匹配注册的prefix,若不匹配则返回为None
            return
        for fn  in  self.preinterceptor:  # 拦截器处理
            request=fn(self.ctx,request)

        for pattern,translator,hande,methods   in self.__routertable:  # 此处须要遍历
            if  not methods  or  request.method  in  methods:
                matcher=pattern.match(request.path.replace(self.prefix,"",1))
                if  matcher:  # 此处若能匹配到,则为True,则能够进行下一步
                    request.args=matcher.group()
                    request.kwargs=DictObj(matcher.groupdict())
                    newdict={}
                    for k,v  in matcher.groupdict().items():  # 此处返回分组名称和匹配值的字典,K是分组名称,V是匹配的结果
                        newdict[k]=translator[k](v) #分组匹配结果,经过分组的名称获取对应的类型进行对其值进行操做并保存
                    request.vars=DictObj(newdict)
                    response=hande(self.ctx,request)  #优先使用本身的属性
                    for  fn  in   self.postinterceptor:
                        response=fn(self.ctx,request,response)

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

    ROUTABLE=[]  # 此处修改为列表的形式比较适合顺序匹配
    ctx=Context()

    #实例的拦截器
    PREINTERCEPTOR=[]
    POSTINTERCEPTOR=[]

    @classmethod  # 增长扩展功能模块
    def extend(cls,name,ext):
        cls.ctx[name]=ext

    # 拦截器的注册
    @classmethod
    def  reg_preinterceptor(cls,fn):  # fn前半段两个参数,后半段三个参数
        cls.PREINTERCEPTOR.append(fn)
        return  fn
    @classmethod
    def reg_postinterceptor(cls,fn):
        cls.POSTINTERCEPTOR.append(fn)
        return fn # 函数须要返回,其自己并无变更
    def __init__(self,**kwargs):
        self.ctx.app=self
        for k,v  in kwargs.items():
            self.ctx[k]=v #添加注册功能

    @classmethod
    def  register(cls,router:Router):
        router.ctx.relate(cls.ctx)  #将上述的CTX添加进来,用于属性的访问控制及上述的NestedContext,将全局的上下文绑定给每个router实例
        # 其在router本身初始化时就本身建立
        router.ctx.router=router   #在本身的字典中中引用本身
        cls.ROUTABLE.append(router) # 此处用于调用上述的函数,完成数据的初始化并传递相关的参数prefix参数

    @dec.wsgify
    def __call__(self,request: Request) -> Response:
        for fn  in  self.PREINTERCEPTOR:  # 注册函数,
            request=fn(self.ctx,request)  #第一个是全局的,第二个是本身的,定义的,须要request不变透明化
            #fn(self.ctx,request) 此处此种写法容易引发别人的误会

        for  router  in  self.ROUTABLE:  # 遍历router传输相关参数
            response=router.match(request) # 此处返回为handler的函数值
            if response:
                #返回的函数进行处理
                for  fn  in  self.POSTINTERCEPTOR:  # 此到处理response相关的方法
                    response=fn(self.ctx.request,response)

                return  response
        raise   exc.HTTPNotFound('访问资源不存在')

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 = '<h1>hello  World</h1>'.encode()
    return res

@pyth.get('/\d+')
def showpython(NestedContext,request:TestWeb.Request):
    res=TestWeb.Response()
    res.body = '<h1>hello  Python</h1>'.encode()
    return res

@admin.get('/\d+')
def showadmin(NestedContext,request:TestWeb.Request):
    res=TestWeb.Response()
    res.body = '<h1>hello  admin</h1>'.encode()
    return res
if __name__ == "__main__":
    ip = '192.168.1.200'
    port = 80
    server = make_server(ip, port, TestWeb())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

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 = '<h1>hello  World</h1>'.encode()
    return res

@pyth.get('/\d+')
def showpython(NestedContext,request:TestWeb.Request):
    res=TestWeb.Response()
    res.body = '<h1>hello  Python</h1>'.encode()
    return res

@admin.get('/\d+')
def showadmin(NestedContext,request:TestWeb.Request):
    res=TestWeb.Response()
    res.body = '<h1>hello  admin</h1>'.encode()
    return res
if __name__ == "__main__":
    ip = '192.168.1.200'
    port = 80
    server = make_server(ip, port, TestWeb())  # 实例化一个websever
    try:
        server.serve_forever()  # 启动
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()  # 关闭
        server.shutdown()  # 删除

setup.py 内容

#!/usr/bin/poython3.6
#conding:utf-8

from  distutils.core  import  setup

setup(
    name='testweb', # 名字
    version='0.1.0',  #版本
    description='testweb',  #打包列表
    author='zhang', # 做者
    author_email='12345678910@163.com',  #
    # url 表示包帮助文档路径
    packages=['testweb']

)

打包

python setup.py sdist

安装

pip install dist/test

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

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

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

相关文章
相关标签/搜索