前面的筆記里面談到了裝飾器和正則表達式,這里我要實現一個簡單的web服務器。具體到功能上,首先是可以通過瀏覽器訪問,還有一點就是有一個簡單的模版系統。
1. 網絡部分
網絡部分使用的twisted,這個代碼從twisted一本書中拷出來的。不要覺得很復雜照抄即可,我也不懂twisted。這里要說的是HTTP協議每行數據都是以\r\n
結尾,所以這里協議繼承自basic.LineReceiver
。
from twisted.protocols import basic
from twisted.internet import protocol, reactor
from template import render_template
class HTTPProtocol(basic.LineReceiver):
def __init__(self,factory):
self.lines = []
self.factory = factory
def lineReceived(self, line):
self.lines.append(line)
if not line:
# self.sendResponse(self.factory.app.dispatch(lines))
self.sendResponse(self.factory.app.dispatch(self.lines))
def sendResponse(self,data):
self.sendLine(b"HTTP/1.1 200 OK")
self.sendLine(b"")
# responseBody = "You said:\r\n\r\n" + "\r\n".join(self.lines)
responseBody = data.encode('utf-8')+b'\r\n'
self.transport.write(responseBody)
self.transport.loseConnection()
class HTTPFactory(protocol.ServerFactory):
def __init__(self,app):
self.app = app
def buildProtocol(self, addr):
return HTTPProtocol(self)
2 路徑裝飾器
前面我談到過裝飾器的問題,每個路徑對應的函數也可以使用裝飾器來實現。把路徑與帶路徑裝飾器的函數關聯起來,這里簡單使用dict
這種數據結構。實現的代碼在下面列出,這里可以看到route
函數是一個成員函數,最重要的一行代碼就是self.routes[url] = func
。本來裝飾器只需要這行代碼就能工作,但是如果裝飾器不返回一個函數,這里說的是wrapper
這個函數,那么就不能在一個函數上使用多個路徑裝飾器。
class Dongge():
def __init__(self):
self.routes = {}
def dispatch(self,lines):
line = lines[0].decode('utf-8')
method,url,version = line.split(' ')
print(line)
print(self.routes)
if url in self.routes:
return self.routes[url]()
return 'No such url resource'
def route(self,url):
def df(func):
self.routes[url] = func
def wrapper(*args, **kwargs):
return func(*args,**kwargs)
return wrapper
return df
app = Dongge()
@app.route('/')
@app.route('/index')
def index():
return 'index'
3 模板
模版在前面已經談到過這方面的內容,這里只需要對render_template
封裝一下即可使用。
def view(self,tname,context):
path = '{}/{}.html'.format(self.templatedir,tname)
try:
with open(path,'r') as f:
html = f.read()
print(html)
return render_template(html,context)
except Exception as e:
print(e)
return ''
4 補充
讓代碼運行起來,其實就是簡單的兩句代碼:
reactor.listenTCP(8000, HTTPFactory(app))
reactor.run()
如果你來跑這代碼會存在什么問題,當然代碼都運行過了肯定沒多大問題。但是這里還有個讓人很苦惱的事情,就是每次修改代碼之后總得重新運行服務器。也許是說,我需要一個東西能監控我代碼的改動,然后自動重啟服務器。這個問題很其實很簡單,看代碼,這代碼都是從網上拷的稍微修改了一下。
#start.py
# -*- coding:utf-8 -*-
from watchdog.observers import Observer
from watchdog.events import *
import time
from server import start
from multiprocessing import Process
class FileEventHandler(FileSystemEventHandler):
def __init__(self):
FileSystemEventHandler.__init__(self)
def on_moved(self, event):
if event.is_directory:
print("directory moved from {0} to {1}".format(event.src_path,event.dest_path))
else:
print("file moved from {0} to {1}".format(event.src_path,event.dest_path))
def on_created(self, event):
if event.is_directory:
print("directory created:{0}".format(event.src_path))
else:
print("file created:{0}".format(event.src_path))
def on_deleted(self, event):
if event.is_directory:
print("directory deleted:{0}".format(event.src_path))
else:
print("file deleted:{0}".format(event.src_path))
def on_modified(self, event):
if event.is_directory:
print("directory modified:{0}".format(event.src_path))
else:
print("file modified:{0}".format(event.src_path))
#如果改動的文件不是start.py則不需要重啟服務器
if event.src_path.find('start.py') == -1:
self.startserver()
'''
啟動服務器需要開啟新進程,重啟和啟動代碼放在一起了
'''
def startserver(self):
if hasattr(self,'p'):
self.p.terminate()
del self.p
p = Process(target=start)
p.start()
self.p = p
if __name__ == "__main__":
observer = Observer()
event_handler = FileEventHandler()
event_handler.startserver()
observer.schedule(event_handler,"./",True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()