一、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 字典
##>>> # 使用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,地址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 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框架中的几个主要的功能模块,希望对大家的学习有所帮助。
更多python模擬Django框架相关文章请关注PHP中文网!