1. HTTP 协议基础:理解我们正在构建什么。
  2. 从零开始:构建最简单的服务器:仅返回 "Hello, World"。
  3. 解析请求:读取客户端发来的 HTTP 请求头和体。
  4. 构建响应:生成符合 HTTP 规范的响应头和体。
  5. 处理不同路径和请求方法:实现路由功能。
  6. 处理静态文件:让服务器能返回网页、图片等文件。
  7. 使用 Web 框架:介绍更专业的工具(如 Flask)。
  8. 总结与进阶

HTTP 协议基础:我们正在构建什么?

在写代码之前,我们必须理解 HTTP(HyperText Transfer Protocol,超文本传输协议)是 Web 通信的基石,它定义了客户端(如浏览器)和服务器之间如何请求和响应数据。

http服务器教程
(图片来源网络,侵删)

一个 HTTP 交互的基本流程如下:

  1. 客户端请求:浏览器向服务器发送一个请求,一个请求包含:

    • 请求行METHOD /path HTTP/1.1
      • METHOD:请求方法,如 GET(获取资源)、POST(提交数据)、PUT(更新资源)、DELETE(删除资源)等。
      • /path:请求的路径,如 /index.html/api/user
      • HTTP/1.1:使用的 HTTP 协议版本。
    • 请求头Key: Value 对,提供关于请求的附加信息,如 Host: example.com, User-Agent: Chrome/90.0
    • 请求体:对于 POSTPUT 等请求,可能会包含发送给服务器的数据(如表单数据、JSON)。
  2. 服务器响应:服务器处理请求后,返回一个响应,一个响应包含:

    • 状态行HTTP/1.1 200 OK
      • HTTP/1.1:HTTP 协议版本。
      • 200:状态码,表示请求成功,常见的还有 404(未找到)、500(服务器内部错误)。
      • OK:状态码的描述性文本。
    • 响应头Key: Value 对,提供关于响应的附加信息,如 Content-Type: text/html类型是 HTML)、Content-Length: 1234长度)。
    • 响应体:实际返回给客户端的数据,如 HTML 文件、JSON 数据、图片等。

我们的目标:编写一个程序,监听一个网络端口,接收客户端的 HTTP 请求,解析它,然后根据请求内容生成一个符合 HTTP 规范的 HTTP 响应,最后发回给客户端。

http服务器教程
(图片来源网络,侵删)

从零开始:构建最简单的服务器

我们将使用 Python 内置的 socket 库来实现。socket 是进行网络通信的基础。

代码:simple_server.py

import socket
# 1. 创建一个 socket 对象
# AF_INET 表示使用 IPv4 协议
# SOCK_STREAM 表示使用 TCP 协议
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定 IP 地址和端口号
# '' 表示监听本机所有可用的网络接口
# 7890 是我们选择的端口号(1024 以上的端口通常需要管理员权限)
server_address = ('', 7890)
server_socket.bind(server_address)
# 3. 开始监听,允许的最大连接数为 5
server_socket.listen(5)
print("服务器已启动,监听端口 7890...")
# 4. 进入主循环,等待客户端连接
while True:
    # accept() 会阻塞程序,直到有客户端连接
    # 它返回一个新的 socket 对象 (client_socket) 和客户端的地址 (client_address)
    client_socket, client_address = server_socket.accept()
    print(f"来自 {client_address} 的连接已建立。")
    # 5. 接收客户端发送的数据
    # recv(1024) 表示最多接收 1024 字节的数据
    request_data = client_socket.recv(1024)
    print("收到的请求数据:")
    print(request_data.decode('utf-8')) # 将字节流解码为字符串
    # 6. 构造 HTTP 响应
    response_body = "Hello, World from my simple server!"
    response = (
        "HTTP/1.1 200 OK\r\n"  # 状态行
        "Content-Type: text/plain\r\n" # 响应头
        "Content-Length: {}\r\n".format(len(response_body)) # 响应头
        "\r\n"  # 空行,分隔响应头和响应体
        response_body  # 响应体
    )
    # 7. 发送响应
    client_socket.sendall(response.encode('utf-8')) # 将字符串编码为字节流
    # 8. 关闭客户端连接
    client_socket.close()
    print(f"与 {client_address} 的连接已关闭,\n")

如何运行和测试

  1. 保存代码:将上面的代码保存为 simple_server.py
  2. 运行服务器:在终端中执行 python simple_server.py,你会看到 "服务器已启动,监听端口 7890..."。
  3. 测试:打开你的网页浏览器(如 Chrome、Firefox),在地址栏输入 http://localhost:7890 然后回车。

会发生什么?

  • 浏览器会向你的服务器发送一个 GET 请求。
  • 你的服务器脚本会接收到这个请求,打印出原始的 HTTP 请求信息,然后构造一个 "Hello, World" 的响应并发送给浏览器。
  • 浏览器接收到响应后,会在页面上显示 "Hello, World from my simple server!"。

解析请求

在上面的例子中,我们只是简单地将 request_data 打印出来,一个真实的请求格式如下:

GET /hello HTTP/1.1
Host: localhost:7890
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

我们需要解析这个字符串,提取出 请求方法请求路径请求头,我们可以手动用字符串分割,但对于更复杂的请求(如带 body 的 POST 请求),这会很麻烦,在实际项目中,我们通常会使用专门的库,但为了学习,我们手动解析一下。

http服务器教程
(图片来源网络,侵删)

代码:parsing_requests.py

# ... (前面的 socket 代码与 simple_server.py 相同) ...
while True:
    client_socket, client_address = server_socket.accept()
    request_data = client_socket.recv(1024).decode('utf-8')
    print("--- 收到新请求 ---")
    print(request_data)
    # 解析请求
    request_lines = request_data.split('\r\n')
    request_line = request_lines[0]
    method, path, version = request_line.split(' ')
    headers = {}
    for line in request_lines[1:]:
        if line.strip() == '': # 空行表示头部结束
            break
        key, value = line.split(': ', 1)
        headers[key] = value
    print(f"解析结果: Method={method}, Path={path}, Version={version}")
    print(f"Headers: {headers}")
    # ... (构造和发送响应的代码) ...

构建响应

响应的格式比请求更简单,我们已经在上面的例子中见过,关键是:

  1. 状态行:必须包含协议版本、状态码和原因短语。
  2. 响应头Key: Value 格式,以 \r\n 分隔。
  3. 空行\r\n\r\n,非常重要,它标志着响应头的结束。
  4. 响应体:实际的数据。

状态码的意义:

  • 200 OK: 成功。
  • 404 Not Found: 请求的资源不存在。
  • 500 Internal Server Error: 服务器内部错误。

代码:building_responses.py

# ... (解析请求的代码) ...
# 根据路径返回不同的响应
if path == '/':
    response_body = "<h1>欢迎访问首页!</h1>"
elif path == '/about':
    response_body = "<h1>关于我们</h1><p>这是一个简单的 HTTP 服务器教程。</p>"
else:
    response_body = "<h1>404 Not Found</h1>"
    status_line = "HTTP/1.1 404 Not Found"
# 构造响应
status_line = "HTTP/1.1 200 OK" # 默认是 200
if response_body.startswith("<h1>404"):
    status_line = "HTTP/1.1 404 Not Found"
response = (
    status_line + "\r\n"
    "Content-Type: text/html\r\n"
    "Content-Length: {}\r\n".format(len(response_body))
    "\r\n"
    response_body
)
client_socket.sendall(response.encode('utf-8'))
client_socket.close()

处理不同路径和请求方法:实现路由

我们的服务器可以根据不同的 URL 路径返回不同的内容了,这就是 路由 的雏形,我们还可以根据不同的 HTTP 方法(GET vs POST)执行不同的操作。

代码:routing_server.py

# ... (socket 初始化代码) ...
def handle_request(method, path, headers):
    """处理请求并返回响应体"""
    print(f"处理请求: {method} {path}")
    if method == 'GET':
        if path == '/':
            return "<h1>首页 - GET 请求</h1>"
        elif path == '/api/info':
            # 返回 JSON 数据
            import json
            data = {"name": "My Server", "version": "1.0"}
            return json.dumps(data)
        else:
            return None # 404
    elif method == 'POST':
        if path == '/api/data':
            # 假设请求体里有数据
            # 注意:这里我们没有实际读取请求体,仅作示例
            return "<h1>数据已接收 - POST 请求</h1>"
        else:
            return None # 404
    return None # 404
# ... (主循环代码) ...
while True:
    client_socket, client_address = server_socket.accept()
    request_data = client_socket.recv(1024).decode('utf-8')
    if not request_data:
        continue
    # 解析请求
    request_lines = request_data.split('\r\n')
    request_line = request_lines[0]
    method, path, version = request_line.split(' ')
    response_body = handle_request(method, path, {})
    # 构造响应
    if response_body is None:
        status_line = "HTTP/1.1 404 Not Found"
        response_body = "<h1>404 Not Found</h1>"
    else:
        status_line = "HTTP/1.1 200 OK"
        # 检查是否是 JSON
        if path == '/api/info':
            headers = "Content-Type: application/json\r\n"
        else:
            headers = "Content-Type: text/html\r\n"
    response = (
        status_line + "\r\n"
        headers
        "Content-Length: {}\r\n".format(len(response_body))
        "\r\n"
        response_body
    )
    client_socket.sendall(response.encode('utf-8'))
    client_socket.close()

处理静态文件

一个 Web 服务器最重要的功能之一就是提供静态文件(如 HTML, CSS, JS, 图片),我们需要:

  1. 将文件路径映射到服务器文件系统路径。
  2. 读取文件内容。
  3. 根据文件扩展名设置正确的 Content-Type 响应头。
  4. 处理文件不存在的情况(404)。

代码:static_file_server.py

import socket
import os
# ... (socket 初始化代码) ...
# 文件扩展名到 Content-Type 的映射
CONTENT_TYPES = {
    '.html': 'text/html',
    '.css': 'text/css',
    '.js': 'application/javascript',
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.gif': 'image/gif',
}
def get_content_type(filepath):
    """根据文件路径获取 Content-Type"""
    _, ext = os.path.splitext(filepath)
    return CONTENT_TYPES.get(ext, 'application/octet-stream') # 默认为二进制流
def serve_static_file(path):
    """尝试提供静态文件"""
    # 安全检查:防止路径遍历攻击 (e.g., /../../../../etc/passwd)
    if '..' in path or path.startswith('/'):
        return None
    # 将请求路径映射到文件系统路径
    # 假设所有静态文件都在 'static' 目录下
    file_path = os.path.join('static', path.lstrip('/'))
    if not os.path.exists(file_path) or not os.path.isfile(file_path):
        return None # 404
    try:
        with open(file_path, 'rb') as f:
            content = f.read()
        return content
    except IOError:
        return None # 500
# ... (主循环代码) ...
while True:
    client_socket, client_address = server_socket.accept()
    request_data = client_socket.recv(1024).decode('utf-8')
    if not request_data:
        client_socket.close()
        continue
    # 解析请求
    request_lines = request_data.split('\r\n')
    request_line = request_lines[0]
    method, path, version = request_line.split(' ')
    # 尝试提供静态文件
    response_body = serve_static_file(path)
    if response_body is not None:
        status_line = "HTTP/1.1 200 OK"
        content_type = get_content_type(path)
        headers = f"Content-Type: {content_type}\r\n"
        # 注意:文件内容是字节,所以长度要用 len() 直接获取
        response = (
            status_line + "\r\n"
            headers
            "Content-Length: {}\r\n".format(len(response_body))
            "\r\n"
        ).encode('utf-8') + response_body
    else:
        # 如果文件不存在,返回 404
        status_line = "HTTP/1.1 404 Not Found"
        response_body = "<h1>404 Not Found</h1>".encode('utf-8')
        response = (
            status_line + "\r\n"
            "Content-Type: text/html\r\n"
            "Content-Length: {}\r\n".format(len(response_body))
            "\r\n"
        ).encode('utf-8') + response_body
    client_socket.sendall(response)
    client_socket.close()

如何测试这个服务器:

  1. 创建一个名为 static 的文件夹。
  2. static 文件夹里放一个 index.html 文件和一些图片。
  3. 运行 python static_file_server.py
  4. 在浏览器中访问 http://localhost:7890/index.html,你应该能看到你的网页。

使用 Web 框架

虽然我们手动构建服务器非常有助于理解底层原理,但在实际开发中,我们几乎不会从零开始写,因为:

  • 效率低:代码量大,容易出错。
  • 功能不完善:缺少路由、模板引擎、数据库连接、安全性等现成的解决方案。
  • 社区支持少:没有丰富的第三方库。

这时,Web 框架就派上用场了,它们提供了构建 Web 应用所需的一切基础设施。

最流行的 Python Web 框架是 FlaskDjango

使用 Flask 重写我们的路由服务器

Flask 的代码极其简洁。

安装 Flask: pip install Flask

代码:app.py

from flask import Flask, jsonify
# 创建一个 Flask 应用实例
app = Flask(__name__)
# @app.route 装饰器用于定义路由
# 它将 URL 路径映射到一个 Python 函数
@app.route('/')
def home():
    """处理首页的 GET 请求"""
    return "<h1>首页 - Flask 服务器</h1>"
@app.route('/api/info')
def api_info():
    """处理 /api/info 的 GET 请求,返回 JSON"""
    data = {"name": "My Flask Server", "version": "1.0"}
    # Flask 的 jsonify 函数会自动将字典转换为 JSON 并设置正确的 Content-Type
    return jsonify(data)
@app.route('/api/data', methods=['POST'])
def api_data():
    """处理 /api/data 的 POST 请求"""
    # 在 Flask 中,你可以通过 request 对象访问请求数据
    from flask import request
    # 获取 JSON 数据: data = request.get_json()
    # 或者获取表单数据: name = request.form.get('name')
    return "<h1>数据已接收 - Flask POST 请求</h1>"
# 运行服务器
# debug=True 开启调试模式,代码修改后会自动重启
if __name__ == '__main__':
    app.run(port=7890, debug=True)

运行 python app.py,然后访问 http://localhost:7890http://localhost:7890/api/info,你会发现功能和我们之前手动实现的完全一样,但代码量却大大减少,而且更清晰、更易于维护。


总结与进阶

本教程带你从最底层的 socket 开始,一步步构建了一个功能从简到繁的 HTTP 服务器,你学到了:

  • HTTP 请求和响应的基本结构。
  • 如何使用 Python 的 socket 库进行网络通信。
  • 如何解析请求和构造响应。
  • 实现了基本的路由和静态文件服务。
  • 理解了为什么在实际项目中要使用 Web 框架(如 Flask)。

进阶方向

如果你对构建自己的 Web 服务器感兴趣,可以继续探索:

  1. 多线程/异步 I/O:我们上面的服务器是同步的,一次只能处理一个客户端请求,当有大量请求时,它会变得非常慢,你可以研究 threading 模块为每个请求创建一个线程,或者使用 asyncioaiohttp 实现更高效的异步服务器。
  2. WSGI:Web 服务器网关接口,这是 Python Web 应用和 Web 服务器之间的标准接口,像 Gunicorn、uWSGI 这样的服务器就是 WSGI 服务器,它们可以运行你的 Flask/Django 应用。
  3. 安全性:学习如何防止常见 Web 攻击,如 SQL 注入跨站脚本跨站请求伪造 等。
  4. 性能优化:学习缓存、负载均衡等技术,让你的服务器能应对高并发场景。

希望这份教程能帮助你打下坚实的基础!