"socketserver" --- A framework for network servers
**************************************************

**Code source :** Lib/socketserver.py

======================================================================

Le module "socketserver" permet de simplifier le développement de
serveurs réseaux.

Availability: not WASI.

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

Il existe quatre classes concrètes fondamentales :

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

   Cette classe permet de créer des flux continus de données entre un
   client et un serveur en utilisant le protocole internet TCP. Si
   *bind_and_activate* est vrai, le constructeur tente automatiquement
   d'invoquer  "server_bind()" et "server_activate()". Les autres
   paramètres sont passés à la classe de base "BaseServer".

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

   Cette classe utilise des datagrammes, qui sont des paquets
   d'information pouvant arriver dans le désordre, voire être perdus,
   durant le transport. Les paramètres sont identiques à "TCPServer".

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

   Ces classes, moins fréquemment utilisées, sont similaires aux
   classes pour TCP et UDP mais utilisent des connecteurs UNIX ; ces
   connecteurs ne sont disponibles que sur les plateformes UNIX. Les
   paramètres sont identiques à "TCPServer".

Ces quatre classes traitent les requêtes de façon *synchrone* : chaque
requête doit être terminée avant de pouvoir traiter la suivante. Cette
méthode n'est pas adaptée si les requêtes prennent beaucoup de temps à
être traitées. Cela peut arriver si les requêtes demandent beaucoup de
calcul, ou si elles renvoient beaucoup de données que le client peine
à traiter. Dans ce cas, la solution est de créer un processus ou un
fil d’exécution séparé pour chaque requête ; les classes de mélange
"ForkingMixIn" et "ThreadingMixIn" peuvent être utilisées pour
réaliser ce comportement asynchrone.

La création d'un serveur requiert plusieurs étapes. Premièrement, vous
devez créer une classe pour gérer les requêtes en héritant de
"BaseRequestHandler" et surcharger sa méthode "handle()", laquelle
traitera les requêtes entrantes. Deuxièmement, vous devez instancier
l'une des classes serveurs et lui passer l’adresse du serveur ainsi
que la classe gérant les requêtes. Il est recommandé de faire ceci
dans une instruction "with". Ensuite, appelez la méthode
"handle_request()" ou "serve_forever()" de l'objet serveur afin de
traiter une ou plusieurs requêtes. Enfin, appelez la méthode
"server_close()" pour fermer le connecteur (à moins que vous n'ayez
utilisé une instruction "with").

Lorsque vous héritez de "ThreadingMixIn" pour déléguer les connexions
à différent fils d’exécution, vous devez déclarer explicitement
comment les fils d’exécution doivent se comporter en cas d'arrêt
abrupt. La classe "ThreadingMixIn" définit un attribut
*daemon_threads*, indiquant si le serveur doit attendre la fin des
fils d’exécution ou non. Vous pouvez utiliser cet attribut si vous
souhaitez que les fils d’exécution soient autonomes. La valeur par
défaut est "False", indiquant que Python ne doit pas quitter avant que
tous les fils d'exécution créés par "ThreadingMixIn" ne soient
terminés.

Toutes les classes de serveurs exposent les mêmes méthodes et
attributs, peu importe le protocole réseau utilisé.


Notes sur la création de serveurs
=================================

Il y a cinq classes dans la hiérarchie. Quatre d'entre elles
représentent des serveurs synchrones de quatre types différents :

   +------------+
   | 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

   Des versions utilisant des fils d’exécution ou des processus
   peuvent être créées pour chaque type de serveur, en utilisant ces
   classes de mélange. Par exemple, "ThreadingUDPServer" est créé
   comme suit :

      class ThreadingUDPServer(ThreadingMixIn, UDPServer):
          pass

   La classe de mélange est en premier car elle surcharge une méthode
   définie dans "UDPServer". Configurer les différents attributs
   changera également le comportement du serveur.

   La classe "ForkingMixIn" et les classes créant des processus
   mentionnées ci-dessous sont uniquement disponibles sur les
   plateformes POSIX prenant en charge "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".

   daemon_threads

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

   Modifié dans la version 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

   Ces classes sont prédéfinies en utilisant les classes de mélange.

Ajouté dans la version 3.12: The "ForkingUnixStreamServer" and
"ForkingUnixDatagramServer" classes were added.

Pour implémenter un service, vous devez créer une classe héritant de
"BaseRequestHandler" et redéfinir sa méthode "handle()". Ensuite, vous
pourrez créer différentes versions de votre service en combinant les
classes serveurs avec votre classe de gestion des requêtes. Cette
classe de gestion des requêtes doit être différente pour les services
utilisant des datagrammes ou des flux de données. Cette contrainte
peut être dissimulée en utilisant les classes de gestion dérivées
"StreamRequestHandler" ou "DatagramRequestHandler".

Bien entendu, vous devrez toujours utiliser votre tête ! Par exemple,
utiliser un serveur utilisant des processus clonés (*forking*)
n'aurait aucun sens si le serveur garde en mémoire des états pouvant
être modifiés par les requêtes reçues. En effet, un processus enfant
traitant une requête n'aurait alors aucun moyen de propager le nouvel
état à son parent. Dans ce cas, vous devez utiliser un serveur
utilisant des fils d'exécution, mais cela demande probablement
d'utiliser des verrous pour protéger l’intégrité des données
partagées.

D'un autre côté, si vous développez un serveur HTTP qui a toutes ses
données stockées hors de la mémoire (sur un système de fichiers par
exemple), une classe synchrone rendrait le service sourd à toute
nouvelle requête aussi longtemps qu'une précédente soit en cours de
traitement. Cette situation pourrait perdurer pendant un long moment
si le client prend du temps à recevoir toutes les données demandées.
Dans ce cas, un serveur utilisant des processus ou des fils
d'exécutions est approprié.

Dans certains cas, il peut être judicieux de commencer à traiter une
requête de façon synchrone mais de pouvoir déléguer le reste du
traitement à un processus enfant si besoin. Ce comportement peut être
implémenté en utilisant un serveur synchrone et en laissant à la
méthode "handle()", de la classe gérant les requêtes, le soin de créer
le processus enfant explicitement.

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).


Objets serveur
==============

class socketserver.BaseServer(server_address, RequestHandlerClass)

   Il s'agit de la classe parente de tous les objets serveur du
   module. Elle déclare l'interface, définie ci-dessous, mais laisse
   aux classes filles le soin d'implémenter la plupart des méthodes.
   Les deux paramètres sont stockés respectivement dans les attributs
   "server_address" et "RequestHandlerClass".

   fileno()

      Renvoie un entier représentant le descripteur de fichier pour le
      connecteur que le serveur écoute. Cette fonction est, la plupart
      du temps, passée à "selectors" afin de pouvoir surveiller
      plusieurs serveurs dans le même processus.

   handle_request()

      Traite une seule requête. Cette fonction appelle, dans l'ordre,
      les méthodes "get_request()", "verify_request()" et
      "process_request()". Si la méthode "handle()" de la classe de
      gestion des requêtes lève une exception, alors la méthode
      "handle_error()" du serveur est appelée. Si aucune requête n'est
      reçue avant « "timeout" » secondes, "handle_timeout()" est
      appelée et "handle_request()" rend la main.

   serve_forever(poll_interval=0.5)

      Gère les requêtes indéfiniment jusqu'à ce que "shutdown()" soit
      appelée. Vérifie si une demande d’arrêt (*shutdown*) a été émise
      toutes les *poll_interval* secondes. Ignore l'attribut
      "timeout". Appelle également "service_actions()", qui peut être
      utilisée par une classe enfant ou une classe de mélange afin
      d'implémenter une action spécifique pour un service donné. Par
      exemple, la classe "ForkingMixIn" utilise "service_actions()"
      pour supprimer les processus enfants zombies.

      Modifié dans la version 3.3: La méthode "serve_forever" appelle
      dorénavant "service_actions".

   service_actions()

      Cette méthode est appelée dans la boucle de "serve_forever()".
      Cette méthode peut être surchargée par une classe fille ou une
      classe de mélange afin d'effectuer une action spécifique à un
      service donné, comme une action de nettoyage.

      Ajouté dans la version 3.3.

   shutdown()

      Demande l'arrêt de la boucle de "serve_forever()" et attend
      jusqu'à ce que ce soit fait. "shutdown()" doit être appelée dans
      un fil d’exécution différent de "serve_forever()" sous peine
      d'interblocage.

   server_close()

      Nettoie le serveur. Peut être surchargée.

   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

      La classe de gestion des requêtes, fournie par l'utilisateur.
      Une instance de cette classe est créée pour chaque requête.

   server_address

      Renvoie l’adresse sur laquelle le serveur écoute. Le format de
      l’adresse dépend de la famille de protocoles utilisée ; pour
      plus de détails, voir la documentation du module "socket". Pour
      les protocoles Internet, cette valeur est une paire formée d'une
      chaine de caractère pour l’adresse et d'un entier pour le port.
      Exemple : "('127.0.0.1', 80)".

   socket

      L'objet connecteur utilisé par le serveur pour écouter les
      nouvelles requêtes.

   Les classes serveurs prennent en charge les variables de classe
   suivantes :

   allow_reuse_address

      Indique si le serveur autorise la réutilisation d'une adresse.
      La valeur par défaut est "False" mais cela peut être changé dans
      les classes enfants.

   request_queue_size

      La taille de la file des requêtes. Lorsque traiter une requête
      prend du temps, toute nouvelle requête arrivant pendant que le
      serveur est occupé est placé dans une file jusqu'à atteindre «
      "request_queue_size" » requêtes. Si la queue est pleine, les
      nouvelles requêtes clientes se voient renvoyer une erreur
      *Connection denied*. La valeur par défaut est habituellement 5
      mais peut être changée par les classes filles.

   socket_type

      Le type de connecteur utilisé par le serveur ;
      "socket.SOCK_STREAM" et "socket.SOCK_DGRAM" sont deux valeurs
      usuelles.

   timeout

      Délai d'attente en secondes, ou "None" si aucune limite n'est
      demandée. Si "handle_request()" ne reçoit aucune requête
      entrante pendant le délai d'attente, la méthode
      "handle_timeout()" est appelée.

   Il existe plusieurs méthodes serveur pouvant être surchargées par
   des classes dérivant de classes de base comme "TCPServer" ; ces
   méthodes ne sont pas utiles aux utilisateurs externes de l'objet
   serveur.

   finish_request(request, client_address)

      Méthode en charge de traiter la requête en instanciant
      "RequestHandlerClass" et en appelant sa méthode "handle()".

   get_request()

      Accepte obligatoirement une requête depuis le connecteur et
      renvoie une paire contenant le *nouvel* objet connecteur utilisé
      pour communiquer avec le client et l'adresse du client.

   handle_error(request, client_address)

      Cette fonction est appelée si la méthode "handle()" de l'objet
      "RequestHandlerClass" lève une exception. Par défaut, la méthode
      imprime la trace d'appels sur la sortie d'erreur standard et
      continue de traiter les requêtes suivantes.

      Modifié dans la version 3.6: N'est maintenant appelée que sur
      les exceptions dérivant de la classe "Exception".

   handle_timeout()

      Cette fonction est appelée lorsque l'attribut "timeout" est
      réglé à autre chose que "None" et que le délai d'attente expire
      sans qu'aucune requête ne soit reçue. Par défaut, cette fonction
      récupère le statut de tous les processus enfants ayant terminé
      pour les serveurs utilisant des processus ou ne fait rien pour
      le cas des serveurs utilisant des fils d’exécution.

   process_request(request, client_address)

      Appelle "finish_request()" pour instancier
      "RequestHandlerClass". Si désiré, cette fonction peut créer des
      processus fils ou des fils d’exécution pour traiter les requêtes
      ; les classes de mélange "ForkingMixIn" et "ThreadingMixIn"
      implémentent cela.

   server_activate()

      Appelée par le constructeur du serveur afin de l'activer. Le
      comportement par défaut pour un serveur TCP est de seulement
      invoquer "listen()" sur le connecteur du serveur. Peut être
      surchargée.

   server_bind()

      Appelée par le constructeur du serveur afin d'assigner (*bind*)
      l'adresse requise au connecteur du serveur. Peut être
      surchargée.

   verify_request(request, client_address)

      Doit renvoyer un booléen. Si la valeur est "True", la requête
      sera traitée. Si la valeur est "False", la requête sera refusée.
      Cette fonction peut être surchargée afin d'implémenter une
      stratégie de contrôle d'accès au serveur. L'implémentation par
      défaut renvoie toujours "True".

   Modifié dans la version 3.6: La gestion du protocole *context
   manager* a été ajoutée. Sortir du gestionnaire de contexte revient
   à appeler "server_close()".


Objets gestionnaire de requêtes
===============================

class socketserver.BaseRequestHandler

   Classe de base de tous les objets gestionnaire de requêtes. Elle
   déclare l'interface, définie ci-dessous, pour tous les
   gestionnaires. Une implémentation concrète doit définir une
   nouvelle méthode "handle()" et peut surcharger n'importe quelle
   autre méthode. Cette classe concrète est instanciée pour chaque
   requête.

   setup()

      Appelée avant la méthode "handle()" afin d'effectuer toutes les
      opérations d'initialisation requises. L'implémentation par
      défaut ne fait rien.

   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()

      Appelée après la méthode "handle()" pour effectuer les
      opérations de nettoyage requises. L'implémentation par défaut ne
      fait rien. Si "setup()" lève une exception, cette méthode n'est
      pas appelée.

   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

   Modifié dans la version 3.6: "wfile" also supports the
   "io.BufferedIOBase" writable interface.


Exemples
========


Exemple pour "socketserver.TCPServer"
-------------------------------------

Implémentation côté serveur :

   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()

Une implémentation alternative du gestionnaire de requêtes utilisant
les flux de données (avec des objets fichier-compatibles simplifiant
la communication en fournissant l'interface fichier standard) :

   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 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.

Implémentation côté client :

   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)

La sortie de cet exemple devrait ressembler à ça :

Serveur :

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

Client :

   $ 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


Exemple pour "socketserver.UDPServer"
-------------------------------------

Implémentation côté serveur :

   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()

Implémentation côté client :

   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)

La sortie de cet exemple devrait ressembler exactement à la sortie de
l'exemple pour le serveur TCP.


Classes de mélange asynchrone
-----------------------------

Pour développer des gestionnaires asynchrones, utilisez les classes
"ThreadingMixIn" et "ForkingMixIn".

Exemple pour "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()

La sortie de cet exemple devrait ressembler à ça :

   $ 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

La classe "ForkingMixIn" est utilisable de la même façon à la
différence près que le serveur crée un nouveau processus fils pour
chaque requête. Disponible uniquement sur les plateformes POSIX
prenant en charge "fork()".
