# `socketserver` — A framework for network servers

Вихідний код: Lib/socketserver.py

Модуль `socketserver` спрощує завдання написання мережевих серверів.

Availability: not WASI.

This module does not work or is not available on WebAssembly. See WebAssembly platforms for more information.

Існує чотири основних класи конкретних серверів:

class socketserver.TCPServer(server_address, RequestHandlerClass, bind_and_activate=True)

  Для цього використовується інтернет-протокол TCP, який забезпечує безперервні потоки даних між клієнтом і сервером. Якщо bind_and_activate має значення true, конструктор автоматично намагається викликати `server_bind()` і `server_activate()`. Інші параметри передаються до базового класу `BaseServer`.

  Змінено в версії 3.15.0a0 (unreleased): The default queue size is now `socket.SOMAXCONN` for `socketserver.TCPServer`.

class socketserver.UDPServer(server_address, RequestHandlerClass, bind_and_activate=True)

  Для цього використовуються дейтаграми, які є окремими пакетами інформації, які можуть надійти не в порядку або бути втраченими під час передачі. Параметри такі самі, як і для `TCPServer`.

class socketserver.UnixStreamServer(server_address, RequestHandlerClass, bind_and_activate=True)
class socketserver.UnixDatagramServer(server_address, RequestHandlerClass, bind_and_activate=True)

  Ці менш часто використовувані класи подібні до класів TCP і UDP, але використовують доменні сокети Unix; вони недоступні на платформах, відмінних від Unix. Параметри такі самі, як і для `TCPServer`.

Ці чотири класи обробляють запити synchronously; кожен запит має бути виконано перед початком наступного запиту. Це не підходить, якщо виконання кожного запиту займає багато часу, тому що він вимагає багато обчислень або тому, що він повертає багато даних, які клієнт повільно обробляє. Рішення полягає у створенні окремого процесу або потоку для обробки кожного запиту; змішані класи `ForkingMixIn` і `ThreadingMixIn` можна використовувати для підтримки асинхронної поведінки.

Створення сервера вимагає кількох кроків. По-перше, ви повинні створити клас обробника запитів, створивши підклас класу `BaseRequestHandler` і перевизначивши його метод `handle()`; цей метод оброблятиме вхідні запити. По-друге, ви повинні створити екземпляр одного з класів сервера, передавши йому адресу сервера та клас обробника запитів. Рекомендовано використовувати сервер у операторі `with`. Потім викличте метод `handle_request()` або `serve_forever()` об’єкта сервера для обробки одного або кількох запитів. Нарешті, викличте `server_close()`, щоб закрити сокет (якщо ви не використали оператор `with`).

Успадковуючи від `ThreadingMixIn` поведінку потокового з’єднання, ви повинні явно оголосити, як ви хочете, щоб ваші потоки поводилися під час раптового завершення роботи. Клас `ThreadingMixIn` визначає атрибут daemon_threads, який вказує, чи повинен сервер чекати завершення потоку. Ви повинні встановити прапорець явно, якщо ви хочете, щоб потоки поводилися автономно; за замовчуванням `False`, що означає, що Python не завершить роботу, доки не завершаться всі потоки, створені `ThreadingMixIn`.

Класи серверів мають однакові зовнішні методи та атрибути, незалежно від того, який мережевий протокол вони використовують.

## Примітки щодо створення сервера

У діаграмі успадкування є п’ять класів, чотири з яких представляють синхронні сервери чотирьох типів:

    +------------+
    | BaseServer |
    +------------+
          |
          v
    +-----------+        +------------------+
    | TCPServer |------->| UnixStreamServer |
    +-----------+        +------------------+
          |
          v
    +-----------+        +--------------------+
    | UDPServer |------->| UnixDatagramServer |
    +-----------+        +--------------------+

Note that `UnixDatagramServer` derives from `UDPServer`, not from `UnixStreamServer` — the only difference between an IP and a Unix server is the address family.

class socketserver.ForkingMixIn
class socketserver.ThreadingMixIn

  Розгалуження та версії потоків кожного типу сервера можна створити за допомогою цих змішаних класів. Наприклад, `ThreadingUDPServer` створюється таким чином:

      class ThreadingUDPServer(ThreadingMixIn, UDPServer):
          pass

  Клас mix-in стоїть на першому місці, оскільки він замінює метод, визначений у `UDPServer`. Налаштування різних атрибутів також змінює поведінку базового серверного механізму.

  `ForkingMixIn` і згадані нижче класи Forking доступні лише на платформах POSIX, які підтримують `fork()`.

  block_on_close

    `ForkingMixIn.server_close` waits until all child processes complete, except if `block_on_close` attribute is `False`.

    `ThreadingMixIn.server_close` waits until all non-daemon threads complete, except if `block_on_close` attribute is `False`.

  max_children

    Specify how many child processes will exist to handle requests at a time for `ForkingMixIn`. If the limit is reached, new requests will wait until one child process has finished.

  daemon_threads

    For `ThreadingMixIn` use daemonic threads by setting `ThreadingMixIn.daemon_threads` to `True` to not wait until threads complete.

  Змінено в версії 3.7: `ForkingMixIn.server_close` and `ThreadingMixIn.server_close` now waits until all child processes and non-daemonic threads complete. Add a new `ForkingMixIn.block_on_close` class attribute to opt-in for the pre-3.7 behaviour.

class socketserver.ForkingTCPServer
class socketserver.ForkingUDPServer
class socketserver.ThreadingTCPServer
class socketserver.ThreadingUDPServer
class socketserver.ForkingUnixStreamServer
class socketserver.ForkingUnixDatagramServer
class socketserver.ThreadingUnixStreamServer
class socketserver.ThreadingUnixDatagramServer

  Ці класи попередньо визначені за допомогою змішаних класів.

Added in version 3.12: The `ForkingUnixStreamServer` and `ForkingUnixDatagramServer` classes were added.

Щоб реалізувати службу, ви повинні отримати клас від `BaseRequestHandler` і перевизначити його метод `handle()`. Потім ви можете запустити різні версії служби, поєднавши один із класів сервера з класом обробника запитів. Клас обробника запитів має відрізнятися для датаграм або потокових служб. Це можна приховати за допомогою підкласів обробників `StreamRequestHandler` або `DatagramRequestHandler`.

Звичайно, вам все одно доведеться використовувати свою голову! Наприклад, немає сенсу використовувати сервер розгалуження, якщо служба містить стан у пам’яті, який можна змінювати різними запитами, оскільки зміни в дочірньому процесі ніколи не досягнуть початкового стану, який зберігається в батьківському процесі та передається кожному дочірньому. . У цьому випадку ви можете використовувати потоковий сервер, але вам, ймовірно, доведеться використовувати блокування для захисту цілісності спільних даних.

З іншого боку, якщо ви створюєте HTTP-сервер, де всі дані зберігаються зовні (наприклад, у файловій системі), синхронний клас, по суті, надаватиме послугу «глухою», поки обробляється один запит, що може бути дуже довго, якщо клієнт повільно отримує всі запитувані дані. Тут підійде сервер потоків або розгалуження.

У деяких випадках може бути доречним обробити частину запиту синхронно, але завершити обробку в розгалуженому дочірньому файлі залежно від даних запиту. Це можна реалізувати за допомогою синхронного сервера та виконання явного розгалуження в методі класу обробника запитів `handle()`.

Another approach to handling multiple simultaneous requests in an environment that supports neither threads nor `fork()` (or where these are too expensive or inappropriate for the service) is to maintain an explicit table of partially finished requests and to use `selectors` to decide which request to work on next (or whether to handle a new incoming request). This is particularly important for stream services where each client can potentially be connected for a long time (if threads or subprocesses cannot be used).

## Серверні об’єкти

class socketserver.BaseServer(server_address, RequestHandlerClass)

  Це суперклас усіх об’єктів сервера в модулі. Він визначає інтерфейс, наведений нижче, але не реалізує більшість методів, що робиться в підкласах. Два параметри зберігаються у відповідних атрибутах `server_address` і `RequestHandlerClass`.

  fileno()

    Повертає цілочисельний файловий дескриптор для сокета, який прослуховує сервер. Ця функція найчастіше передається `selectors`, щоб дозволити моніторинг кількох серверів в одному процесі.

  handle_request()

    Обробити один запит. Ця функція викликає наступні методи в порядку: `get_request()`, `verify_request()` і `process_request()`. Якщо наданий користувачем метод `handle()` класу обробника викликає виняток, буде викликано метод `handle_error()` сервера. Якщо протягом `timeout` секунд не буде отримано жодного запиту, буде викликано `handle_timeout()` і `handle_request()` повернеться.

  serve_forever(poll_interval=0.5)

    Обробляти запити до явного запиту `shutdown()`. Опитування для вимкнення кожні poll_interval секунди. Ігнорує атрибут `timeout`. Він також викликає `service_actions()`, який може використовуватися підкласом або міксином для виконання дій, специфічних для певної служби. Наприклад, клас `ForkingMixIn` використовує `service_actions()` для очищення дочірніх процесів-зомбі.

    Змінено в версії 3.3: Додано виклик `service_actions` до методу `serve_forever`.

  service_actions()

    Це викликається в циклі `serve_forever()`. Цей метод може бути перевизначений підкласами або класами mixin для виконання дій, специфічних для певної служби, наприклад дій очищення.

    Added in version 3.3.

  shutdown()

    Скажіть циклу `serve_forever()` зупинитися та зачекайте, доки він зупиниться. `shutdown()` потрібно викликати, поки `serve_forever()` працює в іншому потоці, інакше він блокується.

  server_close()

    Очистити сервер. Може бути перевизначено.

  address_family

    The family of protocols to which the server’s socket belongs. Common examples are `socket.AF_INET`, `socket.AF_INET6`, and `socket.AF_UNIX`. Subclass the TCP or UDP server classes in this module with class attribute `address_family = AF_INET6` set if you want IPv6 server classes.

  RequestHandlerClass

    Клас обробника запитів, наданий користувачем; екземпляр цього класу створюється для кожного запиту.

  server_address

    Адреса, на якій прослуховується сервер. Формат адреси змінюється в залежності від сімейства протоколів; подробиці дивіться в документації до модуля `socket`. Для інтернет-протоколів це кортеж, що містить рядок, що вказує адресу, і цілий номер порту: `('127.0.0.1', 80)`, наприклад.

  socket

    Об’єкт сокета, на якому сервер прослуховуватиме вхідні запити.

  Класи сервера підтримують наступні змінні класу:

  allow_reuse_address

    Чи дозволить сервер повторне використання адреси. За умовчанням це `False`, і його можна встановити в підкласах, щоб змінити політику.

  request_queue_size

    Розмір черги запитів. Якщо обробка одного запиту займає багато часу, будь-які запити, які надходять, коли сервер зайнятий, розміщуються в черзі, до запитів `request_queue_size`. Після заповнення черги подальші запити від клієнтів отримуватимуть помилку «З’єднання відмовлено». Значення за замовчуванням зазвичай дорівнює 5, але це може бути замінено підкласами.

  socket_type

    Тип сокета, який використовує сервер; `socket.SOCK_STREAM` і `socket.SOCK_DGRAM` є двома загальними значеннями.

  timeout

    Тривалість тайм-ауту, вимірюється в секундах, або `None`, якщо тайм-аут не потрібний. Якщо `handle_request()` не отримує вхідних запитів протягом періоду очікування, викликається метод `handle_timeout()`.

  Існують різні методи сервера, які можуть бути замінені підкласами базових класів серверів, наприклад `TCPServer`; ці методи не корисні зовнішнім користувачам серверного об’єкта.

  finish_request(request, client_address)

    Фактично обробляє запит, створюючи екземпляр `RequestHandlerClass` і викликаючи його метод `handle()`.

  get_request()

    Потрібно прийняти запит від сокета та повернути 2-кортеж, що містить новий об’єкт сокета, який буде використовуватися для зв’язку з клієнтом, і адресу клієнта.

  handle_error(request, client_address)

    Ця функція викликається, якщо метод `handle()` примірника `RequestHandlerClass` викликає виняткову ситуацію. Дія за замовчуванням полягає в тому, щоб надрукувати відстеження стандартної помилки та продовжити обробку подальших запитів.

    Змінено в версії 3.6: Тепер викликаються лише винятки, похідні від класу `Exception`.

  handle_timeout()

    Ця функція викликається, коли для атрибута `timeout` встановлено значення, відмінне від `None`, і період очікування минув, а запити не надходили. Дія за замовчуванням для розгалужених серверів полягає в зборі статусу будь-яких дочірніх процесів, які вийшли, тоді як у потокових серверах цей метод не робить нічого.

  process_request(request, client_address)

    Викликає `finish_request()` для створення екземпляра `RequestHandlerClass`. За бажання ця функція може створити новий процес або потік для обробки запиту; це роблять класи `ForkingMixIn` і `ThreadingMixIn`.

  server_activate()

    Викликається конструктором сервера для активації сервера. Поведінка за замовчуванням для TCP-сервера просто викликає `listen()` у сокеті сервера. Може бути перевизначено.

  server_bind()

    Викликається конструктором сервера, щоб прив’язати сокет до потрібної адреси. Може бути перевизначено.

  verify_request(request, client_address)

    Має повертати логічне значення; якщо значення `True`, запит буде оброблено, а якщо значення `False`, запит буде відхилено. Цю функцію можна замінити, щоб реалізувати контроль доступу до сервера. Стандартна реалізація завжди повертає `True`.

  Змінено в версії 3.6: Додано підтримку протоколу context manager. Вихід із контекстного менеджера еквівалентний виклику `server_close()`.

## Об’єкти обробки запитів

class socketserver.BaseRequestHandler

  Це суперклас усіх об’єктів обробки запитів. Він визначає інтерфейс, наведений нижче. Конкретний підклас обробника запитів повинен визначати новий метод `handle()` і може перевизначати будь-які інші методи. Для кожного запиту створюється новий екземпляр підкласу.

  setup()

    Викликається перед методом `handle()` для виконання будь-яких необхідних дій ініціалізації. Стандартна реалізація нічого не робить.

  handle()

    This function must do all the work required to service a request. The default implementation does nothing. Several instance attributes are available to it; the request is available as `request`; the client address as `client_address`; and the server instance as `server`, in case it needs access to per-server information.

    The type of `request` is different for datagram or stream services. For stream services, `request` is a socket object; for datagram services, `request` is a pair of string and socket.

  finish()

    Викликається після методу `handle()` для виконання будь-яких необхідних дій очищення. Стандартна реалізація нічого не робить. Якщо `setup()` викликає виняток, ця функція не буде викликана.

  request

    The new `socket.socket` object to be used to communicate with the client.

  client_address

    Client address returned by `BaseServer.get_request()`.

  server

    `BaseServer` object used for handling the request.

class socketserver.StreamRequestHandler
class socketserver.DatagramRequestHandler

  These `BaseRequestHandler` subclasses override the `setup()` and `finish()` methods, and provide `rfile` and `wfile` attributes.

  rfile

    A file object from which receives the request is read. Support the `io.BufferedIOBase` readable interface.

  wfile

    A file object to which the reply is written. Support the `io.BufferedIOBase` writable interface

  Змінено в версії 3.6: `wfile` also supports the `io.BufferedIOBase` writable interface.

## Приклади

### `socketserver.TCPServer` Приклад

Це сторона сервера:

    import socketserver

    class MyTCPHandler(socketserver.BaseRequestHandler):
        """
        The request handler class for our server.

        It is instantiated once per connection to the server, and must
        override the handle() method to implement communication to the
        client.
        """

        def handle(self):
            # self.request is the TCP socket connected to the client
            pieces = [b'']
            total = 0
            while b'\n' not in pieces[-1] and total < 10_000:
                pieces.append(self.request.recv(2000))
                total += len(pieces[-1])
            self.data = b''.join(pieces)
            print(f"Received from {self.client_address[0]}:")
            print(self.data.decode("utf-8"))
            # just send back the same data, but upper-cased
            self.request.sendall(self.data.upper())
            # after we return, the socket will be closed.

    if __name__ == "__main__":
        HOST, PORT = "localhost", 9999

        # Create the server, binding to localhost on port 9999
        with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
            # Activate the server; this will keep running until you
            # interrupt the program with Ctrl-C
            server.serve_forever()

Альтернативний клас обробника запитів, який використовує потоки (файлоподібні об’єкти, які спрощують зв’язок, надаючи стандартний файловий інтерфейс):

    class MyTCPHandler(socketserver.StreamRequestHandler):

        def handle(self):
            # self.rfile is a file-like object created by the handler.
            # We can now use e.g. readline() instead of raw recv() calls.
            # We limit ourselves to 10000 bytes to avoid abuse by the sender.
            self.data = self.rfile.readline(10000).rstrip()
            print(f"{self.client_address[0]} wrote:")
            print(self.data.decode("utf-8"))
            # Likewise, self.wfile is a file-like object used to write back
            # to the client
            self.wfile.write(self.data.upper())

The difference is that the `readline()` call in the second handler will call `recv()` multiple times until it encounters a newline character, while the first handler had to use a `recv()` loop to accumulate data until a newline itself. If it had just used a single `recv()` without the loop it would just have returned what has been received so far from the client. TCP is stream based: data arrives in the order it was sent, but there no correlation between client `send()` or `sendall()` calls and the number of `recv()` calls on the server required to receive it.

Це сторона клієнта:

    import socket
    import sys

    HOST, PORT = "localhost", 9999
    data = " ".join(sys.argv[1:])

    # Create a socket (SOCK_STREAM means a TCP socket)
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        # Connect to server and send data
        sock.connect((HOST, PORT))
        sock.sendall(bytes(data, "utf-8"))
        sock.sendall(b"\n")

        # Receive data from the server and shut down
        received = str(sock.recv(1024), "utf-8")

    print("Sent:    ", data)
    print("Received:", received)

Результат прикладу має виглядати приблизно так:

сервер:

    $ python TCPServer.py
    127.0.0.1 wrote:
    b'hello world with TCP'
    127.0.0.1 wrote:
    b'python is nice'

Клієнт:

    $ python TCPClient.py hello world with TCP
    Sent:     hello world with TCP
    Received: HELLO WORLD WITH TCP
    $ python TCPClient.py python is nice
    Sent:     python is nice
    Received: PYTHON IS NICE

### `socketserver.UDPServer` Приклад

Це сторона сервера:

    import socketserver

    class MyUDPHandler(socketserver.BaseRequestHandler):
        """
        This class works similar to the TCP handler class, except that
        self.request consists of a pair of data and client socket, and since
        there is no connection the client address must be given explicitly
        when sending data back via sendto().
        """

        def handle(self):
            data = self.request[0].strip()
            socket = self.request[1]
            print(f"{self.client_address[0]} wrote:")
            print(data)
            socket.sendto(data.upper(), self.client_address)

    if __name__ == "__main__":
        HOST, PORT = "localhost", 9999
        with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
            server.serve_forever()

Це сторона клієнта:

    import socket
    import sys

    HOST, PORT = "localhost", 9999
    data = " ".join(sys.argv[1:])

    # SOCK_DGRAM is the socket type to use for UDP sockets
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # As you can see, there is no connect() call; UDP has no connections.
    # Instead, data is directly sent to the recipient via sendto().
    sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
    received = str(sock.recv(1024), "utf-8")

    print("Sent:    ", data)
    print("Received:", received)

Вихідні дані прикладу мають виглядати точно так само, як для прикладу сервера TCP.

### Асинхронні міксини

Для створення асинхронних обробників використовуйте класи `ThreadingMixIn` і `ForkingMixIn`.

Приклад класу `ThreadingMixIn`:

    import socket
    import threading
    import socketserver

    class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):

        def handle(self):
            data = str(self.request.recv(1024), 'ascii')
            cur_thread = threading.current_thread()
            response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
            self.request.sendall(response)

    class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
        pass

    def client(ip, port, message):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            sock.connect((ip, port))
            sock.sendall(bytes(message, 'ascii'))
            response = str(sock.recv(1024), 'ascii')
            print("Received: {}".format(response))

    if __name__ == "__main__":
        # Port 0 means to select an arbitrary unused port
        HOST, PORT = "localhost", 0

        server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
        with server:
            ip, port = server.server_address

            # Start a thread with the server -- that thread will then start one
            # more thread for each request
            server_thread = threading.Thread(target=server.serve_forever)
            # Exit the server thread when the main thread terminates
            server_thread.daemon = True
            server_thread.start()
            print("Server loop running in thread:", server_thread.name)

            client(ip, port, "Hello World 1")
            client(ip, port, "Hello World 2")
            client(ip, port, "Hello World 3")

            server.shutdown()

Результат прикладу має виглядати приблизно так:

    $ python ThreadedTCPServer.py
    Server loop running in thread: Thread-1
    Received: Thread-2: Hello World 1
    Received: Thread-3: Hello World 2
    Received: Thread-4: Hello World 3

Клас `ForkingMixIn` використовується так само, за винятком того, що сервер створюватиме новий процес для кожного запиту. Доступно лише на платформах POSIX, які підтримують `fork()`.