带你徒手撸一个web服务器程序

平时我们开发web大部分情况下直接使用框架现拉的一个底层,今天我们来徒手撸一个简单的web服务器程序,通过这个简单的示例让你对框架是如何做到有更深的理解。

python自带静态web服务器

其实python给我们自带了一个静态web服务器,我们使用命令行随便进入到一个有静态文件的目录中,运行如下命令:

1
2
Tony-iMac:diandian_blog tony$ python3 -m http.server 8001
Serving HTTP on :: port 8001 (http://[::]:8001/) ...

-m选项说明:

-m表示运行包里面的模块,执行这个命令的时候,需要进入你自己指定静态文件的目录,然后通过浏览器就能访问对应的 html文件了,这样一个静态的web服务器就搭建好了。

此时访问浏览器

  • 静态Web服务器是为发出请求的浏览器提供静态文档的程序,
  • 搭建Python自带的Web服务器使用python3 –m http.server 端口号 这个命令即可,端口号不指定默认是8000

开发自己的静态Web服务器

在上一篇TCP网络应用程序开发流程回顾中,我们已经回顾了网络应用的开发流程,知道了HTTP协议本质上就是基于TCP,只不过http是一个通信一次即断开的TCP,相较于之前的交互型TCP应用是应该是更简单的。

我们这里只需要写一套服务端socket即可,浏览器是一个天生的客户端,代码中也包含了详细的注释,故也不在其他地方做解释。

响应固定页面

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/16 11:16
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : browser_server_socket.py
# @desc : 徒手搭建web服务器(基于socket)
import socket

# 创建socket套接字,使用ipv4,TCP传输协议
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口复用,程序退出,端口立即释放(默认大概程序退出后两分钟左右才释放端口)
socket_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定socket服务ip和端口号
socket_server.bind(('localhost', 9999))
# 监听3个连接服务器的客户端
socket_server.listen(3)
print("服务端开始运行了……")
# 循环等待客户端的连接
while True:
# 等待客户端连接
client_socket, ip_port = socket_server.accept()
print(ip_port, '连接上线了')
# 接收到的客户端发来的数据
recv_data = client_socket.recv(4096)
# 获取客户端发来的数据长度
recv_data_length = len(recv_data)
# # 判断客户端是否主动关闭了连接
if recv_data_length == 0:
client_socket.close()
print(ip_port, '断开了与服务器的链接')
# 响应行
response_line = 'HTTP/1.1 200 OK\r\n'
# 响应头
response_header = 'Server: PWS1.0\r\n'
# 空行
empty_line = '\r\n'
# 响应体
response_body = '<!DOCTYPE HTML><html><head><meta charset="UTF-8"><title>我是标题</title></head><body><h1>我是内容</h1></body></html>'
# 拼接http协议格式
response_data = (response_line+response_header+empty_line+response_body).encode('utf8')
# 发送响应数据
client_socket.send(response_data)
# 关闭客户端连接
client_socket.close()

注:以上响应体response_body我们使用的html代码,如果内容过多可直接使用with open打开html文件访问,此处简单演示,故不作文件打开操作

响应指定页面(进阶)

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/16 11:16
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : browser_server_socket.py
# @desc : 徒手搭建web服务器(基于socket)
import socket

# 创建socket套接字,使用ipv4,TCP传输协议
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口复用,程序退出,端口立即释放(默认大概程序退出后两分钟左右才释放端口)
socket_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定socket服务ip和端口号
socket_server.bind(('localhost', 9999))
# 同时可以监听3个连接服务器的客户端
socket_server.listen(3)
print("服务端开始运行了……")
# 循环等待客户端的连接
while True:
# 等待客户端连接
client_socket, ip_port = socket_server.accept()
print(ip_port, '连接上线了')
# 接收到的客户端发来的数据
recv_data = client_socket.recv(4096)
# 获取客户端发来的数据长度
recv_data_length = len(recv_data)
# 判断客户端是否主动关闭了连接
if recv_data_length == 0:
client_socket.close()
print(ip_port, '断开了与服务器的链接')
# 响应行
response_line = 'HTTP/1.1 200 OK\r\n'
# 响应头
response_header = 'Server: PWS1.0\r\n'
# 空行
empty_line = '\r\n'
# 格式化后的客户端发来的数据,并将响应行请求方式,请求路径分离出来(['GET', '/', 'HTTP/1.1\r\nHost: localhost:9999\r\nConne……'])
format_data = recv_data.decode('utf8').split(' ', maxsplit=2)
# 返回不同响应体
if format_data[1] == '/':
# 响应体
response_body = '<!DOCTYPE HTML><html><head><meta charset="UTF-8"><title>首页</title></head><body><h1>我是首页</h1></body></html>'
else:
response_body = '<!DOCTYPE HTML><html><head><meta charset="UTF-8"><title>错误页</title></head><body><h1>该页面出错了</h1></body></html>'
# 格式化为http协议格式的响应数据
response_data = (response_line+response_header+empty_line+response_body).encode('utf8')
# 响应数据
client_socket.send(response_data)
# 关闭客户端连接
client_socket.close()

多任务版静态web服务器(再进阶)

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/16 11:16
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : browser_server_socket.py
# @desc : 徒手搭建web服务器(基于socket)
import socket
import threading


def client_socket_handle(client_socket):
# 接收到的客户端发来的数据
recv_data = client_socket.recv(4096)
# 获取客户端发来的数据长度
recv_data_length = len(recv_data)
# 判断客户端是否主动关闭了连接
if recv_data_length == 0:
client_socket.close()
# 响应行
response_line = 'HTTP/1.1 200 OK\r\n'
# 响应头
response_header = 'Server: PWS1.0\r\n'
# 空行
empty_line = '\r\n'
# 格式化后的客户端发来的数据
format_data = recv_data.decode('utf8').split(' ', maxsplit=2)
print(format_data)
# 返回不同响应体
if format_data[1] == '/':
# 响应体
response_body = '<!DOCTYPE HTML>' \
'<html>' \
'<head>' \
'<meta charset="UTF-8">' \
'<title>首页</title>' \
'</head>' \
'<body><h1>我是首页</h1></body>' \
'</html>'
else:
response_body = '<!DOCTYPE HTML><html>' \
'<head>' \
'<meta charset="UTF-8">' \
'<title>错误页</title>' \
'</head>' \
'<body><h1>该页面出错了</h1></body>' \
'</html>'
# 格式化为http协议格式的响应数据
response_data = (response_line + response_header + empty_line + response_body).encode('utf8')
# 响应数据
client_socket.send(response_data)
# 关闭客户端连接
client_socket.close()


def main():
# 创建socket套接字,使用ipv4,TCP传输协议
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口复用,程序退出,端口立即释放(默认大概程序退出后两分钟左右才释放端口)
socket_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定socket服务ip和端口号
socket_server.bind(('localhost', 9999))
# 同时可以监听3个连接服务器的客户端
socket_server.listen(3)
print("服务端开始运行了……")
# 循环等待客户端的连接
while True:
# 等待客户端连接
client_socket, ip_port = socket_server.accept()
print(ip_port, '连接上线了')
# 当客户端和服务器建立连接程,创建子线程
main_thread = threading.Thread(target=client_socket_handle, args=(client_socket,))
# 设置守护主线程
main_thread.setDaemon(True)
# 启动进程
main_thread.start()

if __name__ == '__main__':
main()

面向对象版静态web服务器(继续进阶)

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/16 11:16
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : browser_server_socket.py
# @desc : 徒手搭建web服务器(基于socket)
import socket
import threading

class HttpServer(object):
"""面向对象静态web服务器"""
def __init__(self):
# socket句柄,默认使用ipv4和T CP协议
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口复用,程序退出,端口立即释放(默认大概程序退出后两分钟左右才释放端口)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 默认绑定本机ip和9999端口
self.server_socket.bind(('', 9999))
# 设置监听
self.server_socket.listen(128)
print("服务端开始运行了……")

@staticmethod
def client_socket_handle(client_socket):
# 接收到的客户端发来的数据
recv_data = client_socket.recv(4096)
# 获取客户端发来的数据长度
recv_data_length = len(recv_data)
# 判断客户端是否主动关闭了连接
if recv_data_length == 0:
client_socket.close()
# 响应行
response_line = 'HTTP/1.1 200 OK\r\n'
# 响应头
response_header = 'Server: PWS1.0\r\n'
# 空行
empty_line = '\r\n'
# 格式化后的客户端发来的数据
format_data = recv_data.decode('utf8').split(' ', maxsplit=2)
print(format_data)
# 返回不同响应体
if format_data[1] == '/':
# 响应体
response_body = '<!DOCTYPE HTML>' \
'<html>' \
'<head>' \
'<meta charset="UTF-8">' \
'<title>首页</title>' \
'</head>' \
'<body><h1>我是首页</h1></body>' \
'</html>'
else:
response_body = '<!DOCTYPE HTML><html>' \
'<head>' \
'<meta charset="UTF-8">' \
'<title>错误页</title>' \
'</head>' \
'<body><h1>该页面出错了</h1></body>' \
'</html>'
# 格式化为http协议格式的响应数据
response_data = (response_line + response_header + empty_line + response_body).encode('utf8')
# 响应数据
client_socket.send(response_data)
# 关闭客户端连接
client_socket.close()

def main(self):
# 循环等待客户端的连接
while True:
# 等待客户端连接
client_socket, ip_port = self.server_socket.accept()
print(ip_port, '连接上线了')
# 当客户端和服务器建立连接程,创建子线程
main_thread = threading.Thread(target=self.client_socket_handle, args=(client_socket,))
# 设置守护主线程
main_thread.setDaemon(True)
# 启动进程
main_thread.start()


if __name__ == '__main__':
http_server = HttpServer()
http_server.main()

命令行启动静态web服务器

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2020/11/16 11:16
# @Author : 托小尼
# @Email : 646547989@qq.com
# @URI : https://www.diandian100.cn
# @File : browser_server_socket.py
# @desc : 徒手搭建web服务器(基于socket)
import socket
import sys
import threading


class HttpServer(object):
"""面向对象静态web服务器"""
def __init__(self, port=9998):
# socket句柄,默认使用ipv4和T CP协议
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口复用,程序退出,端口立即释放(默认大概程序退出后两分钟左右才释放端口)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 默认绑定本机ip和9999端口
self.server_socket.bind(('', port))
# 设置监听
self.server_socket.listen(128)
print("服务端开始运行了……")

@staticmethod
def client_socket_handle(client_socket):
# 接收到的客户端发来的数据
recv_data = client_socket.recv(4096)
# 获取客户端发来的数据长度
recv_data_length = len(recv_data)
# 判断客户端是否主动关闭了连接
if recv_data_length == 0:
client_socket.close()
# 响应行
response_line = 'HTTP/1.1 200 OK\r\n'
# 响应头
response_header = 'Server: PWS1.0\r\n'
# 空行
empty_line = '\r\n'
# 格式化后的客户端发来的数据
format_data = recv_data.decode('utf8').split(' ', maxsplit=2)
print(format_data)
# 返回不同响应体
if format_data[1] == '/':
# 响应体
response_body = '<!DOCTYPE HTML>' \
'<html>' \
'<head>' \
'<meta charset="UTF-8">' \
'<title>首页</title>' \
'</head>' \
'<body><h1>我是首页</h1></body>' \
'</html>'
else:
response_body = '<!DOCTYPE HTML><html>' \
'<head>' \
'<meta charset="UTF-8">' \
'<title>错误页</title>' \
'</head>' \
'<body><h1>该页面出错了</h1></body>' \
'</html>'
# 格式化为http协议格式的响应数据
response_data = (response_line + response_header + empty_line + response_body).encode('utf8')
# 响应数据
client_socket.send(response_data)
# 关闭客户端连接
client_socket.close()

def start(self):
# 循环等待客户端的连接
while True:
# 等待客户端连接
client_socket, ip_port = self.server_socket.accept()
print(ip_port, '连接上线了')
# 当客户端和服务器建立连接程,创建子线程
main_thread = threading.Thread(target=self.client_socket_handle, args=(client_socket,))
# 设置守护主线程
main_thread.setDaemon(True)
# 启动进程
main_thread.start()


def main():
# 获取命令参数
shell_argvs = sys.argv
# 判断命令参数是否书写
if len(shell_argvs) != 2:
print("启动命令如下: python3 xxx.py 9090")
return False
# 判断端口参数是否是数字
if not shell_argvs[1].isdigit():
print("端口必须为数字")
return False
http_server = HttpServer(int(shell_argvs[1]))
http_server.start()


if __name__ == '__main__':
main()

到这里我们其实已经明白了Django怎么使用一行命令就把服务给启动起来了,看到我们这里的简化版本,原来也就这么简单。