Last active
August 29, 2015 14:23
-
-
Save tawateer/f7acb549c3a49d2c82ae to your computer and use it in GitHub Desktop.
WSGIServer 例子, 理解什么是 WSGI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from flask import Flask | |
from flask import Response | |
flask_app = Flask('flaskapp') | |
@flask_app.route('/hello') | |
def hello_world(): | |
return Response( | |
'Hello world from Flask!\n', | |
mimetype='text/plain' | |
) | |
wsgi_app = flask_app.wsgi_app |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from pyramid.config import Configurator | |
from pyramid.response import Response | |
def hello_world(request): | |
return Response( | |
'Hello world from Pyramid!\n', | |
content_type='text/plain', | |
) | |
config = Configurator() | |
config.add_route('hello', '/hello') | |
config.add_view(hello_world, route_name='hello') | |
wsgi_app = config.make_wsgi_app() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import tornado.web | |
import tornado.wsgi | |
class MainHandler(tornado.web.RequestHandler): | |
def get(self): | |
self.write("Hello world from Tornado!") | |
application = tornado.web.Application([ | |
(r"/hello", MainHandler), | |
]) | |
wsgi_app = tornado.wsgi.WSGIAdapter(application) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/env python | |
class Test(): | |
def __init__(self, app): | |
self.app = app | |
def handler(self): | |
return self.app(self.set_headers) | |
def set_headers(self, content): | |
self.headers_set = content | |
def get_headers(self): | |
return self.headers_set | |
class App(): | |
def __call__(self, func): | |
func("test") | |
return "test" * 2 | |
app = App() | |
test = Test(app) | |
print test.handler() | |
print test.get_headers() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class WSGIAdapter(object): | |
"""Converts a `tornado.web.Application` instance into a WSGI application. | |
Example usage:: | |
import tornado.web | |
import tornado.wsgi | |
import wsgiref.simple_server | |
class MainHandler(tornado.web.RequestHandler): | |
def get(self): | |
self.write("Hello, world") | |
if __name__ == "__main__": | |
application = tornado.web.Application([ | |
(r"/", MainHandler), | |
]) | |
wsgi_app = tornado.wsgi.WSGIAdapter(application) | |
server = wsgiref.simple_server.make_server('', 8888, wsgi_app) | |
server.serve_forever() | |
See the `appengine demo | |
<https://github.com/tornadoweb/tornado/tree/stable/demos/appengine>`_ | |
for an example of using this module to run a Tornado app on Google | |
App Engine. | |
In WSGI mode asynchronous methods are not supported. This means | |
that it is not possible to use `.AsyncHTTPClient`, or the | |
`tornado.auth` or `tornado.websocket` modules. | |
.. versionadded:: 4.0 | |
""" | |
def __init__(self, application): | |
if isinstance(application, WSGIApplication): | |
self.application = lambda request: web.Application.__call__( | |
application, request) | |
else: | |
self.application = application | |
def __call__(self, environ, start_response): | |
method = environ["REQUEST_METHOD"] | |
uri = urllib_parse.quote(from_wsgi_str(environ.get("SCRIPT_NAME", ""))) | |
uri += urllib_parse.quote(from_wsgi_str(environ.get("PATH_INFO", ""))) | |
if environ.get("QUERY_STRING"): | |
uri += "?" + environ["QUERY_STRING"] | |
headers = httputil.HTTPHeaders() | |
if environ.get("CONTENT_TYPE"): | |
headers["Content-Type"] = environ["CONTENT_TYPE"] | |
if environ.get("CONTENT_LENGTH"): | |
headers["Content-Length"] = environ["CONTENT_LENGTH"] | |
for key in environ: | |
if key.startswith("HTTP_"): | |
headers[key[5:].replace("_", "-")] = environ[key] | |
if headers.get("Content-Length"): | |
body = environ["wsgi.input"].read( | |
int(headers["Content-Length"])) | |
else: | |
body = b"" | |
protocol = environ["wsgi.url_scheme"] | |
remote_ip = environ.get("REMOTE_ADDR", "") | |
if environ.get("HTTP_HOST"): | |
host = environ["HTTP_HOST"] | |
else: | |
host = environ["SERVER_NAME"] | |
connection = _WSGIConnection(method, start_response, | |
_WSGIRequestContext(remote_ip, protocol)) | |
# wgsi 的 start_response 函数, 被封装在 connection. | |
# 以 tornado 为例, start_response 最终是在 tornado.web.RequestHandler | |
# 的 flush 函数中执行. | |
request = httputil.HTTPServerRequest( | |
method, uri, "HTTP/1.1", headers=headers, body=body, | |
host=host, connection=connection) | |
# 用 HTTPServerRequest 封装 request. | |
request._parse_body() | |
self.application(request) # 这里的 application 是 http application, | |
# 也就是 tornado.wsgi.WSGIAdapter(application)传入的 application; | |
# 这里调用 http application 来处理 request. | |
# 以 torando 为例, 此句会调用 _RequestDispatcher 类, 传入此applition 和 | |
# request, 因为 application 在初始化的时候指定了 path 和 RequestHandler, | |
# 比如 [(r"/", MainHandler), ...], 所以根据 reqeust 中的 path就能知道对应 | |
# 的 RequststHander, 然后执行 RequestHandler, 传入 application 和 request, | |
# 在 RequestHandler 里面会调用 self.request.connection.write_headers, 里面 | |
# 会执行 start_response 函数. | |
if connection._error: | |
raise connection._error | |
if not connection._finished: | |
raise Exception("request did not finish synchronously") | |
return connection._write_buffer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/env python | |
# Tested with Python 2.7.9, Linux & Mac OS X | |
import socket | |
import StringIO | |
import sys | |
class WSGIServer(object): | |
address_family = socket.AF_INET | |
socket_type = socket.SOCK_STREAM | |
request_queue_size = 1 | |
def __init__(self, server_address): | |
# Create a listening socket | |
self.listen_socket = listen_socket = socket.socket( | |
self.address_family, | |
self.socket_type | |
) | |
# Allow to reuse the same address | |
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
# Bind | |
listen_socket.bind(server_address) | |
# Activate | |
listen_socket.listen(self.request_queue_size) | |
# Get server host name and port | |
host, port = self.listen_socket.getsockname()[:2] | |
self.server_name = socket.getfqdn(host) | |
self.server_port = port | |
# Return headers set by Web framework/Web application | |
self.headers_set = [] | |
def set_app(self, application): | |
self.application = application # self.application 是 wsgi app, 它里面有一个 http application. | |
def serve_forever(self): | |
listen_socket = self.listen_socket | |
while True: | |
# New client connection | |
self.client_connection, client_address = listen_socket.accept() | |
# Handle one request and close the client connection. Then | |
# loop over to wait for another client connection | |
self.handle_one_request() | |
def handle_one_request(self): | |
self.request_data = request_data = self.client_connection.recv(1024) | |
# Print formatted request data a la 'curl -v' | |
print(''.join( | |
'< {line}\n'.format(line=line) | |
for line in request_data.splitlines() | |
)) | |
self.parse_request(request_data) | |
# Construct environment dictionary using request data | |
env = self.get_environ() | |
# It's time to call our application callable and get | |
# back a result that will become HTTP response body | |
result = self.application(env, self.start_response) # 这句话最重要, 调用 wsgi app 的 __call__ 方法, | |
# 传入 env 和 start_response, start_response 是 | |
# 本地函数. | |
# Construct a response and send it back to the client | |
self.finish_response(result) | |
def parse_request(self, text): | |
request_line = text.splitlines()[0] | |
request_line = request_line.rstrip('\r\n') | |
# Break down the request line into components | |
(self.request_method, # GET | |
self.path, # /hello | |
self.request_version # HTTP/1.1 | |
) = request_line.split() | |
def get_environ(self): | |
env = {} | |
# The following code snippet does not follow PEP8 conventions | |
# but it's formatted the way it is for demonstration purposes | |
# to emphasize the required variables and their values | |
# | |
# Required WSGI variables | |
env['wsgi.version'] = (1, 0) | |
env['wsgi.url_scheme'] = 'http' | |
env['wsgi.input'] = StringIO.StringIO(self.request_data) | |
env['wsgi.errors'] = sys.stderr | |
env['wsgi.multithread'] = False | |
env['wsgi.multiprocess'] = False | |
env['wsgi.run_once'] = False | |
# Required CGI variables | |
env['REQUEST_METHOD'] = self.request_method # GET | |
env['PATH_INFO'] = self.path # /hello | |
env['SERVER_NAME'] = self.server_name # localhost | |
env['SERVER_PORT'] = str(self.server_port) # 8888 | |
return env | |
def start_response(self, status, response_headers, exc_info=None): | |
# Add necessary server headers | |
server_headers = [ | |
('Date', 'Tue, 31 Mar 2015 12:54:48 GMT'), | |
('Server', 'WSGIServer 0.2'), | |
] | |
self.headers_set = [status, response_headers + server_headers] | |
# To adhere to WSGI specification the start_response must return | |
# a 'write' callable. We simplicity's sake we'll ignore that detail | |
# for now. | |
# return self.finish_response | |
def finish_response(self, result): | |
try: | |
status, response_headers = self.headers_set | |
response = 'HTTP/1.1 {status}\r\n'.format(status=status) | |
for header in response_headers: | |
response += '{0}: {1}\r\n'.format(*header) | |
response += '\r\n' | |
for data in result: | |
response += data | |
# Print formatted response data a la 'curl -v' | |
print(''.join( | |
'> {line}\n'.format(line=line) | |
for line in response.splitlines() | |
)) | |
self.client_connection.sendall(response) | |
finally: | |
self.client_connection.close() | |
SERVER_ADDRESS = (HOST, PORT) = '', 8888 | |
def make_server(server_address, application): | |
server = WSGIServer(server_address) | |
server.set_app(application) | |
return server | |
if __name__ == '__main__': | |
if len(sys.argv) < 2: | |
sys.exit('Provide a WSGI application object as module:callable') | |
app_path = sys.argv[1] | |
module, application = app_path.split(':') | |
module = __import__(module) | |
application = getattr(module, application) | |
httpd = make_server(SERVER_ADDRESS, application) | |
print('WSGIServer: Serving HTTP on port {port} ...\n'.format(port=PORT)) | |
httpd.serve_forever() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment