当咱们实现一个Web应用(application)的时候,一般不会考虑如何接受HTTP请求、解析HTTP请求、发送HTTP响应等等,咱们只关心处理逻辑,而不用去关心HTTP规范的细节。html
之因此有这层透明,是由于Web Server和Web Application之间有一套规范的接口,这套接口帮咱们隐藏了不少HTTP相关的细节。这套接口规范就是WSGI(Web Server Gateway Interface)。python
Web Server和Web Application都实现WSGI规范,而后各司其职:浏览器
下面就一步步看下WSGI规范的更多内容。app
上面了解到,Web Server和Web Application端都要遵照WSGI规范。对于实现WSGI的Web Application端,必须是一个callable的对象(类,函数,方法等等,实现__call__魔术方法的对象),这个callable对象须要知足下面两个条件:函数
下面就是一个实现Application Interface的一个application函数:post
# This is an application object. It could have any name, except when using mod_wsgi where it must be "application" # The application object accepts two arguments # This is an application object. It could have any name, except when using mod_wsgi where it must be "application" # The application object accepts two arguments def application( # environ points to a dictionary containing CGI like environment variables # which is filled by the server for each received request from the client environ, # start_response is a callback function supplied by the server # which will be used to send the HTTP status and headers to the server start_response): # build the response body possibly using the environ dictionary response_body = 'The request method was %s' % environ['REQUEST_METHOD'] # HTTP response code and message status = '200 OK' # These are HTTP headers expected by the client. # They must be wrapped as a list of tupled pairs: # [(Header name, Header value)]. response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))] # Send them to the server using the supplied function start_response(status, response_headers) # Return the response body. # Notice it is wrapped in a list although it could be any iterable. return [response_body]
在Python中就有一个WSGI server,咱们能够直接使用。ui
在下面的这个例子中,WSGI server监听了"localhost:8080",并绑定了一个支持WSGI规范的application对象;application对象就会处理来自8080端口,并将"Environment dict"的内容生产response传给WSGI server。this
# WSGI server in Python from wsgiref.simple_server import make_server def application(environ, start_response): # Sorting and stringifying the environment key, value pairs response_body = ['%s: %s' % (key, value) for key, value in sorted(environ.items())] response_body = '\n'.join(response_body) status = '200 OK' response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))] start_response(status, response_headers) return [response_body] # Instantiate the WSGI server. # It will receive the request, pass it to the application # and send the application's response to the client httpd = make_server( 'localhost', # The host name. 8080, # A port number where to wait for the request. application # Our application object name, in this case a function. ) # Wait for a single request, serve it and quit. httpd.handle_request() # Keep the server always alive with serve_forever() # httpd.serve_forever()
注意,在application对象返回的时候,咱们使用的是"return [response_body]",当咱们改为"return response_body"以后,同样能够工做,可是效率会很低,由于返回的时候会去迭代response字符串中的每个字符。因此,当处理response字符串的时候,最好是将它包在一个可迭代对象中,例如list。spa
经过浏览器访问后,就能够获得"Environment dict"的内容,这些都是WSGI server提供的信息,包括了HTTP请求的相关信息。code
当咱们执行一个以下的GET请求:
http://127.0.0.1:8080/?name=wilber&hobbies=software
QUERY_STRING(URL中"?"以后的部分)和REQUEST_METHOD这些信息会包含在"Environment dict",从application中能够很方便的获得这些信息。
在application中,可使用cgi模块中的parse_qs函数获得一个由QUERY_STRING生成的字典,方便咱们取出请求的变量信息。
同时,为了不客户端的输入可能存在的脚本注入,可使用cgi模块中的escape函数对输入进行一次过滤。
下面直接看例子:
from wsgiref.simple_server import make_server from cgi import parse_qs, escape html = """ <html> <body> <form method="get" action="/"> <p> Name: <input type="text" name="name"> </p> <p> Hobbies: <input name="hobbies" type="checkbox" value="running"> running <input name="hobbies" type="checkbox" value="swimming"> swimming <input name="hobbies" type="checkbox" value="reading"> reading </p> <p> <input type="submit" value="Submit"> </p> </form> <p> Name: %s<br> Hobbies: %s </p> </body> </html>""" def application(environ, start_response): print "QUERY_STRING: %s" %environ['QUERY_STRING'] print "REQUEST_METHOD: %s" %environ['REQUEST_METHOD'] # Returns a dictionary containing lists as values. d = parse_qs(environ['QUERY_STRING']) # In this idiom you must issue a list containing a default value. name = d.get('name', [''])[0] # Returns the first name value. hobbies = d.get('hobbies', []) # Returns a list of hobbies. # Always escape user input to avoid script injection name = escape(name) hobbies = [escape(hobby) for hobby in hobbies] response_body = html % (name or 'Empty', ', '.join(hobbies or ['No Hobbies'])) status = '200 OK' # Now content type is text/html response_headers = [('Content-Type', 'text/html'), ('Content-Length', str(len(response_body)))] start_response(status, response_headers) return [response_body] httpd = make_server('localhost', 8080, application) # Now it is serve_forever() in instead of handle_request(). # In Windows you can kill it in the Task Manager (python.exe). # In Linux a Ctrl-C will do it. httpd.serve_forever()
从结果中能够看到,请求URL中的QUERY_STRING被WSGI server填入了"Environment dict"中。
当执行一个POST请求的时候,query string是不会出如今URL里面的,而是会包含在request body中。
对于WSGI server,request body存放在"Environment dict"中(environ['wsgi.input']),environ['wsgi.input']对应的是一个file object,能够经过读取文件的方式读取request body。同时,environ.get('CONTENT_LENGTH', 0)中存放着request body的size,咱们能够根据这个值来读取适当长度的request body。
看下面的例子:
from wsgiref.simple_server import make_server from cgi import parse_qs, escape html = """ <html> <body> <form method="post" action="parsing_post.wsgi"> <p> Name: <input type="text" name="name"> </p> <p> Hobbies: <input name="hobbies" type="checkbox" value="running"> running <input name="hobbies" type="checkbox" value="swimming"> swimming <input name="hobbies" type="checkbox" value="reading"> reading </p> <p> <input type="submit" value="Submit"> </p> </form> <p> Name: %s<br> Hobbies: %s </p> </body> </html> """ def application(environ, start_response): # the environment variable CONTENT_LENGTH may be empty or missing try: request_body_size = int(environ.get('CONTENT_LENGTH', 0)) except (ValueError): request_body_size = 0 # When the method is POST the query string will be sent # in the HTTP request body which is passed by the WSGI server # in the file like wsgi.input environment variable. request_body = environ['wsgi.input'].read(request_body_size) d = parse_qs(request_body) print "wsgi.input %s" %environ['wsgi.input'] print "request_body_size %s" %environ.get('CONTENT_LENGTH', 0) print "request_body %s" %request_body name = d.get('name', [''])[0] # Returns the first name value. hobbies = d.get('hobbies', []) # Returns a list of hobbies. # Always escape user input to avoid script injection name = escape(name) hobbies = [escape(hobby) for hobby in hobbies] response_body = html % (name or 'Empty', ', '.join(hobbies or ['No Hobbies'])) status = '200 OK' response_headers = [('Content-Type', 'text/html'), ('Content-Length', str(len(response_body)))] start_response(status, response_headers) return [response_body] httpd = make_server('localhost', 8080, application) httpd.serve_forever()
经过结果,咱们能够看到environ字典中对应的"wsgi.input"和"CONTENT_LENGTH",以及读取出来的"request body"。
本文介绍了WSGI的一些基本内容,以及如何解析GET和POST请求中的参数。
经过WSGI这个规范,Web application的开发人员能够不用关心HTTP协议中的细节问题。