You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

HTTP服务器处理非GET请求时Socket阻塞问题咨询

简易HTTP服务器非GET请求阻塞问题解决

问题描述

我在完成高中作业,实现一个仅支持GET请求的简易HTTP服务器。处理GET请求时能正常返回200或404响应,但发送DELETE等非GET请求时,代码会在client_socket.send()处阻塞,直到停止程序才会返回405或400响应。

原问题核心代码

def handle_client(client_socket):
    print('Client connected')

    while True:
        client_request = client_socket.recv(1024)
        if not client_request:
            break

        valid_http, resource = validate_http_request(client_request)
        if valid_http:
            print('Got a valid HTTP request')
            handle_client_request(resource, client_socket) #function that working well. sends to the client 200 or 404 response 
            break
        else:
            print('Got an invalid HTTP request')
            if resource == "405":
                http_response = "HTTP/1.1 405 Method Not Allowed\nContent-Type: text/plain\nAllow: GET\n\n405 Method Not Allowed. Try another method!"
                #code stops here for some reason
                client_socket.send(http_response.encode())
            elif resource == "400":
                http_response = "HTTP/1.1 400 Bad Request\nContent-Type: text/plain\n\n400 Bad Request"
                #code stops here for some reason
                client_socket.send(http_response.encode())

    print('Closing connection')
    client_socket.close()

原完整代码

import socket

IP = '127.0.0.1'
PORT = 8080
DEFAULT_URL = "webroot/index.html"


def get_file_data(filename):
    try:
        filepath = f"webroot/{filename}"
        with open(filepath, "rb") as file:
            return file.read()
    except FileNotFoundError:
        return None


def handle_client_request(resource, client_socket):
    url = resource if resource else DEFAULT_URL

    split_url = url.split('.')
    filetype = split_url[-1]
    if filetype == 'html':
        http_header = f"HTTP/1.1 200 OK\nContent-Type: text/html\n\n"
    elif filetype == 'jpg':
        http_header = "HTTP/1.1 200 OK\nContent-Type: image/jpeg\n\n"
    else:
        http_header = "HTTP/1.1 404 Not Found\nContent-Type: text/plain\n\n"
        error_message = "404 Not Found\nThe requested resource could not be found."
        http_response = http_header + error_message
        client_socket.send(http_response.encode())
        return

    data = get_file_data(url)

    if data is not None:
        http_response = http_header.encode() + data
        client_socket.send(http_response)
    else:
        http_header = "HTTP/1.1 404 Not Found\nContent-Type: text/plain\n\n"
        error_message = "404 Not Found\nThe requested resource could not be found.\nOmer Sahar"
        http_response = http_header + error_message
        client_socket.send(http_response.encode())


def validate_http_request(request):
    if len(request_split) >= 3:
        if request_split[2] == "HTTP/1.1":
            if request_split[0] == "GET":
                return True, request_split[1]
            else:
                return False, "405"
    return False, "400"


def handle_client(client_socket):
    print('Client connected')

    while True:
        client_request = client_socket.recv(1024)
        if not client_request:
            break

        valid_http, resource = validate_http_request(client_request)
        if valid_http:
            print('Got a valid HTTP request')
            handle_client_request(resource, client_socket)
            break
        else:
            print('Got an invalid HTTP request')
            if resource == "405":
                http_response = "HTTP/1.1 405 Method Not Allowed\nContent-Type: text/plain\nAllow: GET\n\n405 Method Not Allowed. Try another method!"
                client_socket.send(http_response.encode())
            elif resource == "400":
                http_response = "HTTP/1.1 400 Bad Request\nContent-Type: text/plain\n\n400 Bad Request"
                client_socket.send(http_response.encode())

    print('Closing connection')
    client_socket.close()


server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((IP, PORT))
server_socket.listen()
print("Listening for connections on port {}".format(PORT))

while True:
    client_socket, client_address = server_socket.accept()
    print('New connection received')
    handle_client(client_socket)

问题根源

  1. 请求读取不完整:原代码仅调用一次recv(1024),无法保证获取完整的HTTP请求头,尤其是客户端分块发送请求时,服务器会陷入等待剩余数据的循环。
  2. 缺少Content-Length头:HTTP响应未指定内容长度,客户端无法判断响应是否结束,会持续等待更多数据,导致服务器发送响应后无法收到客户端的确认,进而阻塞。
  3. 响应后未及时关闭连接:非GET请求处理完响应后,未跳出循环或关闭客户端socket,服务器会继续等待客户端发送数据,造成阻塞。
  4. 请求验证函数bug:validate_http_request函数未对二进制请求进行解码,也未定义request_split变量,导致请求验证逻辑完全失效。

修正后的完整代码

import socket

IP = '127.0.0.1'
PORT = 8080
DEFAULT_URL = "webroot/index.html"


# Utility function to get file data
def get_file_data(filename):
    if filename == DEFAULT_URL:
        filepath = DEFAULT_URL
    else:
        filepath = f"webroot/{filename}"
    try:
        with open(filepath, "rb") as file:
            return file.read()
    except FileNotFoundError:
        return None


# Handle a client request
def handle_client_request(resource, client_socket):
    if resource == "/":
        url = DEFAULT_URL
    else:
        url = resource

    
    split_url = url.split('.')
    filetype = split_url[-1]
    if filetype == 'html':
        http_header = f"HTTP/1.1 200 OK\nContent-Type: text/html\n"
    elif filetype == 'jpg':
        http_header = "HTTP/1.1 200 OK\nContent-Type: image/jpeg\n"
    else:
        http_header = "HTTP/1.1 404 Not Found\nContent-Type: text/plain\n"
        error_message = "404 Not Found\nThe requested resource could not be found."
        http_response = http_header + f"Content-Length: {len(error_message)}\n\n" + error_message
        client_socket.sendall(http_response.encode())
        client_socket.close()
        return

    data = get_file_data(url)

    if data is not None:
        http_header += f"Content-Length: {len(data)}\n\n"
        http_response = http_header.encode() + data
        client_socket.sendall(http_response)
        client_socket.close()
    else:
        error_message = "404 Not Found\nThe requested resource could not be found.\nOmer Sahar"
        http_header += f"Content-Length: {len(error_message)}\n\n"
        http_response = http_header.encode() + error_message.encode()
        client_socket.sendall(http_response)
        client_socket.close()


# Validate an HTTP request
def validate_http_request(request):
    request = request.decode()
    request_split = request.split()
    if len(request_split) >= 3:
        if request_split[2] == "HTTP/1.1":
            if request_split[0] == "GET":
                return True, request_split[1]
            elif request_split[0] != "GET":
                return False, "405"
    return False, "400"


# Handle a client's request
def handle_client(client_socket):
    print('Client connected')

    while True:
        client_request = b''
        while True:
            chunk = client_socket.recv(1024)
            if not chunk:
                break
            client_request += chunk
            if b'\r\n\r\n' in client_request:
                break

        if not client_request:
            break

        valid_http, resource = validate_http_request(client_request)
        if valid_http:
            print('Got a valid HTTP request')
            handle_client_request(resource, client_socket)
            break
        else:
            print('Got an invalid HTTP request')
            if resource == "405":
                http_header = "HTTP/1.1 405 Method Not Allowed\nContent-Type: text/plain\nAllow: GET"
                error_message = "405 Method Not Allowed. Try another method!"
                http_response = http_header + f"Content-Length: {len(error_message)}\n\n" + error_message
                client_socket.sendall(http_response.encode())
                client_socket.close()
                break
            elif resource == "400":
                http_header = "HTTP/1.1 400 Bad Request\nContent-Type: text/plain"
                error_message = "400: Bad Request"
                http_response = http_header + f"Content-Length: {len(error_message)}\n\n" + error_message
                client_socket.sendall(http_response.encode())
                client_socket.close()
                break


# Set up the server socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((IP, PORT))
server_socket.listen()
print("Listening for connections on port {}".format(PORT))

# Accept and handle client connections
while True:
    client_socket, client_address = server_socket.accept()
    print('New connection received')
    handle_client(client_socket)

关键修复项

  • 完整读取请求:修改请求读取逻辑,循环接收数据直到检测到\r\n\r\n(HTTP请求头结束标记),确保获取完整的请求内容。
  • 添加Content-Length响应头:所有响应都明确指定内容长度,让客户端可以准确判断响应数据的结束位置,避免无限等待。
  • 使用sendall替代send:sendall会确保所有数据都被发送到客户端,避免数据残留导致的异常。
  • 响应后立即关闭连接:处理完每个请求后,主动关闭客户端socket并跳出循环,避免服务器继续等待客户端数据造成阻塞。
  • 修复请求验证函数:对二进制请求进行解码,正确拆分请求行,准确识别请求方法和HTTP版本,确保非GET请求能被正确判定为405错误。

内容的提问来源于stack exchange,提问作者omer sahar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 10:56:00