学了一段时间Python,也看了很多的Web开发框架,轻量级、中量级的、重量级的...感觉都不尽如人意,原因有二:
1、任何开发者都有自己独特的思路,适应现成的框架是很痛苦的,相比完整但是死板的大框架,我更喜欢灵活的小工具,况且Python开发一个框架并不是想象的那么难
2、大部分框架依旧需要依附于独立的Web服务器运行,太麻烦。除了老牌的Twisted和新秀Tornado。在比较了这两个东西之后,我决定拿Twisted开刀。
以前的实践让我很反感那些用滥了的MVC、ORM等思路,没有必要把这些硬和前端Web页面紧绑定。我的想法是要尽可能地简单清晰,而且要实现“Designer Friendly”,也就是说美工不会对着嵌入一大堆符号完全走样的页面叹气。借鉴ASP.NET的一部分思想,我是这样设计的:
1、Web应用的根目录指定为项目源代码的根目录
2、尝试使用HTML页面对应的Python模块渲染页面,失败则直接返回HTML页面
例如/a/b/c/test.html如果对应a.b.c.test模块(很显然在项目中test.html和test.py是位于同一个目录的),规定该模块中必须包含render方法,可选包含master属性,render方法返回一个字典,使用python内置的string.Template对页面进行替换(我认为模板完成简单替换功能就足够了,更多业务逻辑应该交由后台或者Ajax完成),master指定该HTML页面使用的母版页
3、“母版页”的名字源自ASP.NET,思路同SiteMesh,母版页本身也是HTML页面,其中可以包含两个特殊占位符用来替换“被母版页”的Head和Body部分,SiteMesh设计中还有一个Title占位符,我给去掉了,一来加上Title后,势必要把Head中的Title扣掉,太麻烦,二来Title完全没有必要 - 母版页中不设置Title不就是了?
4、母版页中的引用路径应该为绝对路径,建议从根开始,这个原因很简单的,就不多说了
5、母版页也可以有对应的包含render方法的模块,但是母版页不能再有母版页了(实现起来感觉有些麻烦,而且也没什么必要)
6、其它参见源代码
我认为对于一个应用系统来讲安全性是必须的,而且我也不喜欢有状态的Session、Cookie等机制,于是后来又加上了HTTP基本认证和HTTPS的功能,当然也就一两行语句啦。
当然了,严格讲也不能算框架了,放在这里只是想抛砖引玉,和大家共同探讨吧。
/main.py
#!/usr/bin/env python
#coding: utf-8
'''
简单的Web框架
扩展名统一为.html
母版页中的引用路径应该为绝对路径,建议从根开始
统一用utf-8编码
'''
import os
from twisted.internet import reactor, ssl
from twisted.web.resource import Resource, ForbiddenResource
from twisted.web.server import Site
from twisted.web.static import File
from twisted.web.resource import ErrorPage
import importlib
from string import Template
import re
import traceback
class DynamicPage(File):
def directoryListing(self):
# 禁止目录浏览
return ForbiddenResource()
def getMaster(self, request, path):
try:
masterName = os.path.dirname(__file__).replace('\\', '/') + path
masterFile = open(masterName)
masterContent = masterFile.read()
except Exception:
# 找不到母版页则返回空
return None
finally:
masterFile.close()
# 母版页也可以对应.py模块
# 但是母版页不能再有母版页
try:
# 尝试查找对应的py模块
moduleName = path.replace('.html', '').replace('/', '.')[1:]
# importlib只有2.7以上版本才有!
module = importlib.import_module(moduleName)
renderer = getattr(module, 'render')(request)
return Template(masterContent).substitute(renderer)
except Exception:
# 如果没有对应的py模块,则直接返回该页面
return masterContent
def render(self, request):
# HTTP基本认证
user = request.getUser()
password = request.getPassword()
if not (user == 'aaa' and password == 'aaa'):
request.setHeader('WWW-Authenticate', 'Basic realm=\"Secure Area\"')
return ErrorPage(401, 'Unauthorized', 'Authentication required.').render(request)
# 禁止浏览的扩展名
ext = re.search('[^\\.]*\\.([^\\.]*)', request.path).group(1)
if not ext:
return ForbiddenResource().render(request)
if ext in ['py', 'pyc', 'pem']:
return ForbiddenResource().render(request)
try:
# 尝试查找对应的py模块
moduleName = request.path.replace('.html', '').replace('/', '.')[1:]
# importlib只有2.7以上版本才有!
module = importlib.import_module(moduleName)
master = getattr(module, 'master')
renderer = getattr(module, 'render')(request)
except Exception as e:
# 如果没有对应的py模块,则直接返回该页面
return File.render(self, request)
try:
# 打开html文件进行替换
fileName = os.path.dirname(__file__).replace('\\', '/') + request.path
file = open(fileName)
content = file.read()
rendered = Template(content).substitute(renderer)
# 处理母版页
masterContent = DynamicPage.getMaster(self, request, master)
if not masterContent:
return rendered
else:
head = re.search('
(.*)', rendered, re.DOTALL).group(1)body = re.search('
(.*)', rendered, re.DOTALL).group(1)return masterContent.replace('', head).replace('', body)
except Exception as e:
return ErrorPage(500, e, traceback.format_exc().replace('\n', '
')).render(request)
finally:
file.close()
if __name__ == "__main__":
root = DynamicPage(os.path.dirname(__file__).replace('\\', '/'))
site = Site(root)
sslContext = ssl.DefaultOpenSSLContextFactory(
os.path.join(os.path.dirname(__file__), 'privkey.pem').replace('\\', '/'),
os.path.join(os.path.dirname(__file__), 'cacert.pem').replace('\\', '/')
)
reactor.listenSSL(443, site, contextFactory=sslContext)
reactor.run()
/a/b/c/test.html
test你好: $name
/a/b/c/test.py
#coding: utf-8
'''
Created on 2011-5-14
@author: User
'''
master = '/master.html'
def render(request):
return {'name': '张三'}
/master.html
/p>
"http://www.w3.org/TR/html4/loose.dtd">
这是母版页,你好,$name
/master.py
#coding: utf-8
'''
Created on 2011-5-17
@author: User
'''
def render(request):
return dict(name=request.getUser())