Web框架高级功能之模板、拦截器、Json、打包

类Flask框架实现

模板

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <br>显示数据<\br>
    {{id}} {{name}} {{age}}
</body>
</html>

import re
from io import StringIO,BytesIO

d = {'id':5,'name':'tom','age':20}

class Template:
    _pattern = '{{([a-zA-Z0-9_]+)}}'
    regex = re.compile(_pattern)

    @classmethod
    def render(cls,template,data:dict):
        html = StringIO()

        with open(template,encoding='utf-8') as f:
            for line in f:
                start = 0
                newline = ''
                for matcher in cls.regex.finditer(line):
                    newline += line[start:matcher.start()]
                    print(matcher,matcher.group(1))
                    key = matcher.group(1)
                    tmp = data.get(key,'')
                    newline += str(tmp)
                    start = matcher.end()
                else:
                    newline += line[start:]
                html.write(newline)
            print(html.getvalue())
        html.close()#模板渲染
filename = 'index.html'
Template.render(filename,d)

jinja2

文档
官网 https://jinja.palletsprojects.com/en/2.10.x/
中文 http://docs.jinkan.org/docs/jinja2/

安装
pip install jinja2
pip install MarkupSafe

模板构建

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Magedu</title>
</head>
<body>
<ul>
        显示数据
    {% for id,name,age in userlist %}
    <li>{{loop.index}},{{id}},{{name}},{{age}}</li>
    {% endfor %}
</ul>
总共{{usercount}}人
</body>
</html>
#################加载模板代码如下示例###################
from jinja2 import Environment,PackageLoader,FileSystemLoader

# env = Environment(loader=PackageLoader('webarch','templates'))  #包加载器
env = Environment(loader=FileSystemLoader('webarch/templates'))

template = env.get_template('index.html')

userlist = [
    (3,'tpm',20),
    (4,'het',23),
    (7,'asdf',23),
    (1,'aasf',18)
]
d = {'userlist':userlist,'usercount':len(userlist)}
print(template.render(**d))
###############提供模板模块template.py##################
from jinja2 import Environment,PackageLoader,FileSystemLoader

env = Environment(loader=PackageLoader('webarch','templates'))  #包加载器
# env = Environment(loader=FileSystemLoader('webarch/templates'))  #文件系统加载器

def render(name,data:dict):
    """
    模板渲染
    :param name: 去模板目录搜索此模板名的文件
    :param data: 字典数据
    :return: HTML字符串
    """
    template = env.get_template(name) #搜索模块index.html
    return template.render(**data)
##############代码中增加##################

#创建Router对象
idx = Router()
py = Router('/python')
user = Router('/user')

#注册
App.register(idx,py)
App.register(user)

py.register_preinterceptor(ip)

@user.get(r'^/?$')
def userhandler(request):
    userlist = [
        (3, 'tpm', 20),
        (4, 'het', 23),
        (7, 'asdf', 23),
        (1, 'aasf', 18)
    ]
    d = {'userlist':userlist,'usercount':len(userlist)}
    return render('index.html',d)

模块化

 

1.创建包webarch
2.template.py模块移入此包
3.新建web.py模块,将AttrDict、Router、App类放入其中
4.将路由定义、注册代码、handler定义移入webarch/__init__.py中
5.在项目根目录下,建一个app.py,里面放入启动server的代码。

拦截器interceptor

#加入拦截器功能的方式
1、App和Router类直接加入
把拦截器的相关方法、属性分别添加到相关类中
实现简单
2、Mixin
App和Router类都需要这个拦截器功能,可以使用Mixin方式、将属性、方法组合进来,但是,App类拦截器适合使用第二种,但是Router的拦截器是每个实例不一样的,所以使用第一种方式实现

 

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

 

def fn(request:Request,response:Response) -> Response:
    pass

IP拦截

 

#创建Router对象
idx = Router()
py = Router('/python')
user = Router('/user')

#注册
App.register(idx,py)
App.register(user)

#ip拦截
def ip(request:Request):
    print(request.remote_addr,'~~~~~')
    print(type(request.remote_addr))
    if request.remote_addr.startswith('192.'):
        return request
    else:
        return None #返回None将截断请求

py.register_preinterceptor(ip)

Json支持

@py.get(r'^/{id}$')
def pythonhandler(request):
    userlist = [
        (3, 'tom', 20),
        (5, 'jerry', 16),
        (6, 'sam', 23),
        (8, 'kevin', 18)
    ]
    d = {'userlist':userlist,'usercount':len(userlist)}
    res = Response(json=d)
    return res

总结

 

完整代码

 

################webarch/__init__.py#####################
from webob import Request,Response
#模板
from .template import render
from .web import Router,App

#创建Router对象
idx = Router()
py = Router('/python')
user = Router('/user')

#注册
App.register(idx,py)
App.register(user)

#ip拦截
def ip(request:Request):
    print(request.remote_addr,'~~~~~')
    print(type(request.remote_addr))
    if request.remote_addr.startswith('192.'):
        return request
    else:
        return None #返回None将截断请求

py.register_preinterceptor(ip)

@idx.get(r'^/$')
@idx.route(r'^/{id:int}$')  #支持所有方法
def indexhandler(request):
    id = ''
    if request.groupdict:
        id = request.groupdict.id
    return '<h1>magedu.com{}欢迎你</h1>'.format(id)

@py.get(r'^/{id}$')
def pythonhandler(request):
    userlist = [
        (3, 'tom', 20),
        (5, 'jerry', 16),
        (6, 'sam', 23),
        (8, 'kevin', 18)
    ]
    d = {'userlist':userlist,'usercount':len(userlist)}
    res = Response(json=d)
    return res

@user.get(r'^/?$')
def userhandler(request):
    userlist = [
        (3, 'tpm', 20),
        (4, 'het', 23),
        (7, 'asdf', 23),
        (1, 'aasf', 18)
    ]
    d = {'userlist':userlist,'usercount':len(userlist)}
    return render('index.html',d)

 

###########webarch/web.py#############
import re
from webob import Request,Response
from webob.exc import HTTPNotFound
from webob.dec import wsgify

class AttrDict:
    def __init__(self,d:dict):
        self.__dict__.update(d if isinstance(d,dict) else {})

    def __setattr__(self, key, value):
        #不允许修改属性
        raise NotImplementedError

    def __repr__(self):
        return "<AttrDict {}>".format(self.__dict__)

    def __len__(self):
        return len(self.__dict__)

class Router:
    __regex = re.compile(r'/{([^{}:]+):?([^{}:]*)}')

    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
    }

    def __parse(self,src: str):
        start = 0
        repl = ''
        types = {}

        matchers = self.__regex.finditer(src)
        for i, matcher in enumerate(matchers):
            name = matcher.group(1)
            t = matcher.group(2)
            types[name] = self.TYPECAST.get(t, str)

            repl += src[start:matcher.start()]  #拼接分组前
            tmp = '/(?P<{}>{})'.format(
                matcher.group(1),
                self.TYPEPATTERNS.get(matcher.group(2), self.TYPEPATTERNS['str'])
            )
            repl += tmp  # 替换
            start = matcher.end()   #移动
        else:
            repl += src[start:]  # 拼接分组后的内容
        return repl, types

    #####实例
    def __init__(self,prefix:str=''):
        self.__prefix = prefix.rstrip('/\\')    #前缀,例如/product
        self.__routetable = []  #存四元组,列表,有序的

        ##拦截器##
        self.pre_interceptor = []
        self.post_interceptor = []
    ##拦截器注册函数##
    def register_preinterceptor(self,fn):
        self.pre_interceptor.append(fn)
        return fn

    def register_postinterceptor(self,fn):
        self.post_interceptor.append(fn)
        return fn

    def route(self,rule,*methods):    #注册路由
        def wrapper(handler):
            #/student/{name:str}/xxx/{id:int}  ==>  '/student/(?P<name>[^/]+/xxx/(?P<id>[+-]\\d+))'
            pattern,trans = self.__parse(rule)  # 用户输入规则转换为正则表达式
            self.__routetable.append(
                (tuple(map(lambda x:x.upper(),methods)),
                 re.compile(pattern),
                 trans,
                 handler
                 )#(方法元组,预编译正则对象,类型转换,处理函数)
            )
            return handler
        return wrapper

    def get(self,pattern):
        return self.route(pattern,'GET')

    def post(self,pattern):
        return self.route(pattern,'POST')

    def head(self,pattern):
        return self.route(pattern,'HEAD')

    def match(self,request:Request):
        #必须先匹配前缀
        if not request.path.startswith(self.__prefix):
            return None
        ##局部拦截,此Router的请求,开始拦截,处理request##
        for fn in self.pre_interceptor:
            request = fn(request)
            if not request:
                return None #请求为None将不再向后传递,截止

        #前缀匹配,说明就必须这个Router实例处理,后续匹配不上,依然返回None
        for methods,pattern,trans,handler in self.__routetable:
            # not methods表示一个方法都没有定义,就是支持全部方法
            if not methods or request.method.upper() in methods:
                #前提是以__prefix开头了,可以replace,去掉prefix剩下的才是正则表达式需要匹配的路径
                matcher = pattern.match(request.path.replace(self.__prefix,'',1))
                if matcher:#正则匹配
                    newdict = {}
                    for k,v in matcher.groupdict().items():
                        newdict[k] = trans[k](v)
                    #动态增加属性
                    request.groupdict =AttrDict(newdict)#命名分组组成的字典被属性化
                    response = handler(request)

                    ##局部拦截响应,依次拦截,处理响应##
                    for fn in self.post_interceptor:
                        response = fn(request,response)
                    return response

class App:
    _ROUTERS = []   #存储所有一级对象

    ##全局拦截器##
    PRE_INTERCEPTOR  = []
    POST_INTERCEPTOR  = []

    ##全局拦截器注册函数##
    @classmethod
    def register_preinterceptor(cls,fn):
        cls.PRE_INTERCEPTOR.append(fn)
        return fn

    @classmethod
    def register_postinterceptor(cls,fn):
        cls.POST_INTERCEPTOR.append(fn)
        return fn

    #注册路由
    @classmethod
    def register(cls,*routers:Router):
        for router in routers:
            cls._ROUTERS.append(router)

    @wsgify
    def __call__(self, request:Request):
        ##全局拦截请求##
        for fn in self.PRE_INTERCEPTOR:
            request = fn(request)

        #遍历_ROUTERS,调用Router实例的match方法,进行匹配
        for router in self._ROUTERS:
            response = router.match(request)

            ##全局拦截响应##
            for fn in self.POST_INTERCEPTOR:
                response = fn(request,response)

            if response:    #匹配返回非None的Router对象
                return response
        raise HTTPNotFound('<h1>你访问的页面不存在!</h1>')
#########webarch/template.py#############
from jinja2 import Environment,PackageLoader,FileSystemLoader

env = Environment(loader=PackageLoader('webarch','templates'))  #包加载器
# env = Environment(loader=FileSystemLoader('webarch/templates'))  #文件系统加载器

def render(name,data:dict):
    """
    模板渲染
    :param name: 去模板目录搜索此模板名的文件
    :param data: 字典数据
    :return: HTML字符串
    """
    template = env.get_template(name) #搜索模块index.html
    return template.render(**data)
###########app.py#########
from .web import App
from wsgiref.simple_server import make_server

if __name__ == '__main__':
    ip = '127.0.0.1'
    port= 9999
    server = make_server(ip,port,App())
    try:
        server.serve_forever() # server.handle_request() 一次
    except KeyboardInterrupt:
        server.shutdown()
        server.server_close()

发布

#######setup.py####
from distutils.core import setup
import glob

#导入setup函数并传参
setup(
    name='webarch',
    version='0.1.0',
    description='Python WSGI Web Framework',
    author='cy',
    url='https://www.magedu/com',
    packages=['webarch'],
    datafiles=glob.glob('webarch/templates/*.html')#返回列表
)

posted @ 2019-08-08 23:54  小鲨鱼~  阅读(616)  评论(0编辑  收藏  举报