Code source de Serveur

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Script principal du serveur d'écoute de la Note Kfet 2015
Codé par 20-100, inspiré du protocole client/serveur de la webradio codé par Michou.

Author: Vincent Le Gallic <legallic@crans.org>

Dans ce fichier sont définies les classes suivantes :
 * :py:class:`Server` (un serveur qui répond à un unique client)
 * :py:class:`MainServer` (serveur qui attend les connexions entrantes et thread des instances de Server)
 
Si ce script est exécuté en tant que __main__, il instancie un MainServer et le lance.

"""

# Avant toute chose, on n'a pas envie d'être au mauvais endroit
import os
thisfile = os.path.realpath(__file__)
thisdirectory = thisfile.rsplit("/", 1)[0]
os.chdir(thisdirectory)

# useful
import sys
import inspect
import time
import re
# pour rattrapper un SIGHUP
import signal
# communication
import socket
import ssl
import threading
# pour encoder/décoder les photos
import base64
# pour hasher les mdp
import hashlib
# les objets échangés sont des objets json
import json
import traceback
# module d'accès à la bdd
import psycopg2
import psycopg2.extras

# fichier qui contient toutes les variables de configuration (place des fichiers, et compagnie...)
sys.path.append("../config/")
import config
# Définition de la classe Compte
from BaseFonctions import Compte
# module qui définit les commandes du Server
import ServeurFonctions
# module qui définit des fonctions essentielles
import BaseFonctions
# module qui définit les fonctions de trésorerie
import TresorerieFonctions
# module qui définit les fonctions de trésorerie
import WeiFonctions
# module qui définit les erreurs
import ExceptionsNote
# Classe donnant accès aux services d'authentification.
from AuthService import AuthService

#: Encodage qu'on utilise pour faire un print
default_output_encoding = sys.stdout.encoding or "utf-8"

[docs]class Server(object): """Classe du serveur. Répond à un seul client.""" ### Fonctions utilitaires
[docs] def _debug(self, debuglevel, text=u""): """Affiche des informations de debug.""" name = u"[%s]_" % (unicode(self.ip),) name += unicode(self.port) if self.isauth: if self.userid == "special": name += u"-" + self.username else: name += u"-#%s" % (self.userid) BaseFonctions.debug(debuglevel, text, name)
[docs] def _log(self, fonction, cur, params="", relevant_ids=[]): """Enregistre une action dans la table de log. Fournir le curseur n'est **pas** facultatif et il n'est pas COMMITé.""" if self.userid == "special": utilisateur = u"%s (special)" % self.username else: utilisateur = u"%s (bdd)" % self.userid BaseFonctions.log(self.ip, utilisateur, fonction, cur, params, relevant_ids)
_has_acl = ServeurFonctions._has_acl _myself = ServeurFonctions._myself _refresh_session = ServeurFonctions._refresh_session _kill_session = ServeurFonctions._kill_session _has_timeouted = ServeurFonctions._has_timeouted _handle_duplicate_account = ServeurFonctions._handle_duplicate_account ### Communications
[docs] def _jsonload(self, objet): """Charge un objet JSON. Lève une JsonError en cas d'échec.""" try: return json.loads(objet) except: raise ExceptionsNote.JsonError("echec de load : <failed object>\n%s\n</failed object>")
[docs] def _recv(self): """Lit sur la socket jusqu'à ce que l'output soit déJSON-izable""" input = "" maxi = int(config.request_max_size/4096.0)+1 for i in range(maxi): input += self._recv_(4096) try: return self._jsonload(input) except ExceptionsNote.JsonError: pass # Si on n'a rien, c'est que le client a fermé sa socket if len(input) == 0: raise ExceptionsNote.DeadClient # Si on est sorti de la boucle, c'est que la requête est trop longue raise ExceptionsNote.RequestTooLong
[docs] def _recv_(self, size): """Reçoit un message, via ssl.""" return self.socket.read(size)
[docs] def _send(self, objet, retcode=0, errormessage=u""): """Fabrique un message, pour envoyer ``objet`` avec ``retcode`` et ``errormessage`` (par défaut, 0 et ``""``)""" msg = {"retcode": retcode, "errmsg": errormessage, "msg": objet} return self._send_(msg)
[docs] def _send_(self, msg): """ Envoie un message, via ssl. Il est parsé pour gérer les types pgsql, puis JSON-isé avant d'etre envoyé (avec un newline à la fin). """ # Il est autorisé d'envoyer à peu près n'importe quoi. # l'important c'est que ce soit JSON-isable. # Si ça l'est pas, bah tu crashes, t'avais qu'à mieux coder =D try: text = json.dumps(BaseFonctions.pg_parse(msg)) + "\n" except Exception as e: raise ## 20-100 : laisser ça tel quel ? => Je dirais que oui #raise ExceptionsNote.JsonError("echec de dump : <failed object>\n%s\n</failed object>" % (obj,)) # send the length of the json data first self.socket.send('%d\n' % len(text)) # send the json data return self.socket.sendall(text)
### Commandes #### Commandes basiques hello = ServeurFonctions.hello help = ServeurFonctions.help man = ServeurFonctions.man login = ServeurFonctions.login #### Commandes des special users adduser = ServeurFonctions.adduser deluser = ServeurFonctions.deluser die = ServeurFonctions.die myconnection = ServeurFonctions.myconnection # Peut aussi être utilisée par des non-special users users = ServeurFonctions.users #### Who whowith = ServeurFonctions.whowith
[docs] def who(self): """Donne la liste des utilisateurs connectés et leurs ip, port ( + username, userid) N'est en fait qu'un alias de ``whowith({})``. """ return ServeurFonctions.whowith(self, {})
[docs] def whospecial(self): """Donne la liste des utilisateurs spéciaux connectés (ip, port, username)""" return ServeurFonctions.whowith(self, {u"userid": [u"special"]})
[docs] def whomanualclient(self): """Donne la liste des utilisateurs connectés en client manuel""" return ServeurFonctions.whowith(self, {u"client": [u"manual"]})
[docs] def whohttpclient(self): """Donne la liste des utilisateurs connectés en client http""" return ServeurFonctions.whowith(self, {u"client": [u"http django"]})
[docs] def whononeclient(self): """Donne la liste des utilisateurs connectés sans avoir déclaré leur client""" return ServeurFonctions.whowith(self, {u"client": [u"None"]})
#### Commandes de communications client_speak = ServeurFonctions.client_speak client_broadcast = ServeurFonctions.client_broadcast ### Fonctions de note #### Recherche et affichages search = ServeurFonctions.search quick_search = ServeurFonctions.quick_search historique_pseudo = ServeurFonctions.historique_pseudo search_historique_pseudo = ServeurFonctions.search_historique_pseudo whoami = ServeurFonctions.whoami compte = ServeurFonctions.compte get_display_info = ServeurFonctions.get_display_info liste_droits = ServeurFonctions.liste_droits # alias de compte adherent = compte get_tarifs_adhesion = ServeurFonctions.get_tarifs_adhesion #### Modifications chgpass = ServeurFonctions.chgpass # alias de chgpass (c'était son ancien nom) set_passwd = chgpass generate_reset_password = ServeurFonctions.generate_reset_password confirm_reset_password = ServeurFonctions.confirm_reset_password confirm_email = ServeurFonctions.confirm_email update_compte = ServeurFonctions.update_compte supprimer_compte = ServeurFonctions.supprimer_compte update_photo = ServeurFonctions.update_photo get_last_modified_photo = ServeurFonctions.get_last_modified_photo get_photo = ServeurFonctions.get_photo get_preinscription = ServeurFonctions.get_preinscription get_preinscriptions = ServeurFonctions.get_preinscriptions preinscrire = ServeurFonctions.preinscrire del_preinscription = ServeurFonctions.del_preinscription get_default_pseudo = ServeurFonctions.get_default_pseudo inscrire = ServeurFonctions.inscrire readherer = ServeurFonctions.readherer alias = ServeurFonctions.alias unalias = ServeurFonctions.unalias #### Boutons get_boutons = ServeurFonctions.get_boutons get_boutons_categories = ServeurFonctions.get_boutons_categories get_un_bouton = ServeurFonctions.get_un_bouton get_clubs = ServeurFonctions.get_clubs create_bouton = ServeurFonctions.create_bouton update_bouton = ServeurFonctions.update_bouton delete_bouton = ServeurFonctions.delete_bouton #### Transactions crediter = ServeurFonctions.crediter # alias de crediter credit = crediter consos = ServeurFonctions.consos transferts = ServeurFonctions.transferts dons = ServeurFonctions.dons retirer = ServeurFonctions.retirer # alias de retirer retrait = retirer historique_transactions = ServeurFonctions.historique_transactions valider_transaction = ServeurFonctions.valider_transaction devalider_transaction = ServeurFonctions.devalider_transaction #### Activités get_activites = ServeurFonctions.get_activites # alias de get_activites activites = get_activites get_activite = ServeurFonctions.get_activite # alias de get_activite activite = get_activite add_activite = ServeurFonctions.add_activite update_activite = ServeurFonctions.update_activite del_activite = ServeurFonctions.del_activite valider_activite = ServeurFonctions.valider_activite devalider_activite = ServeurFonctions.devalider_activite add_invite = ServeurFonctions.add_invite del_invite = ServeurFonctions.del_invite get_invites = ServeurFonctions.get_invites liste_invites = ServeurFonctions.liste_invites ### Fonctions pour l'application WEI wei_get_info = WeiFonctions.wei_get_info wei_main = WeiFonctions.wei_main wei_get_listes = WeiFonctions.wei_get_listes wei_readherer = WeiFonctions.wei_readherer wei_search = WeiFonctions.wei_search wei_compute_form = WeiFonctions.wei_compute_form wei_update_tables = WeiFonctions.wei_update_tables wei_modify = WeiFonctions.wei_modify wei_modify1a = WeiFonctions.wei_modify1a wei_creer_note = WeiFonctions.wei_creer_note wei_admin = WeiFonctions.wei_admin ### Fonctions de l'application trésorerie liste_remises = TresorerieFonctions.liste_remises remises_open = TresorerieFonctions.remises_open creer_remise = TresorerieFonctions.creer_remise liste_cheques = TresorerieFonctions.liste_cheques transaction = TresorerieFonctions.transaction ajout_remise = TresorerieFonctions.ajout_remise clore_remise = TresorerieFonctions.clore_remise infos_remise = TresorerieFonctions.infos_remise ### Fonction spéciales client Django django_get_accessible_pages = ServeurFonctions.django_get_accessible_pages ### Random garbage mayi = ServeurFonctions.mayi # May I ### Fonctions de boucle
[docs] def _select(self, cmd, param=None): """Sélectionne la bonne commande et l'exécute. Transmet une erreur au client si l'exécution est impossibe (commande inexistante, paramètre attendu, …) """ # On stocke le paramètre self._last_param = param cmd = cmd.lower() # Le exit/quit (alias de la même commande) sont tellement faciles # à faire qu'ils sont fait immédiatement if cmd in ["exit", "quit", "leave"]: self._send("Bye ;-)") self._debug(1, u"Client exit") # On indique à la fonction appelante qu'il faut quitter. return True if BaseFonctions.executable_cmd(cmd): try: action = self.__getattribute__(cmd) except AttributeError: pass else: hasarg = len(inspect.getargspec(action)[0]) > 1 if hasarg: if param == None: self._send(None, 3, u"Cette commande attend un paramètre.") self._debug(3, u"Exécution de %s failed, paramètre attendu. Abort." % (cmd,)) return # On exécute la commande si le client a été reconnu (sauf si elle passe sans hello) # certaines commandes sont autorisées avant le hello : # hello (évidemment), help et man (pour savoir comment le faire) not_hello_cmds = ["hello", "help", "man"] if (cmd in not_hello_cmds) or (self.client_version != None): action(param) else: self._send(None, 1, u"""Tu m'as pas dit bonjour !\nEssaye 'hello "client_version"' ou help.""") else: action() return # Si on n'a pas rencontré un return c'est que la commande n'existe pas try: self._send(None, 404, u"Commande " + cmd + u" inexistante. (regarde help pour la liste)\n") self._debug(3, u"Commande " + cmd + u" inexistante.") except UnicodeDecodeError: self._send(None, 255, u"Commande mal formée (le nom d'une commande ne doit pas sortir de l'ascii).") self._debug(3, u"Commande foirée par UnicodeDecodeError.")
[docs] def _run(self): """Boucle du serveur.""" self._debug(5, u"Thread Server lancé. (id = %s)" % (self.idServer,)) closing = self.exiting.isSet() try: while not closing: try: request = self._recv() except ExceptionsNote.DeadClient: # Le client a fermé sa socket, on quitte self._debug(4, u"Socket fermée à l'autre extrémité. Connexion %s fermée." % (self.idServer,)) closing = True continue except ExceptionsNote.RequestTooLong: # La requête est trop longue, on le signale au client self._send(None, 414, u"Requête trop longue. Taille maximale autorisée : %s Bytes" % (config.request_max_size)) self._debug(3, u"Requête échouée car trop longue.") # Comme il reste éventuellement du garbage sur la socket, on n'arrivera jamais à retomber sur une initialisation de paquet # donc on clos la connexion closing = True continue except ExceptionsNote.JsonError: self._send(None, 2, u"Requête mal formée. (Elle doit être une liste JSON à 1 ou 2 éléments)") self._debug(3, u"Requête mal formée. (non dé-JSON izable)") continue if not (isinstance(request, list) and len(request) in [1, 2]): self._send(None, 2, u"Requête mal formée. (Elle doit être une liste JSON à 1 ou 2 éléments)") self._debug(3, u"Requête mal formée. (pas une liste ou liste de la mauvaise taille)") continue if len(request) == 1: cmd, param = request[0], None elif len(request) == 2: [cmd, param] = request # On va exécuter la commande, suivie éventuellement de paramètres try: quitter = self._select(cmd, param) except ExceptionsNote.WaitNextCommand: # La fonction a spécifiquement demandé à sauter directement à l'attente # de la prochaine instruction client continue if quitter: closing = True closing = closing or self.exiting.isSet() except socket.error: self._debug(3, u"Erreur à la réception.") closing = True except Exception as exc: # On ne sait pas trop ce qui nous a envoyé dans le décor, dans le doute on va quand même # essayer de prévenir le client qu'on a crashé # (s'il écoute toujours et que la socket peur toujours transmettre) try: self._send(None, 666, u"FATAL : erreur interne : %s\n%s" % (str(type(exc)).decode("utf-8"), traceback.format_exc().decode("utf-8"))) except Exception as exc2: pass # Si on arrive là, c'est qu'on a crashé. # On ferme d'abord proprement la socket self.socket.close() # On affiche tout le traceback dans les logs self._debug(2, traceback.format_exc().decode("utf-8")) self._debug(4, u"Fermeture de la socket.") self.socket.close() # On s'enlève de la liste des online del self.auth.masterserver.online_list[self.idServer] del self.auth.masterserver.online_servers_list[self.idServer]
def __init__(self, idServer, sock, address, auth, exiting): """Initialise la classe avec la connexion acceptée.""" self.idServer = idServer self.socket = ssl.wrap_socket(sock, server_side=True, certfile=config.server_certfile, keyfile=config.server_keyfile) self.writingLock = threading.Lock() self.ip, self.port = address # Une référence vers l'AuthService self.auth = auth # On ajoute le serveur à la liste des serveurs online # ainsi, le MainServer peut parler sur la socket # et a aussi d'accessible un lock pour pouvoir parler *seul* self.auth.masterserver.online_servers_list[self.idServer] = (self) # L'évènement du MainServer qui est set quand on va quitter self.exiting = exiting # Au début de la connexion, on n'est pas authentifié, et on n'a pas dit bonjour... self.isauth, self.client_version = False, None # On n'a ni nom... self.username = "" # ...ni numéro self.userid = None # NB, dans les test conditionnels, None<0 est True...
# Main-class : le serveur principal qui écoute et qui threade
[docs]class MainServer(object): """Classe principale."""
[docs] def _debug(self, debuglevel, text=u"", linejumps=0): """Affiche des informations de debug.""" BaseFonctions.debug(debuglevel, text, u"MainServer", linejumps)
[docs] def speak(self, idServer, message, broadcasting=False): """Envoie un message au client d'un ``Server`` particulier. Usage ``MainServer`` seulement. Lève une exception si l'``idServer`` n'existe pas. Est appelé par :py:meth:`Server.client_broadcast` si le client a les droits.""" all_servers = self.online_servers_list if all_servers.has_key(idServer): serveur = all_servers[idServer] # On veut parler à quelqu'un, mais on n'est pas sûr qu'il soit toujours là # ni qu'il est pas déjà entrain d'écrire. # on va tenter 3 fois d'acuérir le lock d'écriture (en se mettant en non-bloquant) # et en attendant 0.2 seconde got_lock = False for i in range(3): if serveur.writingLock.acquire(False): got_lock = True break else: time.sleep(0.2) if got_lock: if broadcasting: try: # On inline aussi le message dans le message d'erreur serveur._send(message, 101, "message broadcasted :\n%s" % (message)) except: pass else: try: serveur._send(message, 102, "message from another Client") except: pass serveur.writingLock.release() if not broadcasting: self._debug(5, u"writing to Server %s (%s:%s) : %s" % (idServer, serveur.ip, serveur.port, message)) else: self._debug(3, u"writing FAILED to Server %s (%s:%s) (message %s)\n" % (idServer, serveur.ip, serveur.port, message) + "Unable to acquire Lock.") else: raise ExceptionsNote.NoSuchServer(idServer)
[docs] def broadcast(self, message): """Envoie un message à tous les clients connectés. Usage ``MainServer`` uniquement. Est appelé par :py:meth:`Server.client_broadcast` si le client a les droits.""" all_servers_ids = self.online_servers_list.keys() self._debug(5, u"Broadcasting : %s" % (message)) for num in all_servers_ids: try: self.speak(num, message, True) except ExceptionsNote.NoSuchServer: pass except AttributeError as e: if (str(e) == "'NoneType' object has no attribute 'write'"): pass
[docs] def run(self): """Accepte les connexions.""" # On lance le Thread try: self.listener.setblocking(0) while not self.exiting.isSet(): connectionreceived = False try: (conn, address) = self.listener.accept() connectionreceived = True except: pass if connectionreceived: new_nb_clients = self.active_clients() + 1 self._debug(5, u"Nouveau client (%s:%s); %s client%s" % (address[0], address[1], new_nb_clients, "s" * (new_nb_clients > 1))) if (self.online_list == {}): ident =1 else: ident = max(self.online_list.keys()) + 1 (ip, port) = address self.online_list[ident] = {"ip": ip, "port": port} try: newserver = Server(ident, conn, address, self.auth, self.exiting) newthread = threading.Thread(name="Server-%s" % str(address), target = newserver._run) # On passe les threads en daemons afin # de pouvoir arrêter le serveur newthread.setDaemon(True) newthread.start() except Exception as e: # Si y'a un échec de la connexion, on va pas mourrir bêtement pour autant self._debug(3, u"Crash de la connexion %s. Erreur : %s %s.\nTraceback :\ns%s" % (list(address), str(type(e)).decode("utf-8"), str(e).decode("utf-8"),traceback.format_exc().decode("utf-8"),)) time.sleep(0.1) except KeyboardInterrupt: self.stopping(u"KeyboardInterrupt")
[docs] def stopping(self, reason="(NO REASON ?!!)"): """Arrête le serveur en précisant pourquoi.""" if not self.exiting.isSet(): self.exiting.set() self.listener.close() self._debug(0, u"Arrêt en cours ... (%s)" % (reason,)) # On prévient les clients qu'on s'éteint self.broadcast("Broadcast from MainServer :\n" + "MainServer Note Kfet 2015 is going down in 5 seconds.\n") # On attend le départ des clients for i in range(5, 1, -1): if self.active_clients() > 0: self._debug(5, u"Attente du départ des " + unicode(str(self.active_clients())) + u" clients ... (" + unicode(str(i)) + u" secondes)") time.sleep(1) else: self._debug(5, u"Plus de clients ...") break if self.active_clients() > 0: self._debug(5, u"Attente du départ des " + str(self.active_clients()) + u" clients ... (1 seconde)") time.sleep(1) if self.active_clients() > 0: # Python quitte quand tous les threads non flaggés daemon finissent # Tous les Server sont daemon, reste le MainThread # là, il est sur le point de finir, donc tout va bien. self.broadcast("Broadcast from MainServer :\nMainServer forces shutdown NOW.\n") self._debug(0, u"Arrêt forcé ...") else: self._debug(5, u"Plus de clients ...")
[docs] def active_clients(self): """Retourne le nombre de threads ``Server``.""" total = 0 for t in threading.enumerate(): if (t.getName()[:7] == "Server-"): total += 1 return total
[docs] def bind(self, addr, port): """Ajoute un port à écouter.""" self._debug(0, u"Ecoute sur " + addr + u":" + unicode(str(config.listen_port))) self.listener.bind((addr, config.listen_port))
[docs] def reload(self): """Appelée en cas de SIGHUP""" reload(config) self._debug(1, u"SIGHUP received, config reloaded")
def __init__(self): """Initialise la classe et les communications.""" message = u"Server started." message += u"\nDebug levels : file=%s stdout=%s" % tuple(i[0] if i[1] else None for i in [(config.debug_level_logfile, config.debug_logfile), (config.debug_level_stdout, config.debug_stdout)]) self._debug(0, message, 2) self.listener = socket.socket() # pour pouvoir réutiliser le port en cas de crash du serveur self.listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) map(lambda addr: self.bind(addr, config.listen_port), config.listen_bind) self.listener.listen(5) # Signale si le serveur est en train de s'arrêter self.exiting = threading.Event() self.auth = AuthService(self) # Permet de maintenir une liste des gens en ligne # (NB : pour éviter d'avoir à savoir quels sont les paramètres, # ce sera une liste (enfin un dico) de dicos dans lesquels on met ce qu'on veut) self.online_list = {} self.online_servers_list = {} # la même chose, mais contenant les Server eux-même
[docs] def run_as_daemon(self, outfile): """Utilisé quand le ``MainServer`` est lancé en daemon""" sys.stderr = Writer(outfile) self.run()
[docs]def override_configuration(): """Parse ``sys.argv`` pour potentiellement écraser des variables de configuration de ``config.py``""" import sys variables = [k for k in config.__dict__.keys() if not re.match('__.*__', k)] for parametre in sys.argv[1:]: try: param, val = parametre.strip('-').split("=") except: if not parametre.strip("-") in ["daemon", "no-output"]: print """Warning : Syntaxe d'override de la configuration : --parametre=valeur ("%s" ne correspond pas)""" % (parametre,) continue if param in variables: typ = type(config.__dict__[param]) try: config.__setattr__(param, typ(val)) except: print 'Warning : paramètre "%s" : impossible de convertir "%s" en %s, paramètre ignoré.' % (param, val, typ.__name__) else: print 'Warning : paramètre "%s" inconnu, ignoré.' % (param,)
### Pour l'affichage de l'aide
[docs]def affiche_options(): """Affiche les variables de configuration qui peuvent être redéfinies au runtime.""" options = [(k, v) for k, v in config.__dict__.items() if not k.startswith("_")] options.sort(lambda x, y: cmp(x[0], y[0])) help = u"" namesize = max([len(k) for (k, _) in options]) template = u"%%-%ss%%s\n" % (namesize + 4,) for k, v in options: if not k == "man_dico": help += template % (k, v) return help
[docs]class Writer(object): """Pour écrire ailleurs que sur stdout""" def __init__(self, filename=None): if filename == None: filename = config.default_stdout self.filename = filename
[docs] def write(self, message): """Écrit ``message`` dans le Writer""" f = open(self.filename, "a") f.write(message) f.close()
[docs]def closing(): """Ferme le fichier de log et termine. *(Comme il est possible qu'on soit lancé en daemon, donc, qu'on ait forké, il ne faut pas appeler cette fonction n'importe où.)*""" # On ferme le fichier pour être bien sûr que ça a été fait f = open(config.logfile, "r") f.close() print "Thanks for using Note Kfet 2015 :D"
if __name__ == "__main__": import sys if "-h" in sys.argv or "--help" in sys.argv: usage = u"""Usage : Serveur.py [--no-output] [--outfile=PATH] [--daemon [--pidfile=PATH]] [OPTIONS] --no-output Redirige stdout vers un fichier (par défaut %s) --outfile Change le fichier vers lequel est redirigé stdout --daemon Lance le processus en daemon. (Implique --no-output) --pidfile Donne le path du fichier mémorisant le pid (par défaut %s) -h Affiche ce message d'aide et quitte --help Affiche un message d'aide plus développé --outfile sans --no-output ni --daemon n'a aucun effet""" % (config.default_stdout, config.pidfile) print usage.encode(default_output_encoding) if "--help" in sys.argv: usage_extend = u""" OPTIONS a la forme générale suivante : --param=VALUE et a pour effet d'écraser le paramètre défini dans config.py ATTENTION : en cas de réception d'un SIGHUP, la config est rechargé à partir des valeurs du fichier config.py, et donc les paramètres que tu as éventuellement passés en ligne de commande sont perdus Voici les paramètres existants et leur valeur par défaut : %s""" % (affiche_options(),) print usage_extend.encode(default_output_encoding) exit(0) # On remplace les variables de config par celles éventuellement fournies en commandline override_configuration() # On remplace stdout if "--no-output" in sys.argv or "--daemon" in sys.argv: outfile = config.default_stdout for arg in sys.argv: arg = arg.split("=") if arg[0].strip('-') == "outfile": outfile = arg[1] sys.stdout = Writer(outfile) # On instancie le MainServer mainserv = MainServer() # Si on reçoit un SIGHUP, on reload la config def sighup_handler(signum, frame): mainserv.reload() signal.signal(signal.SIGHUP, sighup_handler) # Si on reçoit un SIGTERM, on s'arrête proprement def sigterm_handler(signum, frame): mainserv.stopping(u"SIGTERM") signal.signal(signal.SIGTERM, sigterm_handler) # On se lance différemment si on est en daemon ou pas if "--daemon" in sys.argv: child_pid = os.fork() if child_pid == 0: os.setsid() mainserv.run_as_daemon(outfile) closing() else: # On enregistre le pid pidfile = config.pidfile for arg in sys.argv: arg = arg.split("=") if arg[0].strip('-') in ["pidfile"]: pidfile = arg[1] f = open(pidfile, "w") f.write("%s\n" % child_pid) f.close() else: mainserv.run() closing()