自己是个python新手,之前买了本<<python核心编程>>,但看了一半实在看不下去了(内容过于啰嗦,而且在关键点的地方又浅尝辄止),所以希望通过阅读一些简单的开源项目来快速提高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协议是一个文本行的协议,它通常由以下部分组成:
引用
请求行(请求方法 URI路径 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
好了,到这里我们已经把整个流程:从HTTPServer接受外部请求,到我们web应用处理这一过程已经大致说完,希望对各位有帮助。
- 大小: 71 KB
- 大小: 11 KB
- 大小: 28.7 KB
- 大小: 64.7 KB
分享到:
相关推荐
webpy blog源代码 =============== blog.py 主程序模块 model.py 数据模块 templates/ base.html index.html view.html new.html edit.html static/ util.js Javascript工具模块 使用方法: ====...
本源码为基于Flask的Python Web应用设计,共包含75个文件,其中txt文件31个,html文件17个,png文件11个,js文件6个,py文件3个,md文件2个...该项目是一个学习并使用Flask的Python Web应用,适合用于个人学习和开发。
基于机器学习的Web日志统计分析与异常检测工具python源码+项目说明.zip 【资源功能介绍】 命令行下的Web日志审计工具,旨在帮助使用者能够在终端上快速得进行Web日志审计和排查,包含了日志审计、统计的终端图形化和...
【资源说明】 1、该资源包括项目的全部源码,下载可以直接使用! 2、本项目适合作为计算机、数学、电子信息等...基于微信公众帐号实现一个简单的足球活动报名系统源码+项目说明(基于Python 的Web.py and Mysql).zip
webpy简介及其在OpenShift上的部署源码,通过对比可以快速滴学习如何开发站点
包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】...
本项目是一款基于Python的Flask Web框架开发的博客系统,源代码开放,可供学习和二次开发。技术栈涉及多种编程语言,包括Python、HTML、CSS、JavaScript及PHP,确保了系统的前端展示与后端逻辑的丰富性和高效性。 ...
Python源码,指的是Python编程语言的源代码文件,以.py为后缀名。Python是一门高级编程语言,用于编写各种类型的应用程序,包括Web应用程序、桌面应用程序、数据分析、科学计算和人工智能等多个领域。Python语言的源...
1.项目代码功能经验证ok,确保稳定可靠运行。欢迎下载使用! 2.主要针对各个计算机相关专业,包括计科、信息安全、数据科学与...深度学习基于Django框架+python实现的web端人脸识别打码系统源码+项目说明+数据集.zip
该项目是基于python的web类库django开发的一套web网站,给同学做的课程作业。 本人的研究方向是一项关于搜索的研究项目。在该项目中,笔者开发了一个简单版的搜索网站,实现了对数据库数据的检索和更新。通过开发该...
python程序设计 贪吃蛇(内附源码)
基于python+django的深度学习的web端多格式纠错系统的实现.zip 运行步骤 需要先安装Python的相关依赖:django==3.2.8, pymysql,requests,pillow,pytesseractjieba,numpy, 使用pip install 安装 (将目录下的...
说明这个仓库保存的是学习《 Python Web TDD:测试驱动开发》时的代码,最终形成的界面如下所示:部署方法已经编写好的了自动部署脚本,下载了deploy_tools文件夹,然后执行命令: fab deploy:host=watch@watch0.top...
运行本程序十分简单,只需要按照...python app.py ——学习参考资料:仅用于个人学习使用! 本代码仅作学习交流,切勿用于商业用途,否则后果自负。若涉及侵权,请联系,会尽快处理! 未进行详尽测试,请自行调试!
本项目为一组基于Python 3.7的学习练习代码集合,涵盖Web开发与脚本语言交互的丰富示例。项目包含主要语言Python,以及HTML、Shell、JavaScript和TypeScript等多种语言编写的内容。总文件数达到738个,具体包括: -...
* 修改myconf.py 的 `WEIXIN_TOKEN = 'YOUR TOKEN'` 为你自己的weixin token * 配置微信接口信息: * http://XXX.sinaapp.com/weixin * Token: YOUR TOKEN -------- 该资源内项目源码是个人的毕设,代码都测试...
包含: asyncio, flask, sanic, bottle, webpy等 欢迎一起注解Python项目,共同学习。 项目列表: 项目 类型 说明 网络框架 知名项目,包含v0.1,0.4,0.5等版本注解 Python3标准库 初步编程 网络框架 Python3初步...
【资源说明】 1、该资源内项目代码都是经过测试运行成功,功能正常的情况下才上传的,请...3、不仅适合小白学习实战练习,也可作为大作业、课程设计、毕设项目、初期项目立项演示等,欢迎下载,互相学习,共同进步!
* 采用nginx + gunicorn + web.py + supervisor 部署运行 ### 3. 自动化部署 进入hjs_cms/install目录下,修改好远程服务器的业务配置环境(persion.conf)和远程服务器的ssh配置(secret.py),并行: ``` python...