PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Attributs privés en Python

samedi 21 juin 2014 à 03:15

Tout est accessible en Python. Il n’y a pas de variables privées.

Quand on veut une variable à usage interne, la convention est de la nommer avec un underscore devant :

class Monique:
    def __init__(self):
        self._private = "lessons"

Ça n’empêche rien, mais les devs savent qu’il faut éviter d’utiliser cette variable car elle ne fait pas partie de l’API publique de la classe, et l’implémentation pourrait changer plus tard.

Comme self, c’est une convention forte puisque la plupart des libs de completion du code la prennent en compte.

Il existe une fonctionalité moins connue, qui fait que quand on utilise deux underscores, accéder directement à la variable lève un AttributeError:

class Monique:
    def __init__(self):
        self.__private = "lessons"
 
m = Monique()
print(m.__private)
AttributeError: 'Monique' object has no attribute '__private'

Du coup, quelques rares personnes ont utilisé cette feature pour émuler des attributs privés, ignorant le fait qu’on vous dit partout sur la toile que tout est accessible en Python.

C’est une mauvaise idée.

C’est une mauvaise idée car si votre objectif est la sécurité, ça ne sert à rien puisque votre variable est accessible de tas de manières détournées différentes. Par exemple :

print(m.__dict__['_Monique__private'])
# lessons
m.__dict__['_Monique__private'] = None
print(m.__dict__['_Monique__private'])
# None

C’est une mauvaise idée car si votre objectif est de créer une API publique, un seul underscore suffit.

Enfin c’est une mauvaise idée car si vous fournissez une lib qui ne correspond pas à un besoin, quelqu’un peut toujours contourner le problème en monkey patchant votre code le temps que vous trouviez une solution plus propre. En rendant la variable “privée”, vous rendez ceci plus difficile, sans le rendre impossible. Tout le monde y perd.

flattr this!

Mais qui donc hérite de ma classe ?

vendredi 20 juin 2014 à 15:39

Pas une question que l’on se pose souvent, à moins d’avoir une lib qui expose une classe avec beaucoup d’introspection.

Ceci dit pour la culture G, je me suis dit que ça pourrait intéresser quelques geeks de savoir comment lister les enfants d’une classe, récursivement.

def subclasses(cls, _found=()):
    """ Retourne toutes les classes héritant d'une classe """
 
    # On check que la classe est hérite bien de "object".
    # Utile uniquement pour un code 2.x qui utiliserait
    # des old style classes, qui n'ont pas "__subclasses__".
    if not isinstance(cls, type):
      raise TypeError('subclasses works only with new-style classes'
                            ', not %s' % cls)
 
    # On va stocker les classes rencontrées dans un set
    # pour éviter les doublons car on peut tomber sur
    # un héritage en forme de diamant.
    _found = _found or set()
 
    try:
      subs = cls.__subclasses__()
    except TypeError:  # Erreur levée si cls == type, petits vicieux
      subs = cls.__subclasses__(cls)
 
    for sub in subs:
        if sub not in _found:
            _found.add(sub)
            # Un appel récursif comme on les aimes
            subclasses(sub, _found)
 
    return _found

Pas très compliqué, donc, si on connait les piègeounets : ça ne marche pas sur les old syle classes, et il y a un traitement spécial pour type. Celà dit, qui utilise encore des old styles classes ? Et franchement lister les héritiers de type, à par pour satisfaire sa curiosité…

Aller, juste pour le fun, sur un virtualenv vierge:

for x in subclasses(object):
    print x
## <type 'callable-iterator'>
## <class '_abcoll.Sized'>
## <type 'exceptions.EOFError'>
## <class '_abcoll.Mapping'>
## <type 'exceptions.UnicodeWarning'>
## <type 'exceptions.IOError'>
## <type 'memoryview'>
## <type 'code'>
## <type 'exceptions.ImportWarning'>
## <type 'sys.flags'>
## <type 'iterator'>
## <class 'warnings.catch_warnings'>
## <type 'exceptions.Warning'>
## <type 'exceptions.EnvironmentError'>
## <type 'dict'>
## <class 'encodings.utf_8.IncrementalEncoder'>
## <type 'weakproxy'>
## <class '_abcoll.Iterator'>
## <type 'sys.version_info'>
## <type 'posix.stat_result'>
## <type 'frozenset'>
## <class 'codecs.CodecInfo'>
## <class 'zipimport.ZipImportError'>
## <type 'exceptions.StopIteration'>
## <type 'exceptions.ImportError'>
## <class 'site._Printer'>
## <type 'weakref'>
## <class '_abcoll.Sequence'>
## <type 'property'>
## <class 'encodings.utf_8.IncrementalDecoder'>
## <type 'imp.NullImporter'>
## <type 'exceptions.TypeError'>
## <type 'bytearray'>
## <type 'int'>
## <type 'exceptions.UnicodeTranslateError'>
## <type 'instance'>
## <type 'exceptions.BaseException'>
## <type 'exceptions.SyntaxWarning'>
## <type 'file'>
## <class '_abcoll.MappingView'>
## <class '_weakrefset._IterationGuard'>
## <type 'exceptions.UnicodeDecodeError'>
## <type 'classobj'>
## <type 'exceptions.UnicodeError'>
## <class 'abc.abstractproperty'>
## <type 'exceptions.DeprecationWarning'>
## <type 'complex'>
## <type 'set'>
## <type 'buffer'>
## <type 'generator'>
## <type 'exceptions.Exception'>
## <class 'codecs.IncrementalDecoder'>
## <type 'exceptions.UserWarning'>
## <type 'staticmethod'>
## <type 'exceptions.UnicodeEncodeError'>
## <class 'encodings.CodecRegistryError'>
## <class '_abcoll.MutableSequence'>
## <class '_abcoll.KeysView'>
## <type 'cell'>
## <type 'exceptions.StandardError'>
## <type 'enumerate'>
## <type 'exceptions.KeyboardInterrupt'>
## <type 'exceptions.BufferError'>
## <class 'warnings.WarningMessage'>
## <type 'EncodingMap'>
## <class 'codecs.BufferedIncrementalDecoder'>
## <type 'exceptions.ValueError'>
## <class '_abcoll.Set'>
## <type 'exceptions.MemoryError'>
## <type 'exceptions.SystemExit'>
## <type 'reversed'>
## <class '_abcoll.Hashable'>
## <type 'bool'>
## <type 'classmethod'>
## <type 'exceptions.ReferenceError'>
## <type 'exceptions.GeneratorExit'>
## <class '_abcoll.Container'>
## <class '_abcoll.ItemsView'>
## <type 'xrange'>
## <type 'exceptions.PendingDeprecationWarning'>
## <type 'basestring'>
## <type 'exceptions.SystemError'>
## <type 'exceptions.ZeroDivisionError'>
## <class 'site.Quitter'>
## <type 'long'>
## <type 'NotImplementedType'>
## <type 'super'>
## <type 'fieldnameiterator'>
## <class '_abcoll.Callable'>
## <class '_abcoll.MutableSet'>
## <type 'exceptions.ArithmeticError'>
## <class 'abc.ABCMeta'>
## <type 'exceptions.OverflowError'>
## <type 'exceptions.FutureWarning'>
## <type 'sys.float_info'>
## <type 'formatteriterator'>
## <type 'type'>
## <type 'exceptions.AssertionError'>
## <type 'traceback'>
## <type 'exceptions.FloatingPointError'>
## <type 'function'>
## <type 'instancemethod'>
## <type 'sys.long_info'>
## <type 'builtin_function_or_method'>
## <class 'signal.ItimerError'>
## <class 'site._Helper'>
## <type 'exceptions.LookupError'>
## <type 'wrapper_descriptor'>
## <type 'exceptions.KeyError'>
## <class '_abcoll.ValuesView'>
## <type 'slice'>
## <class '_weakrefset.WeakSet'>
## <type 'list'>
## <type 'exceptions.IndexError'>
## <type 'getset_descriptor'>
## <type 'posix.statvfs_result'>
## <type 'exceptions.SyntaxError'>
## <type 'frame'>
## <class 'codecs.BufferedIncrementalEncoder'>
## <type 'PyCapsule'>
## <type 'exceptions.IndentationError'>
## <type 'NoneType'>
## <type 'zipimport.zipimporter'>
## <type 'member_descriptor'>
## <type 'exceptions.AttributeError'>
## <type 'ellipsis'>
## <type 'exceptions.UnboundLocalError'>
## <type 'unicode'>
## <class '_abcoll.Iterable'>
## <type 'exceptions.TabError'>
## <type 'exceptions.NameError'>
## <type 'tuple'>
## <class 'warnings._OptionError'>
## <type 'exceptions.NotImplementedError'>
## <type 'exceptions.RuntimeWarning'>
## <type 'str'>
## <class '_abcoll.MutableMapping'>
## <type 'exceptions.BytesWarning'>
## <type 'module'>
## <type 'exceptions.RuntimeError'>
## <class 'codecs.IncrementalEncoder'>
## <type 'float'>
## <type 'exceptions.OSError'>
## <type 'dictproxy'>
## <type 'weakcallableproxy'>

Putain, ça c’est du remplissage ! 20minutes serait fier de moi.

flattr this!

Où il est présenté une méthode en Python pour afficher de la vidéo 3-bit dans son terminal.

jeudi 19 juin 2014 à 06:36

Ceci est un post invité de 01ivier posté sous licence creative common 3.0 unported.

Il est des choses inutiles plus passionnantes que d’autres.
Il semblerait que j’ai un petit faible pour les choses inutiles que je fais moi-même.

Car, afficher le contenu de sa webcam dans une console, ça ne sert clairement pas à grand chose mais j’ai pourtant crié très fort en serrant les poings quand j’y suis arrivé.

Capturer de la vidéo en Python.

Sous Nunux, il existe un joli petit paquet tout beau tout chaud qui permet de gérer du flux vidéo en Python : python-opencv.

Alors, zou :

sudo apt-get install python-opencv

Et pour afficher sa webcam, seules quelques lignes suffisent :

#-*- coding: utf-8 -*-
 
import cv2
 
# Choix du périphérique de capture. 0 pour /dev/video0.
cap = cv2.VideoCapture(0)
 
while True:
 
    # On capture l'image.
    ret,im = cap.read()
 
    # On l'affiche.
    cv2.imshow('Ma Webcam à moi',im)
 
    # Et on attend 40 millisecondes pour avoir du 25 images par seconde.
    key = cv2.waitKey(40)

GO !!

Je vais bien me garder de vous faire un tuto sur OpenCV tant cette librairie est puissante (comprendre : j’y panne que dalle). Si vous êtes curieux, vous pouvez aller faire un tour ici.

Et, parce que j’avais bien d’autres choses plus intéressantes à faire que d’aller visiter le lien ci-dessus, j’ai fait un petit print du im précédent et découvert une liste toute mignonne dont voici la structure:

# Avec des valeurs pour les niveaux comprises entre 0 et 255.
im[n° de ligne][n° de colonne][niveau de bleu, niveau de vert, niveau de rouge]

J’étais content car j’allais pouvoir récupérer les valeurs de chaque pixel de cette façon:

for ligne in range(hauteur):
 
    for colonne in range(largeur):
 
        niv_bleu = im[ligne][colonne][0]
        niv_vert = im[ligne][colonne][1]
        niv_rouge = im[ligne][colonne][2]

Si, pour un autre projet formidable, je n’avais pas eu à travailler avec une RasberryPi et ses petites cuisses de bébé, je pense que j’utiliserai encore cette méthode de bourrin.

Mais voilà, c’est juste ridicule quand on sait que la liste en question est en fait un array numpy et qu’il est 10, 100 fois plus rapide de faire:

for ligne in range(hauteur):
 
    for colonne in range(largeur):
 
        niv_bleu = im.item(ligne, colonne, 0)
        niv_vert = im.item(ligne, colonne, 1)
        niv_rouge = im.item(ligne, colonne, 2)

Je n’en suis pas à me dire que la prochaine fois que j’achèterai un grille-pain, je lirai la notice avant de l’utiliser, mais presque…

Petite remarque en passant : les valeurs de rouge, de vert et de bleu étant comprise entre 0 et 255, cela nous donne 256 valeurs pour chaque couleur.
Soit 256 x 256 x 256. Soit 2⁸ x 2⁸ x 2⁸. Soit 2²⁴ ==> les couleurs de notre flux sont codées par défaut sur 24 bits.

À noter qu’il est tout à fait possible d’analyser une image sans avoir à l’afficher, ce qui permet d’utiliser OpenCV dans un environnement sans gestionnaire de fenêtre.

Afficher de la couleur dans la console.

Bon. J’avais mes niveaux RVB pour chaque pixel. Il me fallait désormais afficher de la couleur dans la console.

Quelques requêtes Duck Duck Go plus tard, je découvre termcolor qui fait très bien le job mais dont on peut se passer en regardant les codes ANSI de plus près.

Bien entendu, la grande majorité des consoles étant limitées à 8 couleurs, soit 2³, soit 3-bit je me suis restreint à cette qualité.

Démonstration :

Il est possible d’obtenir beaucoup plus de couleurs en combinant les fonds, les caractères et les intensités comme le fait la libcaca, mais on ne joue pas vraiment dans la même cour.

Perso, quand ça s’est affiché en rouge pour la première fois, j’ai eu des frissons partout. Parce qu’il faut bien comprendre que je n’ai toujours aucune idée de ce que “\033[” et autres “m” veulent dire. J’ai copié/collé, c’est tout. Et dans ces cas là, quand ça marche, c’est toujours la fête.

Ce qui pourrait être un problème, on le voit à l’image, c’est qu’une fois que j’ai écrit TATA YOYO en rouge, le prompt devient lui aussi rouge, et ainsi de suite à chaque changement de couleur. Pour remédier à ça, il faut ajouter \033[0m à la fin du texte à afficher pour que le reste soit écrit avec la couleur par défaut du terminal.

Démonstration :

C’est d’ailleurs ce que fait termcolor, sauf que, dans notre cas, nous n’avons pas besoin de revenir à cette valeur par défaut à chaque affichage de pixel vu que le suivant sera lui aussi coloré.

Je vous en parle seulement parce que vous avez l’air sympa.

J’ajouterai qu’après avoir effectué un benchmark de folie exploitant brillamment les deux points qui clignotent à chaque seconde sur mon radio-réveil, il s’est avéré que la solution “maison” était plus performante que termcolor : mon choix était fait.

Du pixel au █

J’ai renoué contact avec le █ il n’y a pas si longtemps. Aussi étonnant que cela puisse paraître, alors que je baigne quasi quotidiennement dans l’informatique depuis 30 ans, il n’est pas impossible que notre dernière rencontre remonte à 1986 sur le Commodore 64 familial.

Pour vous donner une idée de l’émotion qui m’a traversé quand j’ai revu le █, vous pourriez très clairement user de l’expression “le █ d’Olivier” en lieu et place de “la madeleine de Proust” dans vos discussions. Mais, à l’oral, le █ passe mal, et c’est bien dommage.

Pour info, le pseudo unicode de █ c’est \u2588.

Et, pour afficher un █ en couleur, il suffit de faire comme vu au dessus.

Reste à trouver un moyen de passer des millions de couleurs potentielles de notre vidéo aux huit de notre console.

C’est là que vous allez comprendre pourquoi je me suis acharné avec mes captures d’écrans. C’était pour bien vous faire intégrer l’association entre les couleurs et la valeur qui les code. À savoir :

1 : rouge
2 : vert
3 : jaune
4 : bleu
5 : violet
6 : turquoise
7 : blanc

Et là qu’est qu’on remarque ?
Que cela respecte la synthèse additive si on attribue 1 au rouge, 2 au vert et 4 au bleu, bien entendu !

1 + 2 = 3 et en synthèse additive rouge + vert = jaune
1 + 4 = 5 et en synthèse additive rouge + bleu = violet
2 + 4 = 6 et en synthèse additive vert + bleu = turquoise
1 + 2 + 4 = 7 et en synthèse additive rouge + vert + bleu = blanc

Mettez-vous à ma place: je venais de découvrir l’Amérique !

Bon, rétrospectivement, cela ne constitue vraiment rien d’extraordinaire en soi dans la mesure où c’est ce qui découle logiquement d’un codage sur 3 bits mis en place par un être humain qui a juste envie de faire simple plutôt que de faire compliqué.

Mais tout de même, sur le moment…
… L’AMÉRIQUE BORDEL ! L’AMÉRIQUE !

Il devenait alors facile d’évaluer le degré de présence de chaque composante RVB d’un pixel puis de déterminer laquelle des 8 couleurs lui correspondait le plus.

Voilà comment je m’y suis pris :

# On initialise à 0 l'indice du pixel analysé 
indice_couleur = 0
 
# On analyse le niveau de Bleu du pixel.
# S'il est au dessus du seuil...
if img.item(ligne, colonne, 0) > seuil :
 
    #...on ajoute 4 à l'indice.
    indice_couleur += 4
 
# On analyse le niveau de Vert du pixel.
# S'il est au dessus du seuil...
if img.item(ligne, colonne, 1) > seuil :
 
    #...on ajoute 2 à l'indice.
    indice_couleur += 2
 
# On analyse le niveau de Rouge du pixel.
# S'il est au dessus du seuil...
if img.item(ligne, colonne, 2) > seuil :
 
    # ...on ajoute 1 à l'indice.
    indice_couleur += 1

L’indice obtenu correspond alors au code couleur ANSI à utiliser !!

Je veux dire.

Tout de même.

C’est super, non ?

Hum…

Bien, bien…
C’est bientôt fini, il me reste juste…

Quelques remarques supplémentaires.

1) Le noir ANSI est en fait du gris, et c’est bien moche, j’ai donc préféré partir du principe que la console aurait un fond noir et afficher un “espace” pour chaque pixel noir.

2) print(“\033[H\033[2J”) permet d’effacer la console comme le fait os.system(‘clear’).
Mais, j’imagine que ça devait faire trop de 033 dans le script pour moi, parce que, psychologiquement, ça ne passait pas.
J’ai un peu discuté avec moi-même et on a fini par décider d’utiliser le clear.

3) J’ai commencé par utiliser la concaténation pour ajouter mes █ colorés à mon texte_image final :

texte_image += u"\033[3{0}m█".format(indice_couleur))

Mais, Stack Overflow a tapé à la fenêtre et il m’a dit qu’il était beaucoup plus rapide de créer une liste puis d’en joindre les éléments.
J’ai benchmarké avec mon radio-réveil.
Stack Overflow avait raison.

4) Par contre, ce que Stack Overflow s’était bien gardé de me dire, c’est que l’affichage en console avait ses propres limites internes.
Bilan, après avoir optimisé mon code du mieux que je le pouvais, j’ai constaté que le script calculait de toutes façon les texte_image plus vite que la console ne pouvait les afficher.

Ce qui relève un peu du FAIL quand on y pense.

Donc, si vous avez une idée pour que ça ne scintille plus au delà de 25 lignes de hauteur, je suis preneur, sachant que je suis tout à fait à même d’entendre que j’ai fait n’importe quoi dès le début.

ÉDIT: Dans les commentaires, Tmonjalo a proposé une solution qui résout le problème du scintillement en faisant revenir le curseur en haut à gauche plutôt que d’effacer la console. J’ai donc édité le code en conséquence. Merci à lui.

La totale.

Voici le script final. Il est diffusé sous les termes de la très sérieuse WTFPL.

Les variables à modifier pour faire des tests sont le seuil, la largeurOut et la hauteurOut.

À noter aussi que si vous faite un petit…

cap = cv2.VideoCapture("VotreFilm.avi")

… au lieu d’ouvrir la webcam en /dev/video0, et bien vous allez voir VotreFilm.avi dans la console. Super génial !

#-*- coding: utf-8 -*-
 
import cv2
import os
 
# Définition du flux capturé.
# Comme elle sera, de toutes façons, retaillée à la baisse,
# elle est fixée à la valeur la plus petite supportée par la webcam.
# À noter que cette valeur minimale peut varier en fonction de votre cam.
largeurIn = 160
hauteurIn = 120
 
# Définition du flux qui s'affichera en console.
# À savoir le nombre de caractères en largeur et en hauteur.
largeurOut = 60
hauteurOut = 20
 
# Seuil de présence des couleurs rouge, vert, bleu dans un pixel. 
# Entre 0 et 255.
seuil = 120
 
# Choix du périphérique de capture.
# Ici /dev/video0
cap = cv2.VideoCapture(0)
 
# Configuration de la définition du flux.
cap.set(3, largeurIn)
cap.set(4, hauteurIn)
 
# On efface la console.
os.system('clear')
 
# On définit une position de référence pour le curseur.
# En haut à gauche, donc, puisqu'on vient juste d'effacer la console.
print ('\033[s')
 
# Pendant... tout le temps...
while True:
 
    # On capture une image.
    ret, img = cap.read()
 
    # On retaille l'image capturée.
    img = cv2.resize(img,(largeurOut, hauteurOut))
 
    # On initialise une liste qui contiendra tous les éléments de l'image
    liste_image = []
 
    # Pour chaque ligne de l'image.
    for ligne in range(hauteurOut):
 
        # Pour chaque colonne de chaque ligne.
        for colonne in range(largeurOut):
 
            # On initialise à 0 l'indice du pixel analysé 
            indice_couleur = 0
 
            # On analyse le niveau de bleu du pixel.
            # S'il est au dessus du seuil...
            if img.item(ligne, colonne, 0) > seuil :
 
                #...on ajoute 4 à l'indice.
                indice_couleur += 4
 
            # On analyse le niveau de bleu du pixel.
            # S'il est au dessus du seuil...
            if img.item(ligne, colonne, 1) > seuil :
 
                #...on ajoute 2 à l'indice.
                indice_couleur += 2
 
            # On analyse le niveau de bleu du pixel.
            # S'il est au dessus du seuil...
            if img.item(ligne, colonne, 2) > seuil :
 
                # ...on ajoute 1 à l'indice.
                indice_couleur += 1
 
            # Si l'indice obtenu est différent de 0...
            if indice_couleur:
 
                # ...on ajoute un █ coloré à la liste.
                liste_image.append(u"\033[3{0}m█".format(indice_couleur))
 
            # Si l'indice est égal à 0...
            if not indice_couleur:
 
                # ...on ajoute un espace (noir ?) à la liste.
                liste_image.append(" ")
 
        # On fait en sorte que le terminal retrouve sa couleur initiale
        liste_image.append("\n\033[00m")
 
    # On produit un string en mettant bout à bout tous les éléments de la liste
    texte_image = ''.join(liste_image)
 
    # On affiche l'image.
    print(texte_image)
 
    # On replace le curseur à la position de référence.
    print ('\033[u')
 
    # On attend 40 millisecondes pour obtenir du 25 images par seconde.
    key = cv2.waitKey(40)

Des vidéos ! Des vidéos !

Voici une petite vidéo réalisée pour promouvoir un événement à nous. À partir de la 44ème seconde, on peut m’y voir coiffé d’un masque de soudeur en train de faire tenir au plafond un donut géant au moyen d’une batte de base-ball en aluminium :

Pour plus d’information sur cet événement vous pouvez allez voir ici et admirer, par la même occasion, notre magnifique affiche réalisée en pur Python.

Enfin, compte tenu des tauliers du site, je ne pouvais passer à côté de la figure imposée.
Je vous propose donc un extrait de Deep 3-bit, hommage appuyé à Vuk Cosic et son légendaire Deep ASCII.

flattr this!

Comment fonctionne HTTP ?

mercredi 18 juin 2014 à 03:48

Plus je fais du dev Web, plus je m’aperçois que beaucoup de mes collègues n’ont aucune idée de comment fonctionne HTTP sous le capot. Comme vous le savez, ma règle numéro 1 c’est qu’il n’y a rien d’évident, donc petit tuto pour expliquer les bases.

On ne va pas rentrer dans les petits détails, juste une petite intro histoire de savoir ce qui se passe derrière ce script PHP ou cette application bottle.

Article long, vous connaissez la chanson.

Et puis c’est dans le ton de l’actu ^^

La logique de client / serveur

(Je me repompe moi-même)

Se balader sur le Web, c’est comme aller au resto. On est un client, on demande quelque chose au serveur, le serveur va voir en cuisine, et revient avec la bouffe, ou une explication sur l’absence de la bouffe (mais jamais pourquoi la bouffe est dégueulasse, allez comprendre):

Schéma du protocole HTTP, en gros

Le protocole HTTP, en (très) gros

Ce cycle requête/réponse se déroule des centaines de fois quand on parcours un site Web. Chaque lien cliqué déclenche une requête GET, et la page suivante ne s’affiche que parce qu’on a reçu la réponse contenant le HTML de celle-ci. Les formulaires, les requêtes AJAX, la mise à jour de vos Tweets sur votre téléphone, tout ça fonctionne sur le même principe.

Donc, sur votre site Web, Firefox, Chrome et les autres vont faire une requête à une machine, votre machine contient un code (si vous êtes dev, votre code :) qui va recupérer cette requête et générer une réponse.

Tout est texte

Généralement, les développeurs utilisent des outils sophistiqués pour écrire des sites Web. Si bien que quand on écrit du code, on ne voit pas vraiment la requête et la réponse, mais des fonctions, des objets, du HTML… Comment ça arrive et comment ça repart est géré automatiquement.

Regardons ce qui se passe quand ce n’est PAS géré automatiquement.

Voici le code d’un petit serveur HTTP écrit en Python 3. Il accepte n’importe quelle requête sur le port 7777 et retourne toujours une page avec marqué “Coucou”.

import asyncio
 
# Le texte de la réponse qu'on va renvoyer
COUCOU = b"""HTTP/1.1 200 OK
Date: Fri, 16 Jun 2014 23:59:59 UTC
Content-Type: text/html
 
<html>
<body>
<h1>Coucou</h1>
</body>
</html>"""
 
# La fonction qui gère chaque requête et qui renvoie une réponse
# pour chacune d'entre elles. La même réponse à chaque fois pour cet
# exemple, mais on peut fabriquer une réponse différente si on veut.
def handle_request(request, response):
    # On lit la requête
    data = yield from request.read(1000000)
    # On affiche son contenu. Surprise, c'est que du texte !
    print(data.decode('ascii'))
    # On écrit notre réponse, que du texte aussi !
    response.write(COUCOU)
    # On ferme la connexion : le protocole HTTP est stateless,
    # c'est à dire qu'il n'y a pas de maintien d'un état
    # côté client ou serveur et chaque requête est indépendante
    # de toutes les autres.
    response.close()
 
if __name__ == '__main__':
    # Machinerie pour faire tourner le serveur :
    # Récupération de la boucle d'événements.
    loop = asyncio.get_event_loop()
    # Création du serveur qui écoute sur le port 7777
    # et qui va appeler notre fonction quand il reçoit
    # une requête.
    f = asyncio.start_server(handle_request, port=7777)
    # Installation du serveur dans la boucle d'événements.
    loop.run_until_complete(f)
    # On démarre la boucle d'événement.
    print("Serving on localhost:7777")
    loop.run_forever()
 
# Si vous êtes dev, vous commencez à comprendre combien
# les libs et frameworks qui gèrent tout ce bordel pour
# vous sont fantastiques.

Si on lance le serveur et qu’on visite la page http://localhost:7777 sur un navigateur, on va alors voir ceci dans le terminal où tourne le serveur :

$ python3 server.py
Serving on localhost:7777
GET / HTTP/1.1
Host: 127.0.0.1:7777
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Cache-Control: max-age=0

C’est la requête envoyée par notre client, et reçue par notre serveur. “Aller” sur l’adresse veut dire en fait que votre navigateur envoie ce morceau de texte.

Juste après, mon serveur renvoie aussi du texte. Toujours le même dans notre cas :

HTTP/1.1 200 OK
Date: Fri, 16 Jun 2014 23:59:59 UTC
Content-Type: text/html
 
<html>
<body>
<h1>Coucou</h1>
</body>
</html>

Le navigateur l’interprète, et vous fabrique cette page :

Vous contemplez le Web de 1995

Comme vous pouvez le voir, ce n’est que du texte tout simple qui est reçu et envoyé.

Quand quelqu’un développe un site Web, ce qu’il fait vraiment, c’est ça. La plupart du temps il ne le voit pas car ses outils lui facilitent la tâche en analysant le texte et en lui donnant des fonctions et des objets pour le manipuler. Mais derrière, c’est juste du texte.

Quand quelqu’un surf le Web, ce qu’il fait vraiment, c’est ça. Mais le navigateur se charge de le cacher derrière des jolis contrôles, et affiche un résultat bien plus agréable à regarder.

Structure des requêtes

Le texte d’une requête est divisé en 3 parties :

Prenons notre précédente requête d’exemple :

GET / HTTP/1.1
Host: 127.0.0.1:7777
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Cache-Control: max-age=0

L’action demandée sur la ressource est toujours sur la première ligne :

GET / HTTP/1.1

Elle se divise en 3 parties :

Notez bien que le chemin de la ressource n’a pas à correspondre à un chemin réel d’un fichier sur l’ordinateur. Une ressource est quelque chose de complètement virtuel, quelque chose que mon programme met à disposition selon mes désirs. Si c’est un fichier, très bien, mais ce n’est pas obligatoire, et je peux générer n’importe quelle réponse qui me plait.

En effet, si je vais sur l’adresse http://127.0.0.1:7777/user/monique/profile/, vous notez que mon serveur continue de marcher. Il reçoit simplement la requête :

GET /user/monique/profile/ HTTP/1.1
...

C’est à mon serveur de décider ce qu’il choisit de faire avec le chemin /user/monique/profile/. Ici il est un peu con et continue de renvoyer “coucou”.

Bien, on vient de voir “L’action demandée sur la ressource”, voyons maintenant les headers :

Host: 127.0.0.1:7777
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Cache-Control: max-age=0

Les headers sont des informations supplémentaires que le client fourni à mon serveur sous la forme :

Nom-De-L-Information: contenu

Les sauts de ligne sont très importants. La première ligne de la requête est “L’action demandée sur la ressource”, puis on met un saut de ligne, ensuite vient un header, puis un saut de ligne, puis un header… Chaque header doit tenir sur une ligne.

En HTTP/1.1, seul le header Host est obligatoire, mais comme aucun header n’était obligatoire en HTTP/1.0, beaucoup de serveurs acceptent l’absence de tout header.

Les headers contiennent généralement des informations sur le client (ex: Accept-Language vous dit quelles langues le client accepte), le contenu de la requête (ex: Content-Length indique la taille de la requête) ou demande un comportement du server (Cache-Control précise comment gérer les ressources qu’on peut mettre en cache).

Pour “Le corps de la requête”, il nous faut ajouter du contenu à notre requête.

Pour cela, faisons une page avec un petit formulaire HTML :

<html>
<head>
    <title>Test post</title>
</head>
<body>
<form method="post" action="http://127.0.0.1:7777/">
<p><input type="test" value="coucou, tu veux voir mon POST ?" name="salut">
<input type="submit"></p>
</form>
</body>
</html>

Ce qui nous donne cette page :

Capture d'écran d'un simple formulaire web sous firefox

Techniquement, on peut poster un formulaire avec une requête GET, mais shut...

Si on active le formulaire, notre serveur affiche cette requête :

POST / HTTP/1.1
Host: 127.0.0.1:7777
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 41

salut=coucou%2C+tu+veux+voir+mon+POST+%3F

Déjà, vous notez que le verbe d’action a changé : POST / HTTP/1.1.

Ensuite, à la fin de la requête, on laisse une ligne vide, puis on a de nouveau du texte :

salut=coucou%2C+tu+veux+voir+mon+POST+%3F

C’est le corps de la requête.

C’est comme ça que le serveur reçoit le contenu des données des formulaires, et c’est ce qu’on retrouve dans la variable $_POST en PHP ou request.POST en Django

Encore une fois, les outils de programmation évitent au codeur de travailler avec du charabia, et le transforme en quelque chose de facile à comprendre et à manipuler.

Structure des réponses

C’est pareil, ma bonne dame. Reprenons notre exemple de réponse :

HTTP/1.1 200 OK
Date: Fri, 16 Jun 2014 23:59:59 UTC
Content-Type: text/html
 
<html>
<body>
<h1>Coucou</h1>
</body>
</html>

Première ligne, on précise le protocole, puis le code de réponse, qui stipule la nature de votre réponse (tout va bien, une erreur, une redirection, la page n’existe pas, etc).

Ensuite les headers, puis on saute une ligne, et on met le corps de la réponse. Ici, le code HTML qui va donner notre jolie page “Coucou”.

Tout tient là dedans

Tout le reste, toutes les fonctionnalités fantastiques du Web (les liens hypertextes, les fichiers statiques JS et CSS inclus, les cookies, les redirections, le cache, la compression…) ont pour point d’entrée un de ces 3 éléments de la requête ou de la réponse. C’est que c’est un protocole bien foutu.

Enfin, disons, presque tout ? :)


Télécharger le code de l’article

flattr this!

Alzheimer

mardi 17 juin 2014 à 03:43

Visiblement j’ai raté mon coup. Les gens voient l’article comme anti-microsoft alors que je voulais juste souligner la faciliter à oublier qui sont les ordures en prenant un exemple criant.

Si il y a bien un truc qui me fait rager, c’est la capacité de l’homme à oublier à quel point les gens ont été des connards.

Ça marche en politique, en commerce, dans le monde de l’art, et même dans son entourage proche, avec les relations amicales et amoureuses.

Partout en fait. Si quelqu’un fait une grosse crasse, il pourra toujours attendre, et revenir plus tard. Pouf, lavé. En fait, il vaut mieux faire une grosse saloperie de temps en temps, et avoir une bonne image, que d’être quelqu’un de bien sans communiquer dessus. Pour les boites, mais pour vous en tant que personne aussi évidement.

Prenez par exemple cette interview de Bill Gates.

Je prends Bill Gates comme exemple, hein, il y en a beaucoup comme lui.

Lisez les commentaires sous l’interview, vous allez-voir, c’est atterrant.

Déjà, dois-je souligner le fait que le gars utilise une technique de rhétorique de comptoir pour se mettre de la pommade et que ça passe sans problème.

Dès le début, je trouve ça irritant que le public soit aussi con.

Quand on se traine les casseroles qu’il a et qu’à la question “c’est quoi le mythe le plus ennuyeux à votre propos” il répond “qu’on dise que je suis trop bien, lol”, et que personne ne tique… Merde !

C’est dans tous les livres les plus pourris sur les entretiens d’embauche, la première technique qu’on enseigne pour répondre à la question “quel est votre plus gros point faible”.

Mais passons, ce n’est pas le sujet de l’article.

Le sujet, c’est qu’au cours des années, Microsoft, sous la direction de Billy, nous a pourri la vie avec :

Chacun de ces points mérite à lui seul de ranger Gates dans la catégorie connard, et ne plus lui accorder de crédit. Tous ces points ensembles…

Mais non, apparemment ce n’est pas grave. On peut mépriser ses clients, ses employés, les lois, et avoir l’aval du public. Les faits, tout le monde s’en bat les steaks.

En fait, les gens n’associent pas du tout la compagnie avec l’homme. Deux entités distinctes. Il vient d’où son pognon à William, d’après vous ?

Enfin je dis ça, les gens ont pardonné à Microsoft aussi. Comme ils pardonnent à Apple et comme il pardonneront à Google.

Pourtant, ce n’est pas comme si c’était un petit scandale et puis terminé. Non, ils en remettent une couche régulièrement:

J’arrête là car la liste n’en finit pas, des dizaines de scandales (en plus, là je ne prends qu’un exemple par type de scandale ici, sinon…), plus ou moins gros, par an. Tellement que c’en est lassant.

Pourtant, un seul d’entre eux devrait suffire à les renvoyer dans les cordes. Là, il y a de quoi rempir un manuel d’exercice de conclusions pénales, et ça ne fait rien.

Je crois que passée une certaine quantité de magouilles, y a un switch qui s’active dans le cervau qui amorce un SEP field.

Ça me fait penser à cette blague, où Bush discute avec Bin Laden, et un conseiller en com leur demande ce qu’ils foutent :

  • On fait nos plans. On a décidé de tuer 30 000 américains, 140 000 irakiens et un homosexuel séropositif.
  • Pourquoi un homo ?
  • Tu vois Ousama, je te l’avais dit, tout le monde s’en branle des autres !

Après il y a des gens qui disent “oui mais Microsoft est trop gros, il ne peut pas contrôler tout ça“.

MS a la taille choisie par Bill Gates, il est responsable de ce fait, il l’a décidé, chaque jour. S’il était, à l’époque, assez compétent pour être à la tête de la boite, il est responsable de chacune de ces merdes. S’il n’était pas compétent, on avait un abruti aux commandes d’un tank dans un jardin d’enfants. Dans tous les cas, c’est grave.

Il y a aussi le “oui, mais il a fait beaucoup pour l’informatique“.

Déjà, ça reste à démontrer. Qui me dit que si on supprimais M$ à sa naissance, l’informatique ne se porterait pas mieux ? Improuvable.

Ensuite, Johnson & Johnson ont aussi “fait beaucoup” pour la pharmaceutique, mais je ne leur confierais pas l’hébergement d’un colloque du ministère de la santé. (J’étais parti pour un exemple de rétablissement économique de l’Allemagne en 40, mais j’ai reçu un email de Godwin).

Tout ça pour dire que parce que quelqu’un a (peut-être) fait un peu de bien, ça ne l’absout pas de toutes les merdes qu’il a faites. Sinon, je vous mets une baffe dans la gueule, je vous fais un bisou, et on est quitte ?

Ensuite, il y a l’aspect moral du simple fait qu’il a concentré, pour sa petite personne, pendant des dizaines d’années, des milliards de dollars. Il en donne la moitié aujourd’hui, mais les gens semblent oublier qu’ils les a retirés de l’économie et bloqués. Pendant toute sa vie.

Je ne parle pas du flux d’argent qu’il a fait circuler de l’économie à ses comptes, et de ses comptes à l’économie. Non. Je parle de ce qu’il a accumulé dessus, c’est à dire ce qui est resté là, à attendre.

Et oui, c’est ce que personne ne dit quand on parle des ultra-riches : la monnaie, ce n’est pas juste un nombre ou du papier, c’est la représentation d’une ressource, en grande majorité le travail humain. Quand ce sont quelques millions, l’impact n’est pas énorme. Quand ce sont quelques milliards, ce sont des milliers d’années de travail de personnes qui restent en suspens, pour le plaisir d’un seul homme.

Enfin le plaisir : si il a plusieurs milliards en banque, c’est qu’il n’en dépense pas la majorité. C’est une décoration. Un blason. Et après il a redonné, excusez-moi, la moitié ! On l’admire pour ça ? Il annonce qu’à sa mort, une fois qu’il n’aura plus besoin de son aura sociale (putain, le pin’s à $100 milliards les gars), il va tout refiler, et on en parle comme d’un grand homme.

Donc le gars a créé une boite qui, dans notre système économico-social, lui a permis d’immobiliser l’équivalent du PIB d’un petit pays. Pour rien au final, car cela représente la somme de ce qu’il n’a PAS utilisé pendant toutes ces années. Il a gardé ce pognon pour la déco.

Et on l’applaudit pour ça ?

Quand quelqu’un est riche, il n’a pas de responsabilité aux yeux du public. Les gens l’envient. Ou le respectent. Mais personne ne pense qu’il a le devoir d’assumer les conséquences de ce qu’il fait (ou ne fait pas) avec son argent.

En plus, la grosse blague, c’est que les organisations philanthropiques aux USA ont le droit de gérer un portefeuille d’investissement. Ce qui donne ceci :

Comme la plupart des organisations philanthropiques, la Gates Foundation donne chaque année au moins 5% de son capital pour payer un minimum d’impôts. [...] Les 95% restants sont investis. [...] Et là, on en arrive au point soulevé par les journalistes du LA Times qui se sont aperçus que les entreprises dans lesquelles la fondation possédait des parts et dont les objectifs allaient à l’encontre de ses buts caritatifs représentaient 41 % de ses actifs, soit 8,7 milliards de dollars.

En gros le gars fait du pognon en finançant des boites qui sont responsables de la destruction contre laquelle luttent les associations qu’il prétend aider.

Au passage, en donnant son pognon à la fondation, Bilou s’évite aussi de payer pas mal d’impôts. Argent qu’il réinvestit dans cette structure payant elle-même peu d’impôts qui peut alors investir dans ce qui arrange notre bonhomme. C’est pour aider le monde, ma bonne dame.

Donc Bill Gates est une ordure, et il y a des gens pour le défendre, parce que c’est une ordure socialement acceptable, et très visible. C’est vrai qu’en photo il a l’air super sympa.

Mais ce n’est pas Bill Gates qui m’énerve le plus. Ce n’est qu’un exemple. Il y en a des milliers comme lui.

Ce qui m’énerve le plus, c’est chaque abruti qui oublie.

flattr this!