Search code examples
pythonpython-3.xsocketscurl

How I can prevent ‘connection reset by peer’ in curl caused by server closing the socket


I made a basic http server (I am developing my own because I want to do low level http analysis & manipulation in a later stage):

import socket
import threading
import queue
import time

class SocketServer:
    """
        Basic Socket Server in python
    """

    def __init__(self,host,port,max_theads):
        self.host = host
        self.port = port
        self.server_socket = self.__initSocket()
        self.max_theands = max_theads
        self.request_queue = queue.Queue()        

    def __initSocket(self):
        return socket.socket(socket.AF_INET, socket.SOCK_STREAM)


    def __accept(self):
        self.server_socket.listen(5)
        while True:
            client_socket, client_address = self.server_socket.accept()    
            self.request_queue.put((client_socket, client_address))


    def __handle(self):
        while True:
            # Dequeue a request and process it
            client_socket, address = self.request_queue.get()    

            # Read HTTP Request
            # Log Http Request
            # Manipulate Http Request
            # Forward or respond

            client_socket.sendall(b"""HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<html><body>Hello World</body></html>\r\n""");
;
            time.sleep(1)
            client_socket.close()
            self.request_queue.task_done()


    def __initThreads(self):
        for _ in range(self.max_theands):
            threading.Thread(target=self.__handle, daemon=True).start()


    def start(self):
        self.server_socket.bind((self.host, self.port))
        self.__initThreads()
        self.__accept()

And I launch it into a seperate process:

#!/usr/bin/env python3

"""
1. Read settings
2. Bootstrap Manupilator
3. Bootstrap Control Panel

"""
import multiprocessing
from manipulator.http_socket_server import SocketServer

if __name__ == "__main__":

    # @todo read settings file
    host = "0.0.0.0"
    port = 80
    max_threads = 5

    server = SocketServer(host, port, max_threads)
    server_process = multiprocessing.Process(target=server.start)
    server_process.start()

    # Add other main application code here if needed

    server_process.join()

But curl receives:

curl 10.0.0.2
<html><body>Hello World</body></html>
curl: (56) Recv failure: Connection reset by peer

despite the delay in __handle:

            time.sleep(1)

How I can close the socket gracefully?


Solution

  • One fix is to send the content length header:

    content = '<html><body>Hello World</body></html>\r\n'.encode()
    headers = f'HTTP/1.1 200 OK\r\nContent-Length: {len(content)}\r\nContent-Type: text/html\r\n\r\n'.encode()
    client_socket.sendall(headers + content)
    

    After change (and using port 8080):

    C:\>curl localhost:8080
    <html><body>Hello World</body></html>
    

    Full working example:

    import socket
    import threading
    import queue
    
    class SocketServer:
        def __init__(self, host, port, max_theads):
            self.host = host
            self.port = port
            self.server_socket = self.__initSocket()
            self.max_theands = max_theads
            self.request_queue = queue.Queue()
    
        def __initSocket(self):
            return socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
        def __accept(self):
            self.server_socket.listen()
            while True:
                client_socket, client_address = self.server_socket.accept()
                self.request_queue.put((client_socket, client_address))
    
        def __handle(self):
            while True:
                client_socket, address = self.request_queue.get()
                content = '<html><body>Hello World</body></html>\r\n'.encode()
                headers = f'HTTP/1.1 200 OK\r\nContent-Length: {len(content)}\r\nContent-Type: text/html\r\n\r\n'.encode()
                client_socket.sendall(headers + content)
                client_socket.close()
                self.request_queue.task_done()
    
        def __initThreads(self):
            for _ in range(self.max_theands):
                threading.Thread(target=self.__handle, daemon=True).start()
    
        def start(self):
            self.server_socket.bind((self.host, self.port))
            self.__initThreads()
            self.__accept()
    
    if __name__ == '__main__':
    
        host = '0.0.0.0'
        port = 8080
        max_threads = 5
        server = SocketServer(host, port, max_threads)
        server.start()