【Python】Webpy 源码学习
那么webpy是什么呢? 阅读它的源码我们又能学到什么呢?
简单说webpy就是一个开源的web应用框架(官方首页:http://webpy.org/)
它的源代码非常整洁精干,学习它一方面可以让我们快速了解python语法(遇到看不懂的语法就去google),另一方面可以学习到python高级特性的使用(譬如反射,装饰器),而且在webpy中还内置了一个简单HTTP服务器(文档建议该服务器仅用于开发环境,生产环境应使用apache之类的),对于想简单了解下HTTP服务器实现的朋友来说,这个是再好不过的例子了(并且在这个服务器代码中,还可以学习到线程池,消息队列等技术),除此之外webpy还包括模板渲染引擎,DB框架等等,这里面的每一个部分都可以单独拿出来学习.
在JavaWeb开发中有Servlet规范,那么Python Web开发中有规范吗?
答案就是:WSGI,它定义了服务器如何与你的webapp交互
关于WSGI规范,可以参看下面这个链接:
http://ivory.idyll.org/articles/wsgi-intro/what-is-wsgi.html
现在我们利用webpy内置的WSGIServer,按照WSGI规范,写一个简单的webapp,eg:
- #/usr/bin/python
- import web.wsgiserver
- def my_wsgi_app(env, start_response):
- status = '200 OK'
- response_headers = [('Content-type','text/plain')]
- start_response(status, response_headers)
- return ['Hello world!']
- server = web.wsgiserver.CherryPyWSGIServer(("127.0.0.1", 8080), my_wsgi_app);
- server.start()
执行代码:
在具体看WSGIServer代码之前,我们先看一幅图,这幅图概述了WSGIServer内部执行流程:
接下来我们看下代码,ps: 为了较清晰的梳理主干流程,我只列出核心代码段
- # Webpy内置的WSGIServer
- class CherryPyWSGIServer(HTTPServer):
- def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
- max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
- # 线程池(用来处理外部请求,稍后详述)
- self.requests = ThreadPool(self, min=numthreads or 1, max=max)
- # 响应外部请求的webapp
- self.wsgi_app = wsgi_app
- # wsgi网关(http_request ->wsgi_gateway ->webpy/webapp)
- self.gateway = WSGIGateway_10
- # wsgi_server监听地址
- self.bind_addr = bind_addr
- # ...
- class HTTPServer(object):
- # 启动一个网络服务器
- # 如果你阅读过<<Unix网络编程>>,那么对于后面这些代码将会再熟悉不过,唯一的区别一个是c,
- #一个是python
- def start(self):
- # 如果bind_addr是一个字符串(文件名),那么采用unix domain协议
- if isinstance(self.bind_addr, basestring):
- try: os.unlink(self.bind_addr)
- except: pass
- info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
- else:
- # 否则采用TCP/IP协议
- host, port = self.bind_addr
- try:
- info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
- socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
- except socket.gaierror:
- # ...
- # 循环测试 getaddrinfo函数返回值,直到有一个bind成功或是遍历完所有结果集
- for res in info:
- af, socktype, proto, canonname, sa = res
- try:
- self.bind(af, socktype, proto)
- except socket.error:
- if self.socket:
- self.socket.close()
- self.socket = None
- continue
- break
- if not self.socket:
- raise socket.error(msg)
- # 此时socket 进入listening状态(可以用netstat命令查看)
- self.socket.listen(self.request_queue_size)
- # 启动线程池(这个线程池做些什么呢? 稍后会说)
- self.requests.start()
- self.ready = True
- while self.ready:
- # HTTPSever核心函数,用来接受外部请求(request)
- # 然后封装成一个HTTPConnection对象放入线程池中的消息队列里,
- # 接着线程会从消息队列中取出该对象并处理
- self.tick()
- def bind(self, family, type, proto=0):
- # 创建socket
- self.socket = socket.socket(family, type, proto)
- # 设置socket选项(允许在TIME_WAIT状态下,bind相同的地址)
- self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- # socket bind
- self.socket.bind(self.bind_addr)
- # HTTPSever核心函数
- def tick(self):
- try:
- # 接受一个TCP连接
- s, addr = self.socket.accept()
- # 把外部连接封装成一个HTTPConnection对象
- makefile = CP_fileobject
- conn = self.ConnectionClass(self, s, makefile)
- # 然后把该对象放入线程池中的消息队列里
- self.requests.put(conn)
- except :
- # ...
之前我们说过HTTPServer中的request属性是一个线程池(这个线程池内部关联着一个消息队列),现在我们看看作者是如何实现一个线程池的:
- class ThreadPool(object):
- def __init__(self, server, min=10, max=-1):
- # server实例
- self.server = server
- # 线程池中线程数配置(最小值,最大值)
- self.min = min
- self.max = max
- # 线程池中的线程实例集合(list)
- self._threads = []
- # 消息队列(Queue是一个线程安全队列)
- self._queue = Queue.Queue()
- # 编程技巧,用来简化代码,等价于:
- # def get(self)
- # return self._queue.get()
- self.get = self._queue.get
- # 启动线程池
- def start(self):
- # 创建min个WorkThread并启动
- for i in range(self.min):
- self._threads.append(WorkerThread(self.server))
- for worker in self._threads:
- worker.start()
- # 把obj(通常是一个HTTPConnection对象)放入消息队列
- def put(self, obj):
- self._queue.put(obj)
- # 在不超过允许创建线程的最大数下,增加amount个线程
- def grow(self, amount):
- for i in range(amount):
- if self.max > 0 and len(self._threads) >= self.max:
- break
- worker = WorkerThread(self.server)
- self._threads.append(worker)
- worker.start()
- # kill掉amount个线程
- def shrink(self, amount):
- # 1.kill掉已经不在运行的线程
- for t in self._threads:
- if not t.isAlive():
- self._threads.remove(t)
- amount -= 1
- # 2.如果已经kill掉线程数小于amount,则在消息队列中放入线程退出标记对象_SHUTDOWNREQUEST
- # 当线程从消息队列中取到的不是一个HTTPConnection对象,而是一个_SHUTDOWNREQUEST,则退出运行
- if amount > 0:
- for i in range(min(amount, len(self._threads) - self.min)):
- self._queue.put(_SHUTDOWNREQUEST)
- # 工作线程WorkThread
- class WorkerThread(threading.Thread):
- def __init__(self, server):
- self.ready = False
- self.server = server
- # ...
- threading.Thread.__init__(self)
- def run(self):
- # 线程被调度运行,ready状态位设置为True
- self.ready = True
- while True:
- # 尝试从消息队列中获取一个obj
- conn = self.server.requests.get()
- # 如果这个obj是一个“退出标记”对象,线程则退出运行
- if conn is _SHUTDOWNREQUEST:
- return
- # 否则该obj是一个HTTPConnection对象,那么线程则处理该请求
- self.conn = conn
- try:
- # 处理HTTPConnection
- conn.communicate()
- finally:
- conn.close()
刚才我们看到,WorkThread从消息队列中获取一个HTTPConnection对象,然后调用它的communicate方法,那这个communicate方法究竟做了些什么呢?
- class HTTPConnection(object):
- RequestHandlerClass = HTTPRequest
- def __init__(self, server, sock, makefile=CP_fileobject):
- self.server = server
- self.socket = sock
- # 把socket对象包装成类File对象,使得对socket读写就像对File对象读写一样简单
- self.rfile = makefile(sock, "rb", self.rbufsize)
- self.wfile = makefile(sock, "wb", self.wbufsize)
- def communicate(self):
- # 把HTTPConnection对象包装成一个HTTPRequest对象
- req = self.RequestHandlerClass(self.server, self)
- # 解析HTTP请求
- req.parse_request()
- # 响应HTTP请求
- req.respond()
在我们具体看HTTPRequest.parse_request如何解析HTTP请求之前,我们先了解下HTTP协议. HTTP协议是一个文本行的协议,它通常由以下部分组成:
请求头(譬如:User-Agent,Host等等)
空行
可选的数据实体
而HTTPRequest.parse_request方法就是把socket中的字节流,按照HTTP协议规范解析,并且从中提取信息(最终封装成一个env传递给webapp):
- def parse_request(self):
- self.rfile = SizeCheckWrapper(self.conn.rfile,
- self.server.max_request_header_size)
- # 读取请求行
- self.read_request_line()
- # 读取请求头
- success = self.read_request_headers()
- # ----------------------------------------------------------------
- def read_request_line(self):
- # 从socket中读取一行数据
- request_line = self.rfile.readline()
- # 按照HTTP协议规范,把request_line分割成请求方法(method),uri路径(uri),HTTP协议版本(req_protocol)
- method, uri, req_protocol = request_line.strip().split(" ", 2)
- self.uri = uri
- self.method = method
- scheme, authority, path = self.parse_request_uri(uri)
- # 获取uri请求参数
- qs = ''
- if '?' in path:
- path, qs = path.split('?', 1)
- self.path = path
- # ----------------------------------------------------------------
- def read_request_headers(self):
- # 读取请求头,inheaders是一个dict
- read_headers(self.rfile, self.inheaders)
- # ----------------------------------------------------------------
- def read_headers(rfile, hdict=None):
- if hdict is None:
- hdict = {}
- while True:
- line = rfile.readline()
- # 把line按照":"分割成k, v,譬如 Host:baidu.com就被分割成Host和baidu.com两部分
- k, v = line.split(":", 1)
- # 格式化分割后的
- k = k.strip().title()
- v = v.strip()
- hname = k
- # HTTP协议中的有些请求头允许重复(譬如Accept等等),那么webpy就会把这些相同头的value用","连接起来
- if k in comma_separated_headers:
- existing = hdict.get(hname)
- if existing:
- v = ", ".join((existing, v))
- # 把请求头k, v存入hdict
- hdict[hname] = v
- return hdict
至此我们就分析完了HTTPRequest.parse_request方法如何解析HTTP请求,下面我们就接着看看HTTPRequest.respond如何响应请求:
- def respond(self):
- # 把请求交给gateway响应
- self.server.gateway(self).respond()
在继续往下看代码之前,我们先简单思考下,为什么要有这个gateway,为什么这里不把请求直接交给webapp处理?
我自己觉得还是出于分层和代码复用性考虑。因为可能存在,或者需要支持很多web规范,目前我们使用的是wsgi规范,明天可能出来个ysgi,大后天可能还来个zsgi,如果按照当前的设计,我们只需要替换HTTPServer的gateway属性,而不用修改其他代码(类似JAVA概念中的DAO层),下面我们就来看看这个gateway的具体实现(回到本文最初,我们在Server中注册的gateway是WSGIGateway_10):
WSGI网关
- class WSGIGateway(Gateway):
- def __init__(self, req):
- self.req = req # HTTPRequest对象
- self.env = self.get_environ()
- # 获取wsgi的环境变量(留给子类实现)
- def get_environ(self):
- raise NotImplemented
- def respond(self):
- # -----------------------------------
- # 按照 WSGI 规范调用我们得 webapp/webpy
- # -----------------------------------
- response = self.req.server.wsgi_app(self.env, self.start_response)
- # 把处理结果写回给客户端
- for chunk in response:
- self.write(chunk)
- def start_response(self, status, headers, exc_info = None):
- self.req.status = status
- self.req.outheaders.extend(headers)
- return self.write
- def write(self, chunk):
- # 写http响应头
- self.req.send_headers()
- # 写http响应体
- self.req.write(chunk)
WSGIGateway_10继承WSGIGateway类,并实现get_environ方法
- class WSGIGateway_10(WSGIGateway):
- def get_environ(self):
- # build WSGI环境变量(req中的这些属性,都是通过HTTPRequest.prase_request解析HTTP请求获得的)
- req = self.req
- env = {
- 'ACTUAL_SERVER_PROTOCOL': req.server.protocol,
- 'PATH_INFO': req.path,
- 'QUERY_STRING': req.qs,
- 'REMOTE_ADDR': req.conn.remote_addr or '',
- 'REMOTE_PORT': str(req.conn.remote_port or ''),
- 'REQUEST_METHOD': req.method,
- 'REQUEST_URI': req.uri,
- 'SCRIPT_NAME': '',
- 'SERVER_NAME': req.server.server_name,
- 'SERVER_PROTOCOL': req.request_protocol,
- 'SERVER_SOFTWARE': req.server.software,
- 'wsgi.errors': sys.stderr,
- 'wsgi.input': req.rfile,
- 'wsgi.multiprocess': False,
- 'wsgi.multithread': True,
- 'wsgi.run_once': False,
- 'wsgi.url_scheme': req.scheme,
- 'wsgi.version': (1, 0),
- }
- # ...
- # 请求头
- for k, v in req.inheaders.iteritems():
- env["HTTP_" + k.upper().replace("-", "_")] = v
- # ...
- return env
转载于:https://www.cnblogs.com/xiaoleiel/p/8301453.html
【Python】Webpy 源码学习相关推荐
- 【Python】Webpy 源码学习(转)
自己是个python新手,之前买了本<<python核心编程>>,但看了一半实在看不下去了(内容过于啰嗦,而且在关键点的地方又浅尝辄止),所以希望通过阅读一些简单的开源项目来快 ...
- python框架源码学习
最近下了一个别人的接口测试框架原码来学习 1.有用到logbook模块进行日志管理 2.使用xlrd模块对excel数据表的操作 3.使用自定义的代码输出测试报告 4.使用logger模块记录运行时日 ...
- python flask源码解析_用尽洪荒之力学习Flask源码
[TOC] 一直想做源码阅读这件事,总感觉难度太高时间太少,可望不可见.最近正好时间充裕,决定试试做一下,并记录一下学习心得. 首先说明一下,本文研究的Flask版本是0.12. 首先做个小示例,在p ...
- Kalibr源码学习(一): 重投影误差
Kalibr源码学习(一): 重投影误差 给自己挖一个大坑, 从标定结果来学习Kalibr的标定源码, 这里基本以KB模型为例, 也就是标定时, kalibr的模型设定为 --model pinhol ...
- python游戏源码——2绘画简易坦克
python游戏源码--2绘画简易坦克 欢迎大家来看我的博客 话不多说,源码如下 print('''> 人生苦短,我用pyhon. | __\--__|____||=======OOOOO[/ ...
- VS2019编译python解释器源码及学习方法
Python源码编译 Python是当下很火的一门编程语言,在人工智能.数据分析.后端开发等领域可谓是人人都会的语言,在用python实现各种应用服务的同时,估计很少有人去关注python的实现, ...
- [阿里DIN] 从论文源码学习 之 embedding层如何自动更新
[阿里DIN] 从论文源码学习 之 embedding层如何自动更新 文章目录 [阿里DIN] 从论文源码学习 之 embedding层如何自动更新 0x00 摘要 0x01 DIN源码 1.1 问题 ...
- 立体多层玫瑰绘图源码__玫瑰花python 绘图源码集锦
立体多层玫瑰绘图源码__玫瑰花python 绘图源码集锦 目 录: (1)python 玫瑰画法1--立体多层玫瑰 (2)python 玫瑰画法2 (3) python玫瑰画法3 (4) python ...
- python解释器源码 pdf_《python解释器源码剖析》第0章--python的架构与编译python
本系列是以陈儒先生的<python源码剖析>为学习素材,所总结的笔记.不同的是陈儒先生的<python源码剖析>所剖析的是python2.5,本系列对应的是python3.7. ...
最新文章
- 麦克纳姆轮全向移动原理
- 使用网盘搭建svn服务器详解步骤
- 时间序列错位还原之SQL实现案例详解
- java 动态字符串_Java动态编译执行一串字符串,类似于Javascript里的eval函数
- element ui 多个子组件_vue前端UI框架,一点都不圆润,盘它!
- 730阵列卡支持多大硬盘_华为1000多手机哪款好?推荐只此一款!华为品牌性价比最高千元机...
- 面试官:说说你知道多少种线程池拒绝策略
- PyQt5笔记(07) -- 变换控件颜色
- Python自动化运维之15、网络编程之socket、socketserver、select、twisted
- python requests返回值为200 但是text无内容_手把手教你使用Python生成图灵智能小伙伴,实现工作助手闲聊功能
- 用汇编语言实现itoa函数
- python 爬电影名网址评分及导演代码和运行结果
- 地市级地铁数据管理信息系统解决方案
- kuwo.php源码,酷我音乐官方flash播放器调用代码
- 「 Adams 」如何设置积分器与求解器类型
- 如何将腾讯视频QLV格式转换成MP4
- 远程连接centos 服务器,怎么用远程桌面连接CentOS 8
- mysql 运维审计_【MySQL运维】MySQL审计管理
- 【优秀课设】基于Python的百度API的OCR名片识别【含完整API账户】
- 现代软件工程讲义 1 软件工程概论