使用socket写一个最简单的web服务器

http协议是基于TCP实现的, 我们所熟知的nginx, tomcat, apache等服务器,本质上就是一个tcp server, 只要你对http协议有少许的了解,就可以自己实现一个简单的web服务器,当然,它只能是一个你用来验证http协议和提升自己编程能力的小玩意。

1. TCP server

直接上代码,然后再解释代码

import socket

# 创建socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定ip 和端口
server.bind(('0.0.0.0', 8080))
# 开始监听
server.listen(1)

html = """
<html>
<head>
<title>simple server</title>
</head>
<body>
<p>hello world</p>
</body>
</html>
"""
length = len(html.encode())

# 这里也可以不设置Content-Length, 因为Connection 是close
# 具体缘由可以参考文章《来一波原生的http请求》
head = "HTTP/1.1 200 OK\r\nServer: simple server\r\n" \
       "Content-Type: text/html; charset=utf-8\r\n" \
       "Content-Length: {length}\r\nConnection: close\r\n\r\n"

head = head.format(length=length)

while True:
    # 等待客户端连接
    clientsocket, address = server.accept()
    # 接收客户端的数据
    data = clientsocket.recv(1024).decode()
    msg = head + html
    # 向客户端发送数据
    clientsocket.send(msg.encode())
    # 关闭连接
    clientsocket.close()

server.close()

消息头

head的定义完全遵守http协议,Content-Length 是可以不用设置的,因为Connection被设置成了close,如果是keep-alive,那么就必须设置Content-Length

消息体

消息体就是一段html,关于html可以参考文章爬虫数据分析之html

2. 浏览器访问

启动程序后,就可以在浏览器里输入url http://localhost:8080/ 得到的页面内容很简单,只有一个hello world

此时,只要url前面是http://localhost:8080/ ,后面不管跟什么path,得到的结果都是相同的,这是因为我们的server太简单了,没有根据path返回对应的内容,为了让你更进一步的了解http请求时都发生了什么,接下来,我要修改这个server

import socket

# 创建socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定ip 和端口
server.bind(('0.0.0.0', 8080))
# 开始监听
server.listen(1)


def get_path(data):
    index = data.find("\r\n")
    if index == -1:
        return ""
    first_line = data[:index]
    arrs = first_line.split()
    if len(arrs) != 3:
        return ""
    path = arrs[1]
    return path


def get_html_by_path(path):
    if path == '/':
        return get_index()
    elif path == "/name":
        return get_name()
    else:
        return get_404()

html_string = """
    <html>
    <head>
    <title>simple server</title>
    </head>
    <body>
    <p>{content}</p>
    </body>
    </html>
"""

head_string = "HTTP/1.1 {status}\r\nServer: simple server\r\n" \
           "Content-Type: text/html; charset=utf-8\r\n" \
           "Content-Length: {length}\r\nConnection: close\r\n\r\n"

def get_html(status, content):
    html = html_string.format(content=content)
    length = len(html.encode())
    head = head_string.format(status=status, length=length)
    return head + html

def get_404():
    return get_html('404 NOT FOUND', "你访问的资源不存在")

def get_name():
    return get_html("200 OK", "my name is sheng")

def get_index():
    return get_html("200 OK", "hello world")

while True:
    # 等待客户端连接
    clientsocket, address = server.accept()
    # 接收客户端的数据
    data = clientsocket.recv(1024).decode()
    path = get_path(data)

    msg = get_html_by_path(path)
    # 向客户端发送数据
    clientsocket.send(msg.encode())
    # 关闭连接
    clientsocket.close()

server.close()

升级后的server,可以对http://localhost:8080/namehttp://localhost:8080/ 做出正确的响应,访问其他的资源则会返回404

本篇实现的服务器,是最最简单的服务器,但它和那些普遍使用的服务器,诸如nginx,tomcat在原理上是一致的,不同的是他们可以实现多进程,多线程,使用的epoll模型。

你用flask写一个web程序,最终部署的时候,用的可能是nginx,中间件用uwsgi,结构更加复杂,但本质上就是TCP server

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案