最近在實驗室也沒什么事情做,閑著無聊。于是想著造輪子,最后決定寫一個簡單的web框架玩玩。
STEP.1
我們首先得選擇基于什么協議來寫這種框架。我們可以選擇CGI協議,或者是WSGI接口。如果使用CGI,實際上我們只是按著CGI的規范寫了一個python程序,然后每次服務器收到請求,就fork一個程序來執行它,然后返回一個http文檔,性能比較低下。對于WSGI,而是一個存在于服務器和應用間的接口,在WSGI之前,web應用都是依賴于服務器的,現在流行的python框架都支持WSGI接口。
STEP.2 PEP-333
這一段是PEP-333 所提供的樣例代碼。
def simple_app(environ, start_response):
"""Simplest possible application object"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return ['Hello world!\n']
這里的application被傳入了兩個值。
- environ
- start_response。
environ是一個字典,保存了http請求的信息。
start_response是一個函數,發送http響應。她有兩個參數status 和 start_headers。 - status必須是由狀態編號和具體信息組成的字符串,必須符合RFC 2616。
- start_headers是一個(header_name,header_value) 元組的列表元組列表。其中的hearder_name必須是合法的http header字段名。在RFC 2616, Section 4.2中有詳細定義。
當然官方還給出了類的實現。
def __init__(self, environ, start_response):
self.environ = environ
self.start = start_response
def __iter__(self):
status = '200 OK'
response_headers = [('Content-type','text/plain')]
self.start(status, response_headers)
yield "Hello world!\n"
了解了如上信息后,基本上可以開始了。我們就到官方給的代碼上進行修改吧。
STEP.3 將路徑鏈接到函數
首先我們得把用戶請求的路徑,鏈接到函數。我們可以實現一個getPage方法,專門做這件事。我們所擁有的信息,只有environ['PATH_INFO']。
urls = [('^/index/$','func_index'),
('^/comment/$','func_comment'),
('^/environ/$','get_environ'),
('^/post/$','post_test')]#urls是提供給app開發者來做鏈接的。
def getPage(self):
path = self.environ['PATH_INFO']
for pattern in self.urls:
m = re.match(pattern[0],path)#將urls元素的第0項和path進行比對,如果匹配上了,返回函數名
if m:
function = getattr(self,pattern[1])#getattr方法來得到函數
return function()
return '404 not found'#沒匹配上任何東西
寫到這里之后,每次添加頁面,就只需要在urls列表中添加一個元祖就行了。
STEP.4 獲取模版
既然是寫web app,模版肯定是得有的。這里我提供了一種getTemplate方法來做這件事。不過我只提供了變量的替換。
def getTemplate(self,tem_name,rep=0):
#這個函數返回內容,tem_name是文件名字
#參數rep是一個字典,默認為0
f = open('template/'+tem_name)
html = f.read()
if(rep!=0):
for to_replace in rep:
strinfo = re.compile('\{\%\s*'+str(to_replace)+'\s*\%\}')
html = strinfo.sub(rep[to_replace],html)
return html
STEP.5 POST數據的處理
要想獲取POST數據,我們得通過environ['wsgi.input']來處理。而他實際上就是系統的標準輸入。
environ['wsgi.input'] = sys.stdin
知道這點后就很好寫了。
def getPost(self):
if(self.environ['REQUEST_METHOD'] == 'POST'):
try:
request_body_size = int(self.environ.get('CONTENT_LENGTH', 0))#讀出content_length的值
except:
request_body_size = 0
request_body = self.environ['wsgi.input'].read(request_body_size) #請求的body
post_data = parse_qs(request_body)#parse_qs是cgi提供的方法,幫助我們處理請求
return post_data
數據庫的鏈接
import MySQLdb
class Model(object):
def __init__(self):
self.host = 'localhost'
self.port = 3306
self.user = 'admin'
self.passwd = 'admin'
self.db = 'xieyi'
def build_connect(self):
self.conn = MySQLdb.connect(
host = self.host,
port = self.port,
user = self.user,
passwd = self.passwd,
db = self.db
)
def exec_ins(self,ins):
cur = self.conn.cursor()
num = cur.execute(ins)
info = {}
if(num>0):
info = cur.fetchmany(num)
cur.close()
self.conn.commit()
return info
def close(self):
self.conn.close()
STEP.6 清理工作
很多配置如果放到代碼中,會增加閱讀負擔。于是把urls,model抽取出來。
使得配置更加方便。
STEP.7 結束
這只是一個web框架簡易的實現,僅供娛樂。如有疏漏,還望指出。
代碼po在https://github.com/bloodycoder/skeleton