基础知识
file-like Object
StringIO就是在内存中创建的file-like Object,常用作临时缓冲。
import导入的类(或函数)要通过前缀的方式访问
__name__和__file__的作用
#文件名为 " ok7.py "
import os,os.path
print __name__
print __file__
print os.path.abspath(__file__)
print os.path.dirname(os.path.abspath(__file__))
print os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
结果:
编码问题
>>> u'中'
u'\u4e2d'
所以 u '中' = u ' \u4e2d ',只不过一个是计算机的存储形式,一个是计算机的显示形式。Unicode和 utf - 8 之间用encode和decode方法相互转化。
URL编码
编码:urllib.quote(string[, safe]),除了三个符号“ _ . - ”外,将所有符号编码,后面的参数safe是不编码的字符,
使用的时候如果不设置的话,会将斜杠,冒号,等号,问号都给编码了。
如下:
>>> import urllib
>>> print urllib.quote("http://neeao.com/index.php?id=1")
http%3A//neeao.com/index.php%3Fid%3D1
这样在使用urllib.urlopen打开编码后的网址的时候,就会报错了。
设置不编码的符号:
>>> print urllib.quote("http://neeao.com/index.php?id=1",":?=/")
http://neeao.com/index.php?id=1
这下就好了。
CGI环境变量
所有的CGI程序都接收以下的环境变量,这些变量在CGI程序中发挥了重要的作用:
变量名 | 描述 |
---|---|
CONTENT_TYPE | 这个环境变量的值指示所传递来的信息的MIME类型。目前,环境变量CONTENT_TYPE一般都是:application/x-www-form-urlencoded,他表示数据来自于HTML表单。 |
CONTENT_LENGTH | 如果服务器与CGI程序信息的传递方式是POST,这个环境变量即使从标准输入STDIN中可以读到的有效数据的字节数。这个环境变量在读取所输入的数据时必须使用。 |
HTTP_COOKIE | 客户机内的 COOKIE 内容。 |
HTTP_USER_AGENT | 提供包含了版本数或其他专有数据的客户浏览器信息。 |
PATH_INFO | 这个环境变量的值表示紧接在CGI程序名之后的其他路径信息。它常常作为CGI程序的参数出现。 |
QUERY_STRING | 如果服务器与CGI程序信息的传递方式是GET,这个环境变量的值即使所传递的信息。这个信息经跟在CGI程序名的后面,两者中间用一个问号'?'分隔。 |
REMOTE_ADDR | 这个环境变量的值是发送请求的客户机的IP地址,例如上面的192.168.1.67。这个值总是存在的。而且它是Web客户机需要提供给Web服务器的唯一标识,可以在CGI程序中用它来区分不同的Web客户机。 |
REMOTE_HOST | 这个环境变量的值包含发送CGI请求的客户机的主机名。如果不支持你想查询,则无需定义此环境变量。 |
REQUEST_METHOD | 提供脚本被调用的方法。对于使用 HTTP/1.0 协议的脚本,仅 GET 和 POST 有意义。 |
SCRIPT_FILENAME | CGI脚本的完整路径 |
SCRIPT_NAME | CGI脚本的的名称 |
SERVER_NAME | 这是你的 WEB 服务器的主机名、别名或IP地址。 |
SERVER_SOFTWARE | 这个环境变量的值包含了调用CGI程序的HTTP服务器的名称和版本号。例如,上面的值为Apache/2.2.14(Unix) |
cookie
<html>
<body>
<form enctype="multipart/form-data" action="save_file.py" method="post">
<p>File: <input type="file" name="filename" /></p>
<p><input type="submit" value="Upload" /></p>
</form>
</body>
</html>
<form enctype="value"> #value指属性值
属性值 | 描述 |
---|---|
application/x-www-form-urlencoded | 在发送前编码表单的所有字符(默认) |
multipart/form-data | 不对字符编码。 在使用包含文件上传控件的表单时,必须使用该值。 |
text/plain | 空格转换为 "+" 加号,但不对特殊字符编码。 |
结果:
#coding=utf-8
#!/usr/bin/python
import cgi, os
import cgitb; cgitb.enable()
form = cgi.FieldStorage()
# 获取文件名
fileitem = form['filename']
# 检测文件是否上传
if fileitem.filename:
# 设置文件路径
fn = os.path.basename(fileitem.filename)
open('/tmp/' + fn, 'wb').write(fileitem.file.read())
message = 'The file "' + fn + '" was uploaded successfully'
else:
message = 'No file was uploaded'
print """\
Content-Type: text/html\n
<html>
<body>
<p>%s</p>
</body>
</html>
""" % (message,)
datetime strftime函数:
@decorator原理分析:所谓返回值就是指 函数的执行结果
# -*- coding:utf-8 -*-
import functools
def log(path):
def wrapper(func):
func.__bbbb__ = path
return func #wrapper的返回值(执行结果)
return wrapper #log函数的返回值(执行结果)
@log('x')
def now():
return 'hello'
print now.__bbbb__
得到:x
# -*- coding:utf-8 -*-
import functools
def log(path):
def wrapper(func):
func.__bbbb__ = path
return func()
return wrapper
@log('x')
def now():
return 'hello'
print now.__bbbb__
得到:
观察包装函数,
@log('x')
def now():
return 'hello'
相当于执行log( ' x ' )( now ),首先log( ' x ' )返回wrapper闭包(函数),同时' x ' 作为参数传递给wrapper,得到的是
wrapper( now ),这是直接执行wrapper函数啊!
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print '%s %s():' % (text, func.__name__)
return func(*args, **kw)
return wrapper
return decorator
@log('x')
def now():
return 'hello'
用now()函数调用包装函数log后,now就不再指代原函数,而是指代
now==log('x')(now)=decorator(now),即执行decorator()函数的结果——返回的wrapper函数,也就是说now现在指代wrapper函数。现在验证一下:
def view(path):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
r= func(*args, **kw)
if isinstance(r, dict):
logging.info('return Template')
return Template(path,**r) #wrapper函数的返回值
raise ValueError('Expect return a dict when using @view() decorator.')
return wrapper #decorator函数的返回值
return decorator #view函数的返回值
def post(path):
def _decorator(func):
func.__web_route__ = path
func.__web_method__ = 'POST'
return func
return _decorator
@view('test/view.html')
@post('/post/:id')
def testpost():
return dict(a=1)
print testpost.__web_route__
print testpost.__web_method__
两个装饰器叠加使用,默认先执行里面的装饰器,即@post('/post/:id'),可以立即获得 testpost.__web_route__ 和 testpost.__web_method__;再调用外面的装饰器@view('test/view.html') 得到一个wrapper()函数,但并未执行(未激活,返回值是Template的一个实例),最后需要激活wrapper函数,才能得到那个Template实例。
dir()函数和getattr()的使用
# -*- coding: utf-8 -*-
import logging
from transwarp.web import get, view
from models import User, Blog, Comment
@view('test_users.html')
@get('/')
def test_users():
users = User.find_all()
return dict(users=users)
执行下列命令:
python的Web框架 bottle
Web各部分之间的逻辑关系 (WSGI,很好的资料!)
Nginx/Apache/lighttpd:相当于一个request proxy,根据配置,把不同的请求转发给不同的server处理,例如静态的文件请求自己处理,这个时候它就像一个web server,对于fastcgi/python这样的请求转发给flup这样的Server/Gateway进行处理;它们处理的是网络中最底下的两层—网络通信层和协议层。网络通信层:包括TCP网络通信的若干工作,比如bind,listen,connect,receive,send,disconnect;协议层:Apache可以处理http协议中的request和response,并且可以解释header和body;
flup:一个用python写的web server(实际是wsgi server),也就是cgi中所谓的Server/Gateway,它负责接受apache/lighttpd转发的请求,并调用你写的程序(application),并将application处理的结果返回到apache/lighttpd;
WSGI:它是Python应用程序或框架和Web服务器之间交互的接口规范,它规范了Python程序和服务器之间的通信。对于Apache/Nginx等服务器来说,只要支持WSGI规范,就可以保证所有(兼容WSGI的)Python程序能运行;对于Python程序或者框架来说,只要兼容WSGI,就可以保证能在所有(支持WSGI的)服务器上运行。
fastcgi:fastcgi则是Python程序和服务器间底层的通信协议的规范。apache/lighttpd的一个模块,虽然flup可以作为一个独立的web server使用,但是对于浏览器请求处理一般都交给apache/lighttpd处理,然后由apache/lighttpd转发给flup处理,这样就需要一个东西来把apache/lighttpd跟flup联系起来,这个东西就是fastcgi,它通过环境变量以及socket将客户端请求的信息传送给flup并接收flup返回的结果;
框架framework:应该说有了上面的东西你就可以开始编写你的web程序了,但是问题是你就要自己处理浏览器的输入输出,还有cookie、session、模板等各种各样的问题了,框架的作用就是帮你把这些工作都做好了,它就是所谓的web framework。
在WSGI规范下,Web组件被分成三类:client, server, and middleware. WSGI apps(服从WSGI规范的应用)能够被连接起来处理一个request,这也就引发了中间件这个概念,中间件同时实现c端和s端的接口,c看它是上游s,s看它是下游的c。WSGI的s端所做的工作仅仅是接收请求,传给application(做处理),然后将结果response给middleware或client. 除此以外的工作都交给中间件或者application来做。对一个WSGI程序(callable的对象,可以是函数也可以是实现了__call__的对象),将request作为参数传入(不再是纯文本,而是经过包装),同样将经过包装的response作为响应返回。request/response的包装由Python标准库提供。
wsgi是将web server参数python化,封装为request对象传递给apllication命名的func对象并接受其传出的response参数,由于其处理了参数封装和结果解析,wsgi从外观上很接近cgi的调用方式,和unix环境中调用一个程序也很类似:给你一堆环境变量,加上参数。如果把wsgi application看作一个脚本,其实就是外界通过wsgi调用python的脚本而已。
CGI是HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,是一种接口标准,根据其标准编写的程序须运行在网络服务器上,叫做cgi程序。
利用程序的标准输入输出流stdin, stdout,完成 HTTP 通信。HTTP 是文本协议,每次请求的文本以标准输入流的形式进入服务器端 CGI 程序,创建进程;然后进程的标准输出流作为响应。CGI是比较原始的开发动态网站的方式。
一个网站的动态内容是由程序生成的,这个程序接受客户端的请求,然后进行相应处理,再返回给客户端,客户端和服务端的通信是通过HTTP协议。然后我们会发现,这个程序在处理客户端请求的时候,大部分时候会进行很多重复的工作,比如说HTTP请求的解析。也就是说,你的程序需要解析HTTP请求,我的程序也需要解析。于是Web服务器如apache诞生了,apache解析这个HTTP请求,然后把这个请求的各种参数写进进程的环境变量,比如REQUEST_METHOD,PATH_INFO之类的。之后服务器会调用相应的程序来处理这个请求,这个程序也就是我们所要写的CGI程序了。它会负责生成动态内容,然后返回给apache,再由服务器转交给客户端。服务器和CGI程序之间通信,一般是通过进程的环境变量和管道。
相比于cgi每一个请求都产生一个进程 ,fastcgi是启动一个常驻进程。每次当apache接到request的时候,通过IPC或者socket将请求内容发给fastcgi进程,然后fastcgi再转发给上层应用程序。
后来又有人针对Python语言做了好多优化工作,产生了wsgi。经过wsgi之后,就全是Python程序了。request header、request body和response被wsgi server整理成了Python对象。例如字典、FileIO等。
framework拿到这些对象之后,就可以从request header中找到URI,然后根据url映射关系找到对应的函数开始执行。之后将函数的返回值整理成wsgi可以接受的response对象(实际上就是一个str或者generator)返回给wsgi server。
Flask
app = Flask(__name__)
传入的参数__name__表示这句脚本所在文件的名字,即 使用Flask框架的 “ .py ” 文件的名字,这样flask就能根据这个文件的名字找到其所在的目录,也就能找到这个目录下其他的包、模块、模板的根目录(HTML文件所在目录),这样才能完成模板调用和模块导入等工作,如:
wsgiapp.py就是调用Flask框架的PY文件(也就是app = Flask(__name__)脚本语句所在的文件),那么__name__等于wsgiapp,其所在的目录就是web应用的根目录。
而模板文件的目录就是templates,很容易根据__name__就找到这一目录以及其下的HTML文件共模板使用。其他的模块也容易找到并导入。
实际上,Web服务器使用WSGI协议,把接收自浏览器客户端的所有请求都交给这个程序实例( object)处理,实际上使用的是这个实例拥有的方法(route()就是app实例的URL mapping方法)。
@app.route('/', methods=['GET', 'POST'])
def home():
return '<h1>Home</h1>'
服务器并不知道什么请求要用什么函数处理,它只是把所有的请求通过WSGI interface 后都交给framework或WSGI application来处理。
那么怎样才能把从浏览器来的HTTP请求都正确的处理呢,这正是我们编写处理函数(URL映射函数)的初衷。
如上例,就是“GET / HTTP/1.1”的HTTP请求的处理函数。你可能也发现了,每一个HTTP请求都务必有一个对应的处理函数,否则浏览器端的请求就会显示“ 404 not found ” . ——对请求的处理是遍历的,这是我们使用服务器端脚本语言如PHP,python等编写脚本的目的!
那么Web框架要做的工作是什么呢?
1. 将请求的 URL 映射到处理它的代码上;
2. 动态地构造请求的 HTML 返回给客户端——HTML 中带有计算得到的值或者从数据库中取出来的信息。
试想如果不使用Web框架我们要怎么做?
答:编写庞杂的wsgi application传递给名为 wsgiref 的WSGI server(符合WSGI协议标准的服务器)调用。
def application(environ, start_response):
method = environ['REQUEST_METHOD']
path = environ['PATH_INFO']
if method=='GET' and path=='/':
return handle_home(environ, start_response)
if method=='POST' and path='/signin':
return handle_signin(environ, start_response)
...
很显然我们手动拆分HTTP请求参数environ(WSGI server给的),提取出我们想要的信息,然后根据这些信息与很多的“ if ”匹配,“ 定位 ”到精确的处理函数对HTTP请求进行具体的处理事务。
使用了框架之后呢?省略了这个拆分参数和精确“定位”的步骤,我们要做的仅仅是编写URL处理函数即可——什么样的URL请求用什么样的处理函数,
编写处理函数的唯一目的是“注册”—— 即让浏览器客户端URL请求有处可寻处理函数(因为已注册的URL和相应的处理函数是绑定的——通过@decorator)。浏览器客户端URL请求匹配到了注册的URL,就是找到了相应的处理函数。
注册能处理的所有URL,未注册的自然就是非法URL——浏览器请求这样的URL时,服务器将返回错误!
框架是怎么拆分参数的:框架同样接收environ参数作为request线程级变量,通过request获取精确的参数信息。
既然拆分参数的活交给框架来做,用户再用拆分的参数来定位处理函数就毫无意义了,那么怎样办呢?
框架是如何做到精确定位的:框架内部消化拆分后的参数(比如浏览器客户端URL请求路径参数)。Flask在此之前已经通过Python的装饰器@decorator在内部自动地把注册的URL和处理函数给关联起来——根据URL来调用相应的处理函数。
至于匹配方法,一般就是遍历,直到在注册的URL中找到第一个匹配的(正则匹配),然后调用这个注册的URL对应的处理函数(用@decorator绑定的)。
pylons
WSGI Middleware
URL Dispacher:
When the request passes down the middleware, the incoming URL gets parsed in the RoutesMiddleware, and if it matches a URL (See URL Configuration), the information about the controller that should be called is put into the environ dict for use by PylonsApp.
The PylonsApp then attempts to find a controller in the controllers directory that matches the name of the controller, and searches for a class inside it by a similar scheme (controller name + ‘Controller’, ie, HelloController). Upon finding a controller, its then called like any other WSGI application using the same WSGI interface that PylonsApp was called with.
New in version 1.0: Controller name can also be a dotted path to the module / callable that should be imported and called. For example, to use a controller named ‘Foo’ that is in the ‘bar.controllers’ package, the controller name would be bar.controllers:Foo.
This is why the BaseController that resides in a project’s lib/base.py module inherits from WSGIController and has a __call__ method that takes the environ and start_response. The WSGIController locates a method in the class that corresponds to the action that Routes found, calls it, and returns the response completing the request.
Response时最先调用的是PylonsApp,Request 阶段的PylonsApp和Response阶段的PylonsApp不是一回事,前者接受来自Request 阶段的中间层RoutesMiddleware解析得到的URL信息(存储在environ dict中),根据URL信息得到相应的处理函数的信息存储在environ dict中,后者就要根据匹配好的处理函数的信息具体去controllers directory中找处理函数了(同时搜寻该处理函数中的一个类,类的名字是controller name + ‘Controller’)。找到之后就执行该处理函数WSGI application(按照WSGI接口标准调用),执行结果是an iterable (valid PEP 333 WSGI response), which is then sent back as the response.。
所有的处理函数(controller)都继承自BaseController,而lib/base.py模块里面的BaseController类继承自 WSGIController,且有一个__call__方法(参数是environ and start_response),WSGIController 找到一个和Routes匹配的动作相一致的方法(在__call__( )方法里面找),然后调用这个方法完成响应请求的工作。并且,In the event that an action is not found to handle the request, the Controller will raise an “Action Not Found” error if in debug mode, otherwise a 404 Not Found error will be returned.