一、python實現web伺服器
web開發首先要有web伺服器才行。比如apache,但是在開發階段最好有一個簡單方便的程式開發伺服器,
容易重啟進行調試,等開發調試完畢後,再將代碼部署到成熟穩定高效的web伺服器。
# -*- coding: utf-8 -*-from wsgiref import simple_server# 定義一個輸出 hello world 和環境變數的簡單web應用程式def hello_app(environ, start_response): # 輸出 http 頭,text/plain 表示是純文字 start_response('200 OK', [('Content-type','text/plain')]) # 準備輸出的內容 content = [] content.append('Hello world') for key, value in environ.items(): content.append('%s : %s' % (key, value)) # 輸出,根據 wsgi 協議,返回的需要是一個迭代器,返回一個 list 就可以 return ['\n'.join(content)]# 構造程式開發伺服器對象,設定綁定的地址和連接埠,並把 hello world 應用程式傳給他server = simple_server.make_server('localhost', 8080, hello_app)# 啟動程式開發伺服器server.serve_forever()
執行上面這個程式後,開啟瀏覽器,訪問一個以 http://www.php.cn/:8080 開頭的網址即可看到 environ 所包含的內容。
(截取一小部分)
二、基礎知識
瀏覽器和web應用之間使用的是http協議,它規定了請求和響應的格式。
1、請求包(Http Request)
請求主要包括請求的方法,請求的URL,要求標頭,請求體。
請求的方法http規定有GET, POST, PUT, DELETE,只不過通過瀏覽器發起的web請求一般只涉及GET和POST請求。
GET一般用來擷取伺服器內容,POST類似修改內容,PUT添加,DELETE刪除。
一般通過提交html的form表單發起POST請求。成功後需要進行重新導向。
從協議上看GET,HTTP請求最大的區別就是GET請求沒有請求體,而POST請求有。這就意味著可以通過POST請求
向伺服器發送大量資料,如上傳檔案等,當然GET請求也可以通過URL本身以及其參數向伺服器傳遞參數,比如
url?arg1=value&arg2=value
要求標頭就是包含了請求包的描述資訊。 比如編碼,包長度等。
2、響應包(Http Response)
http的響應包的格式更簡單一些,包括狀態代碼,回應標頭和響應體,狀態代碼表示該請求的結果,比如
200表示成功
404表示資源沒有找到
500表示伺服器錯誤
301表示資源已經換了地址,用戶端需要跳轉。
回應標頭和要求標頭類似,包括一些描述資訊,響應體一般就是輸出內容了,大部分是頁面html代碼。
3、請求的生命週期
1. web伺服器接收到原始的http請求後進行一定程度的封裝再交給web應用程式
2. web應用程式處理後,再以一定的格式返回資料給web伺服器
3. web伺服器再將資料封裝成http響應包返回給瀏覽器。
4、關於cgi
cgi(common gateway interface)就是web伺服器與web應用程式之間的一個古老的協議,在cgi協議中,
web伺服器將http請求的各種資訊放到cgi應用程式的環境變數中,cgi應用程式再通過標準輸出,輸出它的回應標頭
和相應內容給web伺服器。
上面用到的程式開發伺服器與應用程式之間所使用的協議叫做wsgi,它和cgi類似,同樣將請求封裝成一種key-value對,
只不過cgi通過環境變數傳給cgi應用程式,而wsgi直接使用python的字典對象來傳遞。
hello_app的第一個參數environ就是包含請求資訊的字典對象,第二個參數是個函數,web應用程式在輸出響應內容
前需要先調用它來輸出狀態代碼和回應標頭。
處理web請求和響應這裡使用webob模組來處理請求和響應,需要安裝,這裡首先要安裝setuptools模組,一個包管理的工具,可以通過這個工具自動下載需要的軟體包,類似ubuntu的app-get。下面是地址:http://www.php.cn/安裝結束,可以直接在命令列中輸入:easy_install webob這樣就會自動下載安裝。
簡單使用:
>>> # 匯入 Request 對象
>>> from webob import Request
>>> environ = {}
>>> # 使用 Request 來封裝 environ 字典
>>> req = Request(environ)
使用一個Request類來封裝environ,然後通過Request對象的屬性和方法對environ進行訪問。由於只有在一個web環境才能得到一個真實的environ字典,為了方便大家在shell中進行測試,webob提供了一個類比簡單web請求的方法:
也可以通過req尋找其它有用的資訊
同時也可以通過webob模組中的Response對象來封裝響應資訊。
下面使用webob模組重寫之前的hello_app
# -*- coding: utf-8 -*-from wsgiref import simple_serverfrom webob import Request, Response# 我們順便增加了一個功能,就是根據使用者在 URL 後面傳遞的參數# 顯示相應的內容def hello_app(request): content = [] # 擷取 get 請求的參數 content.append('Hello %s'%request.GET['name']) # 輸出所有 environ 變數 for key, value in request.environ.items(): content.append('%s : %s' % (key, value)) response = Response(body='\n'.join(content)) response.headers['content-type'] = 'text/plain' return response# 對請求和響應進行封裝def wsgi_wrapper(environ, start_response): request = Request(environ) response = hello_app(request) # response 對象本身也實現了與 wsgi 伺服器之間通訊的協議, # 所以可以幫我們處理與web伺服器之間的互動。 # 這一句比較奇怪,對象使用括弧是什麼意思。。。。 return response(environ, start_response)server = simple_server.make_server('localhost', 8080, wsgi_wrapper)server.serve_forever()
為了讓 wsgi_wrapper 更加通用一點,可以把它設計成裝飾器的形式:
# -*- coding: utf-8 -*-from wsgiref import simple_serverfrom webob import Request, Response# 寫成裝飾器的 wsgi_wrapperdef wsgi_wrapper(func): def new_func(environ, start_response): request = Request(environ) response = func(request) return response(environ, start_response) new_func.__name__ = func.__name__ new_func.__doc__ = func.__doc__ return new_func# 應用程式@wsgi_wrapperdef hello_app(request): content = [] content.append('Hello %s'%request.GET['name']) for key, value in request.environ.items(): content.append('%s : %s' % (key, value)) response = Response(body='\n'.join(content)) response.headers['content-type'] = 'text/plain' return responseserver = simple_server.make_server('localhost', 8080, hello_app)server.serve_forever()
三、模板
果然,還是需要用到模板,不能總是直接在Response中寫上長串的html代碼。
python中的模板引擎主要有mako, genshi, jinjia等。
mako 主要特點在於模板裡面 可以比較方便的嵌入Python代碼,而且執行效率一流;
genshi 的特點在於基於 xml, 非常簡單易懂的模板文法,對於熱愛xhtml的朋友來說是很好的選擇,
同時也可以嵌入Python 代碼,實現一些複雜的展現邏輯;
jinja 和genshi 一樣擁有很簡單的模板文法,只是不 依賴於 xml 的格式,同樣很適合設計人員直接進行模板的製作,
同時也可以嵌入Python 代碼實現一些複雜的展現邏輯。
這裡使用Mako,地址ttp://pypi.python.org/pypi/Mako,下載python setup.py install進行安裝
簡單的模組例子:
## -*- coding: utf-8 -*-<html> <head> <title>簡單mako模板</title> </head> <body> <h5>Hello ${name}!</h5> <ul> % for key, value in data.items(): <li> ${key} - ${value} <li> % endfor </ul> </body></html>
儲存為simple.html檔案,然後需要給模板對象傳遞data和name兩個參數,然後進行渲染,就可以輸入html內容
# -*- coding: utf-8 -*-# 匯入模板對象from mako.template import Template# 使用模板檔案名稱構造模板對象tmpl = Template(filename='./simple.html', output_encoding='utf-8')# 構造一個簡單的字典填充模板,並print出來print tmpl.render(name='python', data = {'a':1, 'b':2})
儲存為test_template.py檔案,運行就可以輸入內容:
$ python test_template.py
<html> <head> <title>簡單mako模板</title> </head> <body> <h5>Hello python!</h5> <ul> <li> a - 1 <li> <li> b - 2 <li> </ul> </body></html>
下面對hello_app程式進行重構:
1. 把 wsgi_wrapper 單獨放到通用模組 utils.py:
# -*- coding: utf-8 -*-from webob import Requestdef wsgi_wrapper(func): def new_func(environ, start_response): request = Request(environ) response = func(request) return response(environ, start_response) new_func.__name__ = func.__name__ new_func.__doc__ = func.__doc__ return new_func
2. 把 hello_app 給徹底獨立出來,形成單獨的模組 controller.py :
# -*- coding: utf-8 -*-from utils import wsgi_wrapperfrom webob import Responsefrom mako import Template# 整合了模板功能的 hello_app@wsgi_wrapperdef hello_app(request): tmpl = Template(filename='./simple.html', output_encoding='utf-8') content = tmpl.render(name=request.GET['name'], data=request.environ) return Response(body=content)
3. 這樣 main.py 就變成這樣了:
# -*- coding: utf-8 -*-from wsgiref import simple_serverfrom controller import hello_appserver = simple_server.make_server('localhost', 8080, hello_app)server.serve_forever()
四、ORM(Object Relation Mapping, 對象關係映射)
終於也要這一步了,作為web應用,還是需要與資料庫進行合作。
這裡使用sqlalchemy,是一個 ORM (對象-關係映射)庫,提供Python對象與關聯式資料庫之間的映射。和Django的models
用法很像,也是可以通過python代碼來建立資料庫表,並進行操作。
sqlalchemy 還可以自動對應 Python 對象的繼承,可以實現eager loading、lazy loading, 可以直接將 Model 映射到自定
義的 SQL 陳述式,支援n多的資料庫等等等等。 可以說 sqlalchemy 既有不輸於 Hibernate 的強大功能,同時不失 Python
的簡潔優雅。
使用方法:
# -*- coding: utf-8 -*-from sqlalchemy import *from sqlalchemy.orm import sessionmaker, scoped_sessionfrom sqlalchemy.ext.declarative import declarative_base# 建立資料庫引擎,這裡我們直接使用 Python2.5 內建的資料庫引擎:sqlite,# 直接在目前的目錄下建立名為 data.db 的資料庫engine = create_engine('sqlite:///data.db')# sqlalchemy 中所有資料庫操作都要由某個session來進行管理# 關於 session 的詳細資料請參考:http://www.sqlalchemy.org/docs/05/session.htmlSession = scoped_session(sessionmaker(autocommit=False, autoflush=False, bind=engine))Base = declarative_base()class Dictionary(Base): # Python 對象對應關聯式資料庫的表名 __tablename__ = 't_dictionary' # 定義自動,參數含義分別為:資料庫欄位名,欄位類型,其他選項 key = Column('key', String(255), primary_key=True) value = Column('value', String(255))# 建立資料庫Base.metadata.create_all(engine)session = Session()for item in ['python','ruby','java']: # 構造一個對象 dictionary = Dictionary(key=item, value=item.upper()) # 告訴 sqlalchemy ,將該對象加到資料庫 session.add(dictionary)# 提交session,在這裡才真正執行資料庫的操作,添加三條記錄到資料庫session.commit()# 查詢資料庫中Dictionary對象對應的資料for dictionary in session.query(Dictionary): print dictionary.key, dictionary.value
上面的代碼你執行兩遍就會報錯,為什麼。。。因為插入資料庫的主鍵重複了。。。。
這樣就可以整合到之前的controller.py檔案中
# -*- coding: utf-8 -*-from utils import wsgi_wrapperfrom webob import Responsefrom mako.template import Template# 匯入公用的 model 模組from model import Session, Dictionary@wsgi_wrapperdef hello_app(request): session = Session() # 查詢到所有 Dictionary 對象 dictionaries = session.query(Dictionary) # 然後根據 Dictionary 對象的 key、value 屬性把列錶轉換成一個字典 data = dict([(dictionary.key, dictionary.value) for dictionary in dictionaries]) tmpl = Template(filename='./simple.html', output_encoding='utf-8') content = tmpl.render(name=request.GET['name'], data=data) return Response(body=content)
五、URL分發控制
給不同的資源設計不同的 URL, 用戶端請求這個 URL,web應用程式再根據使用者請求的 URL 定位到具體功能並執行之。
提供一個乾淨的 URL 有很多好處:
1. 可讀性,通過 URL 就可以大概瞭解其提供什麼功能
2. 使用者容易記住也方便直接輸入
3.設計良好的 URL 一般都更短小精悍,對搜尋引擎也 更友好
使用selector模組來處理url映射
下載地址http://pypi.python.org/pypi/selector, 下載那個source檔案進行python setup.py install
首先把urls的配置單獨放到urls.py中
# -*- coding: utf-8 -*-from controller import hello_appmappings = [('/hello/{name}', {'GET':hello_app})]
修改main.py
# -*- coding: utf-8 -*-from wsgiref import simple_serverfrom urls import mappingsfrom selector import Selector# 構建 url 分發器app = Selector(mappings)server = simple_server.make_server('localhost', 8080, app)server.serve_forever()
然後,在 hello_app 中就可以通過 environ['wsgiorg.routing_args'] 擷取到 name 參數了,
不過在 wsgi_wrapper 其實還可以進一步簡化 hello_app 的工作: 直接把解析得到的參數
當作函數參數傳過去!修改 utils.py:
from webob import Requestdef wsgi_wrapper(func): def new_func(environ, start_response): request = Request(environ) position_args, keyword_args = environ.get('wsgiorg.routing_args', ((), {})) response = func(request, *position_args, **keyword_args) return response(environ, start_response) new_func.__name__ = func.__name__ new_func.__doc__ = func.__doc__ return new_func
那 hello_app 就可以改成這樣了:
...@wsgi_wrapperdef hello_app(request, name=''): ... content = tmpl.render(name=name, data=data) return Response(body=content)執行main.py,訪問http://localhost:8080/hello/Python
總結
以上部分的實現,就是類似Django架構中的幾個主要的功能模組,希望對大家的學習有所協助。