WSGI(4)-服务器实现示例

本节将仔细说明服务器流程并使用pep代码讲解,并写一个自己的wsgi服务器,使其可以被浏览器支持,最后调用自定义的应用以及现有的flask等框架。

主要流程

服务器的主要流程如下:

  1. 接收application。函数实现可以是参数,类可以在init传入。
  2. 创建environ。设置wsgi版本,是否多线程多进程,http或https访问等。
  3. 将start_response函数传入应用,用于获取status和response_header。可接受exc_info,在响应内容被发送的情况下报错,如果在设置响应内容前响应内容缓存有问题则报错。
  4. 在start_response函数中缓存响应头信息。
  5. 获取应用返回的可迭代响应内容。
  6. 调用write函数,用于写数据。写数据必须要在start_response之后,需要获取了status和response_header信息。write第一次响应先写头信息,然后写响应内容。

PEP333代码

以下是PEP333提供的代码,这里加一个简单应用使用,这里没有加入http监听,所以只是简单调用即可,该代码实现了以上的流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import os
import sys


def run_with_cgi(application):
# 环境变量设置
environ = dict(os.environ.items())
environ['wsgi.input'] = sys.stdin
environ['wsgi.errors'] = sys.stderr
environ['wsgi.version'] = (1, 0)
environ['wsgi.multithread'] = False
environ['wsgi.multiprocess'] = True
environ['wsgi.run_once'] = True
# http访问设置
if environ.get('HTTPS', 'off') in ('on', '1'):
environ['wsgi.url_scheme'] = 'https'
else:
environ['wsgi.url_scheme'] = 'http'

headers_set = []
headers_sent = []

def write(data):
if not headers_set:
raise AssertionError("write() before start_response()")

elif not headers_sent:
# Before the first output, send the stored headers
status, response_headers = headers_sent[:] = headers_set
sys.stdout.write('Status: %s\r\n' % status)
for header in response_headers:
sys.stdout.write('%s: %s\r\n' % header)
sys.stdout.write('\r\n')
# 写数据
sys.stdout.write(data)
sys.stdout.flush()

def start_response(status, response_headers, exc_info=None):
if exc_info:
try:
if headers_sent: # 已发送响应头,则抛出错误
# Re-raise original exception if headers sent
raise exc_info[0], exc_info[1], exc_info[2]
finally:
exc_info = None # avoid dangling circular ref
elif headers_set: # 如果已经设置响应头,则抛出错误
raise AssertionError("Headers already set!")

headers_set[:] = [status, response_headers]
return write

result = application(environ, start_response)
try:
for data in result:
if data: # don't send headers until body appears
write(data)
if not headers_sent:
write('') # send headers now if body was empty
finally:
if hasattr(result, 'close'):
result.close()


def simple_app(environ, start_response):
response_headers = [('Content-Type', 'text/html')]
status = '200 OK'
start_response(status, response_headers)
return ["<h1>hello world</h1>"]


if __name__ == '__main__':
run_with_cgi(simple_app)

运行结果如下:

服务器编写

以上是pep333提供的服务器代码,实现了一个标准的wsgi服务器,这是一个简单的函数,下面通过改造使用一个类来进行管理,加入socket,使其可以通过浏览器访问。
首先说一下服务器的基本流程:

  1. init。初始化socket;
  2. bind。绑定socket到某个端口;
  3. listent。监听端口,持续循环;
  4. accept。接受请求;
  5. recv/send。获取数据并响应;
  6. close。关闭连接。

下面是实现了wsgi的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# coding=utf8

import sys
import socket
import datetime
import StringIO


class WSGIServer(object):
socket_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
socket_limit = 1

def __init__(self, host, port):
# 初始化
self.socket = socket.socket(self.socket_family, self.socket_type)
# 设置socket,否则可能出现地址被使用
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定主机和端口
self.socket.bind((host, port))
# 开始监听
self.socket.listen(self.socket_limit)
# 获取服务主机和端口
self.server_name, self.server_port = self.socket.getsockname()[:2]

# 设置应用
def set_application(self, application):
self.application = application

# 循环接收请求
def serve_forever(self):
while True:
# 获取连接
self.client_connect, self.client_address = self.socket.accept()
self.handle_request()

# 处理请求
def handle_request(self):
# 获取数据
self.request_data = self.client_connect.recv(1024)
self.request_lines = self.request_data.splitlines()
try:
self.parse_request()
env = self.get_environ()
resp_data = self.application(env, self.start_response)
self.finish_response(resp_data)
except Exception:
pass

# 获取请求方式,访问路径,以及访问版本
def parse_request(self):
request_line = self.request_lines[0]
self.request_method, self.path, self.request_version = request_line.split()

# 获取环境参数
def get_environ(self):
env = {
# wsgi环境参数
'wsgi.version': (0, 1),
'wsgi.url_scheme': 'http',
'wsgi.input': StringIO.StringIO(self.request_data),
'wsgi.errors': sys.stderr,
'wsgi.multithread': False,
'wsgi.multiprocess': False,
'wsgi.run_once': False,
# cgi环境参数
'REQUEST_METHOD': self.request_method,
'PATH_INFO': self.path,
'SERVER_NAME': self.server_name,
'SERVER_PORT': str(self.server_port)
}
return env

def start_response(self, status, response_headers, exc_info=None):
headers = [
('Data', datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT')),
('Server', 'WSGIServer 0.1')
]
self.response_headers = response_headers + headers
self.status = status

# 响应请求,写数据
def finish_response(self, resp_data):
try:
response = 'HTTP/1.1 {status}\r\n'.format(status=self.status)
for header in self.response_headers:
response += '{0}:{1}\r\n'.format(*header)
response += '\r\n'
for data in resp_data:
response += data
self.client_connect.sendall(response)
finally:
self.client_connect.close()


# 创建服务
def make_server(host, port, application):
server = WSGIServer(host, port)
server.set_application(application)
return server


def simple_app(environ, start_response):
response_headers = [('Content-Type', 'text/html')]
status = '200 OK'
start_response(status, response_headers)
return ["<h1>hello world</h1>"]


if __name__ == '__main__':
PORT = 7000
server = make_server('', PORT, simple_app)
server.serve_forever()

代码中的调用关系如下:

这里可以看到其主要流程有如下:

  1. main程序中调用make_server得到server;
  2. server调用server_forever开始循环监听;
  3. server_forever调用handle_request,用于处理请求;
  4. handle_request调用parse_request解析请求内容;
  5. handle_request调用get_environ获取env;
  6. handle_request调用application,传入env和start_response;
  7. application调用start_response,传入status和response_headers;
  8. application返回resp_data;
  9. handle_request调用finish_response,传入resp_data。

每次请求循环执行4-9。

使用flask应用

写一个简单的flask应用

1
2
3
4
5
6
7
8
9
10
11
from flask import Flask

app = Flask('flask_app')

@app.route('/')
def hello_world():
return 'Hello World!'

@app.route('/test')
def test():
return 'route_test'

调用方式:

1
2
3
PORT = 7000
server = make_server('', PORT, app.wsgi_app)
server.serve_forever()

需要注意的是这里应用需要使用flask实例的wsgi_app。
调用结果如下:

这里还可以加入更多的比如jinja模板等。