(8)Django框架學習-python模擬Django框架


原貼來源

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://localhost:8080 開頭的網址即可看到 environ 所包含的內容。
 (截取一小部分) 

基礎知識

瀏覽器和web應用之間使用的是http協議,它規定了請求和響應的格式。
 
請求包(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
 
請求頭就是包含了請求包的描述信息。 比如編碼,包長度等。
 
響應包(Http Response)
http的響應包的格式更簡單一些,包括狀態碼,響應頭和響應體,狀態碼表示該請求的結果,比如
200表示成功
404表示資源沒有找到
500表示服務器錯誤
301表示資源已經換了地址,客戶端需要跳轉。
響應頭和請求頭類似,包括一些描述信息,響應體一般就是輸出內容了,大部分是頁面html代碼。
 
請求的生命周期
1. web服務器接收到原始的http請求后進行一定程度的包裝再交給web應用程序
2. web應用程序處理后,再以一定的格式返回數據給web服務器
3. web服務器再將數據包裝成http響應包返回給瀏覽器。
 
關於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。下面是地址:
安裝結束,可以直接在命令行中輸入:
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_server
from 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_server
from webob import Request, Response

# 寫成裝飾器的 wsgi_wrapper
def 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_wrapper
def 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 response

server = 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,地址 http://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 Request

def 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_wrapper
from webob import Response
from mako import Template

# 整合了模板功能的 hello_app
@wsgi_wrapper
def 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_server
from controller import hello_app

server = 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_session
from 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.html
Session = 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_wrapper
from webob import Response
from mako.template import Template
# 導入公用的 model 模塊
from model import Session, Dictionary

@wsgi_wrapper
def 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_app
mappings = [( '/hello/{name}', { 'GET' :hello_app})]
修改main.py
# -*- coding: utf-8 -*-
from wsgiref import simple_server
from urls import mappings
from 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 Request

def 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_wrapper
def hello_app(request, name = '') :
    ...
    content = tmpl.render(name =name, data =data)
     return Response(body =content)
執行main.py,訪問 http://localhost:8080/hello/Python

總結

以上部分的實現,就是類似Django框架中的幾個主要的功能模塊,雖然感覺有點東拼西湊的,
但大體可以了解一個web框架需要的基本功能,不過要開發出一個成熟的web框架,還需要更多模塊來
支持,比如Session管理,安全機制等等,感興趣的話,可以閱讀Django的源代碼,去了解更加詳細的
框架實現機制。
 






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM