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)
问题根源
- 请求读取不完整:原代码仅调用一次
recv(1024),无法保证获取完整的HTTP请求头,尤其是客户端分块发送请求时,服务器会陷入等待剩余数据的循环。 - 缺少Content-Length头:HTTP响应未指定内容长度,客户端无法判断响应是否结束,会持续等待更多数据,导致服务器发送响应后无法收到客户端的确认,进而阻塞。
- 响应后未及时关闭连接:非GET请求处理完响应后,未跳出循环或关闭客户端socket,服务器会继续等待客户端发送数据,造成阻塞。
- 请求验证函数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
相关产品推荐
相关产品推荐

