本文主要内容:将Web框架和Web服务器结合实现简单的服务器,及相关python知识的了解

0x01 服务器代码(MyWebServer.py)

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
# coding:utf-8

import socket
import re
import sys

from multiprocessing import Process
from MyWebFramework import Application

# 设置静态文件根目录
HTML_ROOT_DIR = "./html"

WSGI_PYTHON_DIR = "./wsgipython"


class HTTPServer(object):
""""""
def __init__(self, application):
"""构造函数, application指的是框架的app"""
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.app = application

def start(self):
self.server_socket.listen(128)
while True:
client_socket, client_address = self.server_socket.accept()
# print("[%s, %s]用户连接上了" % (client_address[0],client_address[1]))
print("[%s, %s]用户连接上了" % client_address)
handle_client_process = Process(target=self.handle_client, args=(client_socket,))
handle_client_process.start()
client_socket.close()

def start_response(self, status, headers):
"""
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
star
"""
response_headers = "HTTP/1.1 " + status + "\r\n"
for header in headers:
response_headers += "%s: %s\r\n" % header

self.response_headers = response_headers

def handle_client(self, client_socket):
"""处理客户端请求"""
# 获取客户端请求数据
request_data = client_socket.recv(1024)
print("request data:", request_data)
request_lines = request_data.splitlines()
for line in request_lines:
print(line)

# 解析请求报文
# 'GET / HTTP/1.1'
request_start_line = request_lines[0]
# 提取用户请求的文件名
print("*" * 10)
print(request_start_line.decode("utf-8"))
file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line.decode("utf-8")).group(1)
method = re.match(r"(\w+) +/[^ ]* ", request_start_line.decode("utf-8")).group(1)

env = {
"PATH_INFO": file_name,
"METHOD": method
}
response_body = self.app(env, self.start_response)

response = self.response_headers + "\r\n" + response_body

# 向客户端返回响应数据
client_socket.send(bytes(response, "utf-8"))

# 关闭客户端连接
client_socket.close()

def bind(self, port):
self.server_socket.bind(("", port))


def main():
sys.path.insert(1, WSGI_PYTHON_DIR)
if len(sys.argv) < 2:
sys.exit("python MyWebServer.py Module:app")
# python MyWebServer.py MyWebFrameWork:app
module_name, app_name = sys.argv[1].split(":")
# module_name = "MyWebFrameWork"
# app_name = "app"
m = __import__(module_name)
app = getattr(m, app_name)
http_server = HTTPServer(app)
# http_server.set_port
http_server.bind(8000)
http_server.start()


if __name__ == "__main__":
main()

1.1 sys.argv

  • sys.argv[]

    一个从程序外部获取参数的桥梁,外部参数不唯一,所以sys.argv以列表形式存储参数;其第一个元素是程序本身,随后才依次是外部给予的参数

  • 简单测试

1
2
3
#本测试是在jupyter notebook中测试
import sys
sys.argv
['c:\\users\\fishmouse\\appdata\\local\\programs\\python\\python37\\lib\\site-packages\\ipykernel_launcher.py',
 '-f',
 'C:\\Users\\fishmouse\\AppData\\Roaming\\jupyter\\runtime\\kernel-049997d2-f810-4826-9743-6db3b4d2e9ea.json']
  • 本地测试

测试代码

1
2
3
import sys
b=sys.argv
print(b)
  • 测试结果(第三个输出原因是在测试代码中打印了b[0])

  • Pycharm为当前程序添加(python MyWebServer.py)的参数

0x02 Web框架代码(MyWebFramework.py)

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
# coding:utf-8

import time

# from MyWebServer import HTTPServer


# 设置静态文件根目录
HTML_ROOT_DIR = "./html"


class Application(object):
"""框架的核心部分,也就是框架的主题程序,框架是通用的"""
def __init__(self, urls):
# 设置路由信息
self.urls = urls

def __call__(self, env, start_response):
path = env.get("PATH_INFO", "/")
# /static/index.html
if path.startswith("/static"):
# 要访问静态文件
file_name = path[7:]
# 打开文件,读取内容
try:
file = open(HTML_ROOT_DIR + file_name, "rb")
except IOError:
# 代表未找到路由信息,404错误
status = "404 Not Found"
headers = []
start_response(status, headers)
return "not found"
else:
file_data = file.read()
file.close()

status = "200 OK"
headers = []
start_response(status, headers)
return file_data.decode("utf-8")

for url, handler in self.urls:
#("/ctime", show_ctime)
if path == url:
return handler(env, start_response)

# 代表未找到路由信息,404错误
status = "404 Not Found"
headers = []
start_response(status, headers)
return "not found"


def show_ctime(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
start_response(status, headers)
return time.ctime()


def say_hello(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
start_response(status, headers)
return "hello world"

def say_haha(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
start_response(status, headers)
return "hello big world"

urls = [
("/", show_ctime),
("/ctime", show_ctime),
("/sayhello", say_hello),
("/sayhaha", say_haha),
]
app = Application(urls)
# if __name__ == "__main__":
# urls = [
# ("/", show_ctime),
# ("/ctime", show_ctime),
# ("/sayhello", say_hello),
# ("/sayhaha", say_haha),
# ]
# app = Application(urls)
# http_server = HTTPServer(app)
# http_server.bind(8000)
# http_server.start()

2.1 __call__

  • 在Python中,函数其实是一个对象:
1
2
f=abs
f.__name__
'abs'
1
f(-123)
123



由于 f 可以被调用,所以,f 被称为可调用对象。
所有的函数都是可调用对象。
一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法__call__()。
  • 我们把 Person 类变成一个可调用对象:
1
2
3
4
5
6
7
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __call__(self, friend):
print('My name is %s...' % self.name)
print ('My friend is %s...' % friend)
  • 可以对 Person 实例直接调用:
1
2
p = Person('Bob', 'male')
p("Tim")
My name is Bob...
My friend is Tim...


单看 p('Tim') 你无法确定 p 是一个函数还是一个类实例,所以,在Python中,函数也是对象,对象和函数的区别并不显著。

测试结果