主要参考: Let’s Build A Web Serve
服务器
#/usr/bin/env python
# -*- coding: utf-8 -*-
'''
A module for WSGI: As a demostration
Usage:
python wsgiserver.py wsgiapp:app
'''
__author__ = 'Van Abel'
import socket
import StringIO
import sys
class WSGIServer(object):
socket_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
max_queue = 10
def __init__(self, s_addr):
# Create a listening socket
self.s = s = socket.socket(
self.socket_family,
self.socket_type
)
# Allow to reuse the same address
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
# Bind to addr, port
s.bind(s_addr)
# Listen on port
s.listen(self.max_queue)
except socket.error as e:
print 'Server error >> %s ' % e
sys.exit(1)
# Get server host,port
shost, self.s_port = self.s.getsockname()[:2]
self.s_host = socket.getfqdn(shost)
# Return headers set by web framework/app
self.headers = []
def set_app(self, app):
self.app = app
def server_forever(self):
s = self.s
while True:
# New client conn
self.c_conn, c_addr = s.accept()
# Handle request and close
# Then loop over to wait for another client conn
self.handle()
def handle(self):
# Read request from client
self.r_data = r_data = self.c_conn.recv(1024)
# Print formatted request data as 'curl -v'
print(''.join(
'< {line}\n'.format(line=line)
for line in r_data.splitlines()
))
# Parse the request
self.parse(r_data)
# Construct environment dict useing r_data
env = self.get_env()
# Call app(defined in wsgiapp.py and pass in by set_app())
# and get result as HTTP response body
b_data = self.app(env, self.start)
# Construct a response and send to client
self.send(b_data)
def parse(self, data):
headers = data.splitlines()[0].rstrip('\r\n')
# Break down the headers
(
self.method,
self.path,
self.protcl
) = headers.split()
def get_env(self):
env = {}
# Required WSGI variables
env['wsgi.version'] = (1, 0)
env['wsgi.url_scheme'] = 'http'
env['wsgi.input'] = StringIO.StringIO(self.r_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.method
env['PATH_INFO'] = self.path
env['SERVER_NAME'] = self.protcl
env['SERVER_PORT'] = str(self.s_port)
return env
def start(self, status, r_headers, exc_info=None):
# Add necessary server header
s_headers = [
('Data', 'Tue, 19 Dec 2017 15:23:41 CST'),
('Server', 'WSGI Server 0.1')
]
self.headers = [status, r_headers + s_headers]
def send(self, body):
try:
status, r_headers = self.headers
r = 'HTTP/1.x {status}\r\n'.format(status=status)
for header in r_headers:
r += '{0}: {1}\r\n'.format(*header)
r += '\r\n'
for data in body:
r += data
# Print formatted response data as 'curl -v'
print(''.join(
'> {line}\n'.format(line=line)
for line in r.splitlines()
))
self.c_conn.sendall(r)
finally:
self.c_conn.close()
SERVER_ADDRESS = (HOST, PORT) = '', 8000
def make_server(s_addr, app):
server = WSGIServer(s_addr)
server.set_app(app)
return server
if __name__ == '__main__':
if len(sys.argv) < 2:
sys.exit('Usage:\n\tProvide a WSGI app object as module:callable')
app_path = sys.argv[1]
module, app = app_path.split(':')
module = __import__(module)
app = getattr(module, app)
httpd = make_server(SERVER_ADDRESS, app)
print('WSGIServer: Serving HTTP on port {port} ...\n'.format(port=PORT))
httpd.server_forever()
客户端
#/usr/bin/env python
# -*- coding: utf-8 -*-
'''
wsgiapp.py
A demo client for server_wsgi.py
'''
__author__ = 'Van Abel'
def app(environ, start):
# A barebones WSGI app
# you can take it as a start point of your webframework
status = '200 OK'
resp_h = [('Content-Type', 'text/plain')]
start(status, resp_h)
return 'Hello world from a simple WSGI app!\n'