封装框架1
Posted escapist
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了封装框架1相关的知识,希望对你有一定的参考价值。
目的:
1、学习web框架是如何工作的
2、如何编写用户友好的API 除了功能性能 就是友好
先掌握框架的核心内容
框架 大同小异
WSGI 是Python的标准 也需要容器
servlet 是Java的规范 需要容器 tomcat
也有的不是WSGI规范 例如 tornado
从头实现一个框架 python web框架很简单
已有的框架(这些功能) 通过文档都可以自己学习
从原理上看框架是如何实现的
我们会被大框架的辅助性功能所干扰
最简单的wsgi应用
environ参数:是客户端和服务器端的环境,由容器传递给应用
start_response函数:传递应用要返回的头信息
return的内容就是 body体
只能是这两个参数是容器与应用通信的 是位置参数,名字可以取别的
def application(environ, start_response): start_response(\'200 ok\', [(\'Content-Type\', \'text/plain\')]) # 返回文本格式 return ["hello,world".encode()] # 返回的要是bytes的可迭代对象 if __name__ == \'__main__\': from wsgiref.simple_server import make_server server = make_server(\'0.0.0.0\', 4000, app=application) # Python自带的容器 try: server.serve_forever() except KeyboardInterrupt: server.shutdown()
这两个参数是容器传递给应用的(位置参数)
协议规范的两个参数
了解environ参数内容
# 了解environ def application(environ, start_response): for k, v in environ.items(): print(\'{}--->{}\'.format(k, v)) start_response(\'200 ok\', [(\'Content-Type\', \'text/plain\')]) return ["hello,world".encode()] if __name__ == \'__main__\': from wsgiref.simple_server import make_server server = make_server(\'0.0.0.0\', 4000, app=application) try: server.serve_forever() except KeyboardInterrupt: server.shutdown()
容器是干什么的?
容器是用来做协议转换的
http协议---->转换为WSGI协议
tomcat 也是把http-->servlet 协议
协议只是应用程序调用的规范
主要关心客户端传来的信息
import os def application(environ, start_response): for k, v in environ.items(): if k not in os.environ.keys(): print(\'{}--->{}\'.format(k, v)) start_response(\'200 ok\', [(\'Content-Type\', \'text/plain\')]) return ["hello,world".encode()]
REMOTE_ADDR--->127.0.0.1 CONTENT_LENGTH---> wsgi.version--->(1, 0) wsgi.multiprocess--->False wsgi.errors---><_io.TextIOWrapper name=\'<stderr>\' mode=\'w\' encoding=\'UTF-8\'> REMOTE_HOST---> SERVER_NAME--->DESKTOP-16F3VS1 wsgi.url_scheme--->http wsgi.file_wrapper---><class \'wsgiref.util.FileWrapper\'> PATH_INFO--->/favicon.ico HTTP_PRAGMA--->no-cache wsgi.run_once--->False HTTP_CACHE_CONTROL--->no-cache CONTENT_TYPE--->text/plain REQUEST_METHOD--->GET HTTP_ACCEPT_ENCODING--->gzip, deflate, sdch, br SERVER_SOFTWARE--->WSGIServer/0.2 HTTP_ACCEPT--->image/webp,image/*,*/*;q=0.8 QUERY_STRING---> HTTP_USER_AGENT--->Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/57.0.2987.98 Safari/537.36 GATEWAY_INTERFACE--->CGI/1.1 wsgi.input---><_io.BufferedReader name=844> SERVER_PROTOCOL--->HTTP/1.1 SERVER_PORT--->4000 HTTP_CONNECTION--->keep-alive HTTP_HOST--->127.0.0.1:4000 HTTP_COOKIE--->csrftoken=0SMasfbAKS3PQIfs2p6JBoRfbphIHVF0; _ga=GA1.1.522430974.1483446120 wsgi.multithread--->True SCRIPT_NAME---> HTTP_ACCEPT_LANGUAGE--->zh-CN,zh;q=0.8 HTTP_REFERER--->http://127.0.0.1:4000/?name=wanglei&age=23&name=duanyu
解析QUERY_STRING
from urllib.parse import parse_qs def application(environ, start_response): query_string = environ.get(\'QUERY_STRING\') params = parse_qs(query_string) name = params.get(\'name\', [\'unknown\'])[0] # 不管是不是单个值得到的值是列表 print(name) start_response(\'200 ok\', [(\'Content-Type\', \'text/plain\')]) # headers return ["hello,world {}".format(name).encode()]
http ---> 代表schema
127.0.0.1 ---> 代表host
8000 -----> 代表port
/... ----> 代表path
?name=wanglei ---> 代表query_string
get请求不带body 其请求参数通过path或者query_string传递
容器:线程/进程管理 生产中gunicorn容器很好 uwsgi容器必须要和nginx配合才能使用
协议转换
输入转化为 environ 输出
头信息用start_response返回 body用return 返回
输入输出都有协议转换 是应用返回 不是容器返回
可能产生XSS攻击
from urllib.parse import parse_qs def application(environ, start_response): query_string = environ.get(\'QUERY_STRING\') params = parse_qs(query_string) name = params.get(\'name\', [\'unknown\'])[0] print(name) start_response(\'200 ok\', [(\'Content-Type\', \'text/html\')]) # 当Content-Type 是 \'text/html\' 小心XSS攻击
return ["hello,world {}".format(name).encode()]
url : http://127.0.0.1:4000/?name=<script type=\'text/javascript\' >alert(\'fuck%20it\');</script>
from urllib.parse import parse_qs from html import escape def application(environ, start_response): query_string = environ.get(\'QUERY_STRING\') params = parse_qs(query_string) name = params.get(\'name\', [\'unknown\'])[0] print(name) start_response(\'200 ok\', [(\'Content-Type\', \'text/html\')]) return ["hello,world {}".format(escape(name)).encode()] # escape函数将字符进行转义
webob封装Request对象 要传入environ参数
from webob import Request from html import escape def application(environ, start_response): request = Request(environ) # Request接受环境作为参数 params = request.params # 解析后的 qs name = params.get(\'name\', \'unknown\') # 唯一的区别是取列表的 最后一个 http://127.0.0.1:4000/?name=wanglei&name=duanyu print(name) start_response(\'200 ok\', [(\'Content-Type\', \'text/html\')]) return ["hello,world {}".format(escape(name)).encode()] # 结果是 hello,world duanyu
webob封装Response对象
而且不用使用escape来转义字符(内部已经做了处理)
from webob import Request, Response def application(environ, start_response): request = Request(environ) params = request.params name = params.get(\'name\', \'unknown\') response = Response() response.text = \'<h1>hello,world {}</h1>\'.format(name) # 设置body体 response.status_code = 200 # 设置状态码 response.content_type = \'text/html\' # 设置头信息 return response(environ, start_response) # 最后必须要这样返回
设置的头信息 可以不通过start_response里面来设置了 Response对象返回的时候需要传递 environ 和 start_response 参数
以面向对象的方式来封装输入输出
使用wsgify装饰器
作用: 1. 应用的参数只需要request, 返回Response即可
2.返回的时候不用再给Response传递 environ 和start_response 参数了
from webob import Request, Response from webob.dec import wsgify @wsgify def application(request: Request) -> Response: params = request.params name = params.get(\'name\', \'unknown\') response = Response() # 只需要处理视图函数的最后返回一个 Response对象即可 response.text = \'<h1>hello,world {}</h1>\'.format(name) response.status_code = 200 response.content_type = \'text/html\' return response
实现wsgify装饰器
from webob import Request, Response from functools import wraps def wsgify(fn): @wraps(fn) def wrapper(environ, start_response): # 装饰器中被装饰的函数不需要和装饰的函数参数保持一致 request = Request(environ) response = fn(request) # 相当于先执行下面的application函数 return response(environ, start_response) # 相当于在这里面给 response传参数 return wrapper @wsgify def application(request: Request) -> Response: params = request.params name = params.get(\'name\', \'unknown\') print(name) response = Response() response.text = \'<h1>hello,world {}</h1>\'.format(name) response.status_code = 200 response.content_type = \'text/html\' return response
最基本的路由:
根据不同的请求处理不同的逻辑
from webob import Request, Response from webob.dec import wsgify @wsgify def application(request: Request) -> Response: response = Response() if request.path == \'/hello\': response.text = \'<h1>hello page</h1>\' response.status_code = 200 response.content_type = \'text/html\' elif request.path == \'/\': response.text = \'<h1>index page </h1>\' response.status_code = 200 response.content_type = \'text/html\' return response
路由的基本思想
一个URL----->函数 将URL和视图函数分开写
静态路由: 现在开发者只需要关系怎么写应用就可以了
from webob import Request, Response from webob.dec import wsgify def hello(request: Request) -> Response: response = Response() response.text = \'<h1>hello page </h1>\' response.status_code = 200 response.content_type = \'text/html\' return response def index(request: Request) -> Response: return Response(body=\'<h1>index page</h1>\', status=200, content_type=\'text/html\') routes = { \'/hello\': hello, # 路由和函数映射的字典 \'/\': index } @wsgify def application(request: Request) -> Response: return routes[request.path](request) # --->注意这里要传递参数给视图函数
动态路由:
手动添加URL和视图函数的对应关系
from webob import Request, Response from webob.dec import wsgify def hello(request: Request) -> Response: response = Response() response.text = \'<h1>hello page </h1>\' response.status_code = 200 response.content_type = \'text/html\' return response def index(request: Request) -> Response: return Response(body=\'<h1>index page</h1>\', status=200, content_type=\'text/html\') class Application: routes = {} @classmethod def register(cls, path, handler): cls.routes[path] = handler @wsgify def __call__(self, request): return self.routes[request.path](request) if __name__ == \'__main__\': from wsgiref.simple_server import make_server Application.register(\'/hello\', hello) # 注册路由 Application.register(\'/\', index) server = make_server(\'0.0.0.0\', 4000, app=Application()) # 这里要特别注意 当函数调用的时候才会 执行__call__方法体的内容 try: server.serve_forever() except KeyboardInterrupt: server.shutdown()
可以实现动态注册路由了 route字典 也就不需要了,不需要事先将对应关系写好了
改写动态路由:用类来改写
def index(request: Request) -> Response: response = Response(body=\'hello,world\', content_type=\'text/html\', ) return response def hello(request: Request) -> Response: params = request.params name = params.get(\'name\', \'unknown\') response = Response() response.text = \'hello,world {}\'.format(name) response.status_code = 200 response.content_type = \'text/plain\' print(params) return response
class Application: route = {} # 路由和处理函数的对应关系 @classmethod def register(cls, path, handler): # 注册路由函数 cls.route[path] = handler @classmethod def default_handler(cls, request: Request) -> Response: #自定义 找不到路由时的处理方法 return Response(body=\'Not found\', status=404) @wsgify def __call__(self, request: Request) -> Response: return self.route.get(request.path, self.error_handler)(request) # 没有注册的url就返回默认的 handler
webob中已经有了错误的方法 不需要自己编写404错误的函数
from webob import exc
@classmethod def error_handler(cls, request: Request) -> Response: # webob 的错误 raise exc.HTTPNotFound("NOT FOUND")
可以改写为如下,没必要再定义一个错误处理函数,直接出错了就捕获 并且抛出404
class Application: route = {} @classmethod def register(cls, path): def inner(handler): cls.route[path] = handler return handler return inner @wsgify def __call__(self, request: Request) -> Response: try: return self.route[request.path](request) # 这里就不需要默认值了 except KeyError: raise exc.HTTPNotFound(\'not found .......\') # 没有注册的url就抛出异常 执行下面的
我们发现每次都要手动注册url,可以实现在定义视图函数的时候绑定url(装饰器的方式)
class Application: route = {} @classmethod def register(cls, path): # 用装饰器改写注册方法,实现最便捷的注册路由 def inner(handler): cls.route[path] = handler return handler return inner @wsgify def __call__(self, request: Request) -> Response: try: return self.route[request.path](request) # 也可以使用 反射 except KeyError: raise exc.HTTPNotFound(\'not found .......\') app = Application() @app.register(\'/\') # 这样就可以在定义视图的时候绑定url路由了 def index(request: Request) -> Response: response = Response(body=\'hello,world\', content_type=\'text/html\', ) return response
使用正则表达式匹配 让路由更加具有表现性
class Application: route = {} @classmethod def register(cls, pattern): def inner(handler): cls.route[pattern] = handler return handler return inner @wsgify def __call__(self, request: Request) -> Response: for pattern, handler in self.route.items(): if re.match(pattern, request.path): return handler(request) raise exc.HTTPNotFound(\'NOT FOUND\') app = Application() @app.register(\'^/$\') # 因为正则表达式匹配是贪婪模式的 所以需要加上 前后牟定 否则这里会全部匹配 def index(request: Request) -> Response: response = Response(body=\'hello,world\', content_type=\'text/html\', ) return response @app.register(\'^/hello$\') # 这里也需要加上牟定 def hello(request: Request) -> Response: params = request.params name = params.get(\'name\', \'unknown\') response = Response() response.text = \'hello,world {}\'.format(name)return response
使用正则表达式匹配让路由更加具有表现性
事先编译好pattern
class Application: route = [] # 使用列表 而不是字典 @classmethod def register(cls, pattern): def inner(handler): cls.route.append((re.compile(pattern), handler)) # 事先编译好 return handler return inner @wsgify def __call__(self, request: Request) -> Response: for pattern, handler in self.route: if pattern.match(request.path): return handler(request) raise exc.HTTPNotFound(\'NOT FOUND\') app = Application() @app.register(\'^/$\') def index(request: Request) -> Response: response = Response(body=\'hello,world\', content_type=\'text/html\', ) return response
将正则捕获到的参数 放到 request.args 和 request.kwargs里面(可以使用request.kwargs.name来访问字段)
class _Vars: def __init__(self, data=None): if data is not None: self._data = data # 注意改名 else: self._data = {} def __getattr__(self, item): try: return self._data[item] # 这样就可以实现request.kwargs.name 这种来访问字段了 except KeyError: raise AttributeError("NO attribute {}".format(item)) def __setattr__(self, key, value): if key != \'_data\': raise NotImplemented self.__dict__[\'_data\'] = value # 表示不能对他属性进行修改 class Application: route = [] # 使用列表 而不是字典 @classmethod def register(cls, pattern): def inner(handler): cls.route.append((re.compile(pattern), handler)) return handler return inner @wsgify def __call__(self, request: Request) -> Response: for pattern, handler in self.route: m = pattern.match(request.path) if m: request.args = m.groups() request.kwargs = _Vars(m.groupdict()) # 也可以使用元组来封装 return handler(request) raise exc.HTTPNotFound(\'NOT FOUND\') app = Application() @app.register(\'^/$\') def index(request: Request) -> Response: response = Response(body=\'hello,world\', content_type=\'text/html\') return response @app.register(\'^/hello/(\\w+)$\') # 测试 args http://127.0.0.1:3001/hello/wanglei def hello(request: Request) -> Response: name = request.args[0] response = Response() response.text = \'hello,world {}\'.format(name) return response
@app.register(\'^/hello/(?P<name>\\w+)$\') # 测试 kwargs http://127.0.0.1:3001/hello/wanglei def hello(request: Request) -> Response: name = request.kwargs.name response = Response() response.text = \'hello,world {}\'.format(name) return response
将method也加进匹配规则里面
把 method 也作为匹配的条件 class _Vars: def __init__(self, data=None): if data is not None: self._data = data # 注意改名 else: self._data = {} def __getattr__(self, item): try: return self._data[item] except KeyError: raise AttributeError("NO attribute {}".format(item)) def __setattr__(self, key, value): if key != \'_data\': raise NotImplemented self.__dict__[\'_data\'] = value class Application: route = [] #VSCode自定义代码片段14——Vue的axios网络请求封装VSCode自定义代码片段14——Vue的axios网络请求封装
VSCode自定义代码片段14——Vue的axios网络请求封装