首页 > 代码库 > 框架1

框架1

目的

实现一个简单的web框架 只实现rest(有一整套严格的规范)

常见框架有 : Rails pyramid bottle web.py flask

理解框架实现原理

 

动态网页的发展历程
最早的时候,web就是一些静态文件

最早的动态页面是CGI实现的(动态生成返回的内容)

 

CGI怎么工作? 

当请求一个地址的时候,http服务器就会启动一个外部程序,并且把外部程序的输出返回给用户。

 

CGI协议规范了http和外部程序的通信方式
通信方式:
输入:通过环境变量   http设置环境变量
输出:通过标准输出   http服务器截获外部程序的输出作为输出的

 

CGI缺陷:并发模式是多进程(每一次请求都启动一个进程),而且进程由http服务器管理造成效

率低(http不仅要提供服务,还要提供进程管理),崩溃的时候处理起来很麻烦,造成性能低下。

 

使用CGI开发并不慢

 

CGI发展--->FastCGI(把进程管理交给其他的后台服务来处理)
所以http只需要和FastCGI 通信就行了

http服务器只需要把http协议转化成fastCGI协议,然后通过socket
发送给fastCGI daemon(后台进程)

例如:php fmp

 

使得并发模型变得多种多样(perl线程池,php进程池)进程管理由fastCGI实现

http服务器只需要管理http请求和响应就行了

单一原则:只做擅长的事情

 

PEP3333定义WSGI
另一个发展方向-->wsgi通用web服务网关接口
分成两部分:容器(也有叫网关或服务器) 容器实现协议转换http-->WSGI
                 应用 处理业务逻辑

 

容器和应用怎么通信? 
FastCGI --> 通过socket通信

uWSGI 是一个WSGI的容器
WSGI的容器和应用通过函数调用通信

 

什么时候?
同一个进程内才能经过函数调用来通信

也就是说WSGI容器和引用必须是跑在同一进程/线程内的

并不是同一进程就不能实现多进程

 

类比

FastCGI:可以理解http服务器就是容器,Fast daemon就是应用
二者之间通过socket通信

gunicorn uWSGI 容器 都可以实现多进程
多线程 协程 见WSGI官网或者PEP3333

 

一个WSGI application 应该是一个callable对象

 1 def application(environ,start_response): # 传递的参数有规定:环境 回调函数
 2     body=hello,world
 3     status =200 ok   ---http协议状态码的规范
 4     headers=[
 5        (content-type,text/plain),  # 每一项都是一个二元组
 6        (content-length,str(len(body)))  #头信息必须都是 str
 7     ]
 8     start_response(status,headers)  # 代表开始响应了    返回头部信息
 9     return [body.encode()]  可以传递多行 ---WSGI解析的时候是迭代的,所以返回列表
10                    要是bytes的可迭代对象

http是支持流式处理 使用yield也可以

 

此时是跑不起来的 因为没有容器
python标准库里面有容器的实现 wsgiref  传递的应用必须要是一个可调用对象

技术分享
1 if __name__ == __main__:
2     from wsgiref.simple_server import make_server 创建容器的实例
3     server=make_server(0.0.0.0,3000,application)
4     try:
5         server.server_forver()
6     except KeyboardInterrupt:
7         server.shutdown()
View Code

environ 请求的内容 是一个字典 本地的和服务端的信息都在里面
start_response 必须是callable对象 代表开始响应

output分为两部分 start_response是返回头部信息 

return是返回body体

 

environ和start_response两个参数都是容器传进来的

 

gunicron -b 0.0.0.0 文件名:应用名
--threads INT
--help


开发的时候用wsgiref使用方便 生产的用gunicron性能好

 

QUERY_STRING 里面存储的是用户传递来的参数
127.0.0.1:5000/?name=wanglei&age=15  -->qs = name=wanglei&age=15  解析这些东西就可以得到参数了

environ.get(‘QUERY_STRING‘) # 获得 qs

 

怎么解析?

 

解析出来的值是list,因为可能存在多个值

from urllib.parse import parse_qs

def application(environ, start_response):
    parm = parse_qs(environ.get(‘QUERY_STRING‘)) # 解析出来的是字典
    name = parm.get(name, [unknown‘])[0]  # 因为得到的是一个列表
    body = "hello,world {}".format(name)
    status = 200 ok
    headers = [(content-type, text/html), 
               (content-length, str(len(body)))
               ]
    start_response(status, headers)
    return [body.encode()]

http://127.0.0.1:9000/hell0/?name=wanglei&age=22

parse_qs(‘name=wanglei&age=13&name=duan‘)
{‘age‘:[13],‘name‘:[‘wanglei‘,‘duan‘]}

 

结果---> hello,world wanglei

这样是比较危险的 可能产生XSS攻击

http://127.0.0.1:9000/hell0/?name=<script type=‘text/javascript‘>alert(‘fuck it‘);</script>

 技术分享

 

 1 from urllib.parse import parse_qs
 2 from html import escape
 3 
 4 
 5 def application(environ, start_response):
 6     parm = parse_qs(environ.get(QUERY_STRING))
 7     name = parm.get(name, unknown)[0]
 8     body = "hello,world {}".format(escape(name)) # 进行转义
 9     status = 200 ok
10     headers = [(content-type, text/html),
11                (content-length, str(len(body)))
12                ]
13     start_response(status, headers)
14     return [body.encode()]

网页结果:

技术分享

就会进行转义把它当做普通的文本处理

通过qs 就可以实现客户端和服务端通信 

 

层次太低 太底层的系统,需要进行封装

 

可以将environ 抽象成 request对象
将start_response和return抽象成response对象

 

封装Request对象

import os

class Request:
    def __init__(self, environ):
        self.params = parse_qs(environ.get(QUERY_STRING)) # 参数
        self.path = environ.get(PATH_INFO)
        self.method = environ.get(REQUEST_METHOD) # 请求方法
        self.body = environ.get(wsgi.input) # body内容
        self.headers = {}
        server_env = os.environ
        for k, v in environ.items(): # headers
            if k not in server_env.keys(): # 过滤掉服务器端的信息
                self.headers[k.lower()] = v
def application(environ, start_response):
    request = Request(environ)
    name = request.params.get(name, [unknown‘])[0]
    body = "hello,world {}".format(escape(name))
    status = 200 ok
    headers = [(content-type, text/html),
               (content-length, str(len(body)))
               ]
    start_response(status, headers)
    return [body.encode()]

封装Response对象

class Response:
    STATUS={
          200:ok
          404:Not found
    }
    def __init__(self, body=None):
        if body is None:
            self.body=‘‘
        self.body = body
        self.status=200 ok
        self.headers={
            content-type‘:text/html,
             content-length‘:str(len(body))
        }

    def set_body(self,body):
        self.body=body
        self.headers[content-length]=str(len(self.body))

  

   def set_header(self,name,value):
      self.headers[name]=value

def set_status(self,status_code):
        if status_code in self.STATUS.keys():
            self.status={} {}.format(status_code,self.STATUS[status_code])def set_header(self,name,value):
        self.headers[name]=value

    def __call__(self,start_response):
        start_response(self.status,[(k,v) for k,v in self.headers.items()]) # 列表解析生成 headers
        return [self.body.encode()]
def application(environ, start_response):
    request=Request(environ)
    name = request.parms.get(name, [None])[0]
    body = "hello,world %s" % escape(name)
    return Response(body)(start_response) # 返回给容器
def set_status(self,status_code,status_text=‘‘):
    self.status={} {}.format(status_code,self.STATUS.get(status_code,status_text))
        允许非标准的状态码

为什么要用__call__?       最后返回给容器要是一个可调用对象

 

很显然不能这么简单的封装

使用webob 类似 flask里面的werzeug都是对请求和响应的封装

webob 现在是 pylons的一个子项目 国外流行


webob不是web框架,只是以面向对象的方式封装WSGI
请求和响应 只做这一件事

非标状态码 Nginx 499
request.parms.getall() 返回所有的 而get 返回最后一个

from webob import Request, Response

def application(environ, start_response):
    request = Request(environ)
    name = request.params.get(name, unknown) # 有多个值返回最后一个
    body = "hello,world {}".format(name)
    return Response(body)(environ,start_response) # 使用webob 返回的时候要加入 environ 和start_response 两个参数

 webob提供了一个装饰器

以前:

from webob import Request, Response

def application(environ, start_response):
    request = Request(environ)
    name = request.params.get(name, unknown)
    body = "hello,world {}".format(name)
    return Response(body)(environ,start_response)

现在:

from webob import Request, Response
from webob.dec import wsgify

@wsgify
def app1(request: Request) -> Response:
    name = request.params.get(name, unknown)
    body = "hello,world {}".format(name)
    return Response(body)

小图标问题

@wsgify
def app1(request: Request) -> Response:
    if request.path == /favicon.ico:
        resp = Response(content_type=img/x-icon) # 小图标的content_type
        resp.body_file = open(./favicon.ico, rb) # 图片不能使用 utf8来解析 所以图片一定要是rb模式
        return resp
    name = request.params.get(name, unknown)
    body = "hello,world {}".format(escape(name))
    return Response(body)

或者

@wsgify
def app1(request: Request) -> Response:
    if request.path == /favicon.ico:  
        with open(./favicon.ico,rb) as f:  
            resp = Response(body=f.read(),content_type=img/x-icon)
            return resp
    name = request.params.get(name, unknown)
    body = "hello,world {}".format(escape(name))
    return Response(body)

显然以这种方式来匹配url不靠谱

 

怎么处理路由

@wsgify
def app1(request: Request) -> Response:
    if request.path == /favicon.ico:
        return favicon(request)
    if request.path.endswith() == /hello:
        return hello(request)
    if request.path.startswith() == /:
        return index(request)

def index(request):
    return Response("index page")

def hello(request):
    name = request.params.get(name, unknown)
    body = "hello,world {}".format(escape(name))
    return Response(body)
@wsgify  # 使用正则
def app1(request: Request) -> Response:
    if re.match(r‘^/favicon$, request.path):
        return favicon(request)
    if re.match(r‘^/hello/$, request.path):
        return hello(request)
    if re.match(r‘/$‘, request.path):
        return index(request)  

用类来重写application

from webob import Response
from webob.dec import wsgify
import re


class Application:
    def __init__(self):
        self.routes = []

    def route(self, rules, handler):
        self.routes.append((rules, handler))

    @wsgify
    def __call__(self, request):
        for rule,handler in self.routes:
            if re.match(rule,request.path):
                return handler(request)


def index(request):
    return Response("index page")


def hello(request):
    name = request.params.get(name, unknown)
    body = "hello,world {}".format(ename)
    return Response(body)


def favicon(request):
    with open(./favicon.ico, rb) as f:
        resp = Response(body=f.read(), content_type=img/x-icon)
        return resp


if __name__ == __main__:
    from wsgiref.simple_server import make_server
application
=Application() application.route(r‘/favicon.ico‘,favicon) # 注册路由 application.route(r‘/hello‘,hello) application.route(r‘/$‘,index) server = make_server(0.0.0.0, 9000, application) try: server.serve_forever() except KeyboardInterrupt: server.shutdown()
实现装饰器来注册路由

from
webob import Response from collections import namedtuple from webob.dec import wsgify import re Route = namedtuple(Route, (pattern, methods, handler)) class Application: def __init__(self): self.routes = [] def _route(self, rules, methods, handler): self.routes.append(Route(rules, methods, handler)) def router(self, pattern, methods=None): if methods is None: # 不传递该参数表示可以是任意方法 methods = [GET, POST, HEAD, OPTION, DELETE‘,‘HEAD‘,‘PUT‘] def dec(handler): self._route(pattern, methods, handler) return handler return dec @wsgify def __call__(self, request): for route in self.routes: if request.method in route.methods or request.method==route.methods: if re.match(route.pattern, request.path): return route.handler(request) app = Application() @app.router(r/, methods=GET) def index(request): return Response("index page") @app.router(rhello, methods=GET) def hello(request): name = request.params.get(name, unknown) body = "hello,world {}".format(escape(name)) return Response(body) @app.router(rfavicon) def favicon(request): with open(./favicon.ico, rb) as f: resp = Response(body=f.read(), content_type=img/x-icon) return resp if __name__ == __main__: from wsgiref.simple_server import make_server server = make_server(0.0.0.0, 9000, app) try: server.serve_forever() except KeyboardInterrupt: server.shutdown()

 

预先将正则表达式编译好
from
webob import Response from collections import namedtuple from webob.dec import wsgify import re Route = namedtuple(Route, (pattern, methods, handler)) class Application: def __init__(self): self.routes = [] def _route(self, rules, methods, handler): self.routes.append(Route(re.compile(rules), methods, handler)) def router(self, pattern, methods=None): if methods is None: methods = [GET, POST, HEAD, OPTION, DELETE, HEAD] def dec(fn): self._route(pattern, methods, fn) return fn return dec @wsgify def __call__(self, request): for route in self.routes: if request.method in route.methods or request.method == route.methods: if route.pattern.match(request.path): return route.handler(request) app = Application() @app.router(r^/$, methods=GET) def index(request): return Response("index page") @app.router(r^/hello$, methods=GET) def hello(request): name = request.params.get(name, unknown) body = "hello,world {}".format(name) return Response(body) @app.router(rfavicon) def favicon(request): with open(./favicon.ico, rb) as f: resp = Response(body=f.read(), content_type=img/x-icon) return resp if __name__ == __main__: from wsgiref.simple_server import make_server server = make_server(0.0.0.0, 9000, app) try: server.serve_forever() except KeyboardInterrupt: server.shutdown()

 

如果想要将app一些全局的东西传递进来

from webob import Response
from collections import namedtuple
from webob.dec import wsgify
import re

Route = namedtuple(Route, (pattern, method, handler))

class Application:
    def __init__(self, **options):
        self.routes = []
        self.options = options  # 将要传递的参数 保存在 options里面

    def _route(self, rules, methods, handler):
        self.routes.append(Route(re.compile(rules), methods, handler))

    def router(self, pattern, methods=None):
        if methods is None:
            methods = [GET, POST, HEAD, OPTION, DELETE, HEAD]
        def dec(fn):
            self._route(pattern, methods, fn)
            return fn

        return dec

    @wsgify
    def __call__(self, request):
        for route in self.routes:
            if request.method in route.methods or request.method == route.methods:
                if route.pattern.match(request.path):
                    return route.handler(request, self) # 将对象本身传递进视图函数


app = Application(debug=True)  # 如果想要传递一些全局的变量到 视图里面


@app.router(r^/$, methods=GET)
def index(request, ap):
    if ap.options.get(debug): # 根据传递进来的参数做一些事情
        for k, v in request.headers.items(): 
            print({}===>{}.format(k, v))
    return Response("index page")


@app.router(r^/hello$, methods=GET)
def hello(request, ap):
    name = request.params.get(name, unknown)
    body = "hello,world {}".format(name)
    return Response(body)


@app.router(r^/favicon$)
def favicon(request, ap):
    with open(./favicon.ico, rb) as f:
        resp = Response(body=f.read(), content_type=img/x-icon)
        return resp


if __name__ == __main__:
    from wsgiref.simple_server import make_server

    server = make_server(0.0.0.0, 9000, app)
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        server.shutdown()

是不是单线程看容器  这样写应用就不需要关注多进程 多线程等

gunicron -w 8    8个进程

 

web框架主要功能
request解析
Response封装
路由

rest 是不处理cookie和session  模板等

 

路由是web框架最重要的部分

 

exc 里面都是异常,基本上每一个对应一个状态码

exc.HTTPTemporaryRedirect(location)
临时跳转

 

框架1