Web框架高級功能之模板、攔截器、Json、打包

類Flask框架實現html

模板python

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

jinja2web

文檔
官網 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)

模塊化json

 

1.建立包webarch
2.template.py模塊移入此包
3.新建web.py模塊,將AttrDict、Router、App類放入其中
4.將路由定義、註冊代碼、handler定義移入webarch/__init__.py中
5.在項目根目錄下,建一個app.py,裏面放入啓動server的代碼。

攔截器interceptorapp

#加入攔截器功能的方式
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

總結函數

 

完整代碼oop

 

################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')#返回列表
)

相關文章
相關標籤/搜索