Sam & Max » http http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 Pourquoi obliger le SSL par défaut est une grosse connerie 39 http://sametmax.com/pourquoi-obliger-le-ssl-par-defaut-est-une-grosse-connerie/ http://sametmax.com/pourquoi-obliger-le-ssl-par-defaut-est-une-grosse-connerie/#comments Wed, 06 May 2015 09:06:13 +0000 http://sametmax.com/?p=16175 Chrome et Mozilla parlent de limiter les fonctionnalités de leurs navigateurs sur les sites qui n’utilisent pas SSL.

Sur le papier, c’est pavé de bonnes intentions : forcer les auteurs de sites à chiffrer la communication entre le serveur et le client. Plus de sécurité pour les utilisateurs, tant pis pour les dev paresseux, pas vrai ?

Sauf que HTTPS fonctionne sur le principe d’autorités centrales. Vous devez obtenir un certificat auprès d’une autorité de confiance, sans quoi le navigateur va afficher ça :

This connection is untrusted

Michou ne va pas lire ce texte, encore moins chercher et trouver comment accéder au site. Elle va courir. Un virus !

Les moteurs de recherche vont vous plomber, l’utilisateur va fuir, bref, votre site est mort.

Et c’est la que ça devient fun, car les autorités centrales sont des organismes privés, et il n’y en a pas beaucoup. Ils ne sont pas tenus de vous filer un certificat, et peuvent le retirer à tout moment.

En clair, forcer SSL partout, c’est donner la main à quelques grosses boites pour décider qui a le droit d’être sur le Web ou pas. Vous voulez faire un truc qui ne leur plait pas ? Attention, montrer un tableau classique avec des nichons ne plait pas à Apple, faire un noeud Tor ne plait pas à Amazon, les bitcoins ne plaisent pas à Paypal et être gay ne plait pas à plein de gouvernements.

Toutes ces entités travaillent main dans la main avec les autorités de certification, et avoir Mozilla en proposer une n’y changera rien. Si vous faites un truc que ces gens ne veulent pas, vous effacer du Web sera aussi facile que de faire pression pour révoquer votre certificat.

Sans jugement. Sans procès. Sans recours possible.

Le Web deviendra un app store.

EDIT :

Je rajoute mon commentaire sur let’s encrypt ici sinon je sens que tout le monde va radoter

L’article a un lien qui pointe vers let’s encrypt. Ca ne change rien : c’est centralisé, privé, et subira des pressions comme tous les autres. J’aime Mozilla, j’ai confiance en eux, mais ils n’ont aucun moyen de garantir la neutralité de let’s encrypt, ne serait que par rapport aux gouvernements.

Et puis quoi ? A la fin tous les sites “border line” seront chez eux ? Comme ça bam, un bon single point of failure à attaquer pour tout faire tomber d’un coup ?

FBI. FBI. FBI.

]]>
http://sametmax.com/pourquoi-obliger-le-ssl-par-defaut-est-une-grosse-connerie/feed/ 39
Le don du mois : Python requests 4 http://sametmax.com/le-don-du-mois-python-requests/ http://sametmax.com/le-don-du-mois-python-requests/#comments Wed, 04 Feb 2015 11:55:58 +0000 http://sametmax.com/?p=15846 Il n’y aucune cohérence dans ma manière de donner. Je le fais au fil de l’eau, anarchiquement. Parfois j’oublie pendant des mois et des mois.

Là, je me baladais sur l’excellente documentation de la non moins excellente lib requests, à la recherche du one-liner qui allait, encore une fois, me faire gagner un bon quart d’heure.

Et j’ai vu un bouton “Buy request pro”.

Curieux. Il y a plus mieux que le meilleur de requests ?

Je clique, et en fait le bouton est juste là pour proposer de supporter requests. On “achète” la lib gratuite :)

Le minimum est $12, ce qui est fort raisonnable considérant que ce petit bout de code m’a sauvé la mise un million de fois :

  • Pas besoin de se soucier de l’encoding du payload.
  • Traitement élégant des headers.
  • Api super simple et propre.
  • Doc excellente.
  • Ergonomie mettant les cas les plus courants à portée de main.
  • Automatisation des choses importantes comme les redirections ou les cookies.
  • Fonctions avancées pour les cas compliqués comme la vérification SSL.
  • Extensibilité.
  • Un claque pédagogique qui a influencé toute la communauté Python.

Bref, 10 euros, c’est le prix de mon amour en février. Je suis un mec facile.

]]>
http://sametmax.com/le-don-du-mois-python-requests/feed/ 4
Utiliser requests de manière non bloquante facilement 12 http://sametmax.com/utiliser-requests-de-maniere-non-bloquante-facilement/ http://sametmax.com/utiliser-requests-de-maniere-non-bloquante-facilement/#comments Wed, 21 Jan 2015 07:17:41 +0000 http://sametmax.com/?p=15797 En attendant le dossier sur la programmation non bloquante, voici une petite lib qui résout un cas d’école : faire une requête HTTP sans bloquer avec une jolie API, en pur Python.

Pour ça, on dégaine pip et installe requests-futures, un plugin pour la célèbre lib requests qui fonctionne avec Python 2 et 3 :

pip install requests-futures

requests-futures va créer pour vous une pool de workers (2 par défaut) et quand vous faites une requête, la lib vous retourne un objet future qui vous permet d’attacher un callback.

Fiou, le nombre de liens référant à d’autres articles du blog est en train d’exploser.

Exemple :

import time
from requests_futures.sessions import FuturesSession
 
# Cette session est notre point d'entrée, c'est elle
# qui gère nos workers. Faites help(FuturesSession)
# pour voir ses paramètres.
session = FuturesSession()
 
# Les URLs sur lesquelles on va faire
# nos requêtes
URLs = [
    "http://sametmax.com",
    "http://sebsauvage.net",
    "http://indexerror.net",
    "http://afpy.org",
    "http://0bin.net"
]
 
# Notre callback qui sera appelé quand une 
# des requêtes sera terminée. Il reçoit
# l'objet future pour seul paramètre
def faire_un_truc_avec_le_resultat(future):
    # On est juste intéressé par le résutlat, qui
    # est un objet response typique de la lib
    # request
    response = future.result()
    print(response.url, response.status_code)
 
# On traite chaque URL. Comme on a 2 workers,
# on pourra traiter au mieux 2 URLs en parallèle,
# mais toujours sans bloquer le programme
# principal
for url in URLs:
    # On fait notre requête GET
    future = session.get(url)
    # On rajoute le callback à appeler quand
    # le résultat de la requête arrive.
    # La flemme de faire la gestion des erreurs.
    future.add_done_callback(faire_un_truc_avec_le_resultat)
 
# Juste pour montrer que c'est bien non bloquant
for x in range(10):
    print(x)
    time.sleep(1)

Output :

0
1
(u'http://sebsauvage.net/', 200)
(u'http://sametmax.com/', 200)
2
(u'http://indexerror.net/', 200)
(u'http://0bin.net/', 200)
(u'http://www.afpy.org/', 200)
3
4
5
6
7
8
9

On remerciera turgon37 pour sa question sur IndexError qui m’a amené à écrire cet article.

]]>
http://sametmax.com/utiliser-requests-de-maniere-non-bloquante-facilement/feed/ 12
Qu’est-ce que les websockets et à quoi ça sert ? 8 http://sametmax.com/quest-ce-que-les-websockets-et-a-quoi-ca-sert/ http://sametmax.com/quest-ce-que-les-websockets-et-a-quoi-ca-sert/#comments Tue, 30 Dec 2014 04:51:57 +0000 http://sametmax.com/?p=15615 Le protocole WebSocket vise à développer un canal de communication full-duplex sur un socket TCP. LOL. C'est clair non ? Vous inquiétez pas, tonton Sam est là.]]>

Le protocole WebSocket vise à développer un canal de communication full-duplex sur un socket TCP.

LOL. C’est clair non ?

Vous inquiétez pas, tonton Sam est là.

Le Web a évolué. On est passé de Gopher a HTTP 1 puis 1.1. Et on a eu AJAX pour rafraîchir la page sans tout recharger.

Et maintenant on a des apps complètes qui font des centaines de requêtes au serveur alors même que l’utilisateur ne change pas de page. D’ailleurs, je parie que plein de gens ne savent même plus ce qu’est une page…

Le problème c’est qu’AJAX, c’est toujours HTTP, et HTTP est sans état (stateless) : il ne garde aucune information en mémoire d’une requête à l’autre. Ça a des avantages, mais cela implique qu’à chaque requête, il faut ouvrir une connexion et la refermer. Ce qui bouffe quelques ms à chaque fois, et d’autant plus si on utilise SSL.

Une autre limite, c’est que le serveur ne peut pas envoyer de données au client (ici le navigateur) si le client ne fait pas une requête au préalable. Du coup, pour savoir si il y a quelque chose de nouveau, le navigateur doit régulièrement faire des requêtes au serveur ou utiliser des gros hacks comme le long polling.

Les websockets (c’est un abus de langage, on devrait parler du protocole Websocket) ont été créés pour répondre à ces besoins : elles permettent d’ouvrir une connexion permanente entre le navigateur et le serveur. Ainsi, chaque requête est plus rapide, et plus légère. En prime, le serveur peut envoyer des requêtes au navigateur pour le prévenir qu’il y a du nouveau.

Ceci permet de faire tout ce que permettait de faire AJAX mais en plus rapide, et en plus léger. Et également d’envoyer des notifications (ce contenu a changé, un message est arrivé, l’autre joueur a fait cette action…) au navigateur au moment où l’événement se produit.

En gros, de faire des apps Web quasi temps réel.

Il existe d’autre technos pour faire cela : applets Java, flash, comet, server sent events…

Mais aucune n’ont décollé. Websocket est donc aujourd’hui la solution de facto.

Caractéristiques

Le protocole Websocket utilise l’abréviation ws et wss si SSL, les URLs vers des endpoints websocket ressemblent donc à : ws://domaine.tld/chemin/vers/truc/.

Intelligemment, il utilise un handshake compatible avec celui de HTTP, permettant à un serveur de gérer les deux sur les mêmes ports. Donc on peut faire du Websocket sur le port 80 et 443. Néanmoins, certains proxy se gourent quand ils voient du websocket non chiffré et gauffrent votre connexion en la traitant comme du HTTP. Donc si vous voulez une app solide, investissez dans un certif SSL.

Tout ça fonctionne à partir de IE10. Notez comme IE est devenu le standard de ce qui ce fait de moins bien à tel point que je n’ai même pas besoin de vous parler des autres, vous savez que ça marche. Il existe en plus des plugins flash pour simuler des websockets sur les navigateurs anciens, c’est à dire les encore plus vieux IE.

Par défaut, les websockets permettent de faire de requêtes crossdomain, contrairement à AJAX. Avec les nouvelles apps qui utilisent NodeJS en local (comme popcorntime) on peut imaginer une nouvelle type d’attaque : une page web qui se connecte à un serveur websocket local de votre machine. Comme les websockets sont souvent utilisées pour du RPC, il y a du potentiel.

Bon, ta gueule, et montre le code

Vous noterez que ce qui prend du temps dans l’exemple c’est la connexion, qu’on ne fait qu’une fois. Ensuite l’échange de données est super rapide.

Ceci est un exemple Javascript, mais un client websocket n’est pas forcément un navigateur. En fait, c’est très précisément le cas avec WAMP, dont les clients peuvent être des programmes Python, Objective C, Java, C++, etc. L’avantage de WAMP, c’est qu’il automatise toute la machinerie pour découper la logique de son programme en divers fonctions et services, plutôt que d’avoir à tout faire à la main avec send() et onmessage().

Dans tous les cas, il vous faudra un serveur qui supporte les Websockets pour l’utiliser. En Python, c’est Tornado ou Twisted (sur lequel est basé le serveur WAMP crossbar). En Javascript, c’est NodeJS. Quoi qu’il en soit, il vous faut un logiciel qui gère l’IO de manière non bloquante, car il y a de nombreuses connexions ouvertes en simultanées, si on veut que ça soit performant.

]]>
http://sametmax.com/quest-ce-que-les-websockets-et-a-quoi-ca-sert/feed/ 8
Bridge HTTP/WAMP 5 http://sametmax.com/bridge-httpwamp/ http://sametmax.com/bridge-httpwamp/#comments Mon, 29 Dec 2014 10:07:59 +0000 http://sametmax.com/?p=13072 Il y a 4 gros freins à l’adoption de WAMP :

  1. L’incompréhension de la techno. Les gens ne voient pas à quoi ça sert, ce qu’ils peuvent faire avec, et ont peur. Logique. Je vais pas les blâmer, c’est pareil avec toute nouvelle techo. S’investir dans un nouveau truc a un coût, surtout un truc jeune.
  2. La doc. je vais travailler sur la question, mais c’est un travail de fond.
  3. L’API : ça va avec les deux points plus haut. Il faut quelque chose de plus haut niveau. L’API flaskesque est un bon début, il faut maintenant continuer dans ce sens.
  4. L’intégration avec les anciennes technos. Par exemple, un site Django. Personne n’a envie de mettre toute son ancienne stack à la poubelle.

Toute les libs qui introduisent une nouvelle façon de travailler rencontrent ce problème. Quand les ORM sont sortis, c’était pareil. Quand les Django et Rails, et Symfony sont sortis, c’était pareil.

Mais puisqu’on le sait, on peut agir.

Les 3 premiers points ont déjà un début de solution, ce qui nous intéresse c’est donc le 4ème point.

Il y a de nombreuses choses à faire pour l’intégration : authentification, actions bloquantes, communications…

On ne peut pas tout résoudre d’un coup, mais une solution qui ratisse large serait de créer un bridge HTTP/WAMP.

Le principe : faire un client WAMP qui soit aussi client HTTP avec une API REST.

Fonctionnement

En envoyant des requêtes HTTP avec un webtoken pour s’authentifier, on peut faire un register/subscribe/call/publish sur le bridge en spécifiant une URL de callback. Le bridge transmet tout ça à routeur WAMP. Quand un événement arrive sur le bridge qui concerne une des URLs de callback, il fait une requête sur l’URL avec les infos arrivées via WAMP.

API :

POST /register/

{
    // token d'authentification
    token: "fdjsklfqsdjm",
    // On enregistre des urls de callbacks pour chaque "function" exposées en RPC
    // Quand le bridge reçoit un appel RPC via WAMP, il fera une requête POST
    // sur la bonne URL. Votre app récupère les données via POST, et retourne
    // du JSON que le bridge va transmettre via WAMP.
    endpoints: {
        "nom_fonction_1":  "http://localhost:8080/wamp/rpc/nom_fonction_1/",
        "nom_fonction_2":  "http://localhost:8080/wamp/rpc/nom_fonction_2/"
    }
}

POST /subscribe/

{
    token: "fdjsklfqsdjm",
    // On enregistre des urls de callbacks chaque abonnement à un topic.
    // Quand le bridge reçoit un message PUB via WAMP, il fera une requête POST
    // sur la bonne URL. Votre app récupère les données via POST.
    endpoints: {
        "nom_fonction_1":  "http://localhost:8080/wamp/pub/nom_fonction_1/",
        "nom_fonction_2":  "http://localhost:8080/wamp/pub/nom_fonction_2/"
    }
}

POST /call/nom_fonction/

{
    token: "fdjsklfqsdjm",
    // Le bridge fera l'appel RPC, récupère la valeur de retour, et la renvoie
    // à cette URL via POST ou une erreur 500 en cas d'exception.
    callback: "http://localhost:8080/wamp/callback/nom_fonction",
    // Les params à passer à l'appel RPC
    params: ['param1', 'param2']
}

POST /publish/nom_sujet/

{
    token: "fdjsklfqsdjm",
    // Le bridge fera la publication WAMP
    // Les params à passer lors de la publication
    params: ['param1', 'param2']
}

On peut rajouter des fioritures : recharger le fichier de config qui contient les API keys, se désabonner, désinscrire un callback RPC, etc.

Bien entendu, du fait d’avoir un intermédiaire, c’est peu performant, mais suffisant pour permettre une communication entre des apps existantes et vos apps WAMP et mettre un pied dedans.

Intégration poussée

Ceci permet une intégration générique, de telle sorte que tout le monde puisse intégrer son app facilement avec quelques requêtes HTTP. Néanmoins, avoir un plugin pour son framework plug and play faciliterait la vie de beaucoup de gens.

Donc la partie 2, c’est de faire des apps pour les frameworks les plus courants qui wrappent tout ça.

Par exemple, pour Django, ça permettrait de faire :

settings.py

WAMP_BRIDGE_URL = "http://localhost:8181/"

urls.py

from wamp_bridge.adapters.django import dispatcher
 
urlpatterns += ('',
    url('/wamp/, dispatcher),
    ...
)

views.py

from wamp_bridge.adapters.django import publish, call, rpc, sub
 
@rpc()
def nom_fonction_1(val1, val2):
    # faire un truc
 
@sub()
def nom_topic_1(val1, val2):
    # faire un truc
 
def vue_normale(request):
    publish('sujet', ['arg1'])
    resultat = call('function', ['arg1'])

Ca répond pas à des questions du genre : “comment je fais pour garder mon authentification Django” mais c’est déjà super glucose.

Implémentation

On peut créer un bridge dans plein de langages, mais je pense que Python est le plus adapté.

Les clients JS utiliseraientt de toute façon nodeJS, qui n’a pas besoin de bridge, puisque déjà asynchrone. Un routeur en C demanderait de la compilation. PHP est trop moche. Java, c’est tout un bordel à setuper à chaque fois. C#, si il faut se taper Mono sous Linux…

En prime, le routeur crossbar est déjà en Python, donc a déjà tout sous la main pour le faire. On peut même penser à l’intégrer à crossbar plus tard histoire que ce soit batteries included.

Il faut une implémentation Python 2 et Python 3, donc une avec Twisted, et une avec asyncio.

Pour le client twisted, on peut fusionner le client WAMP ordinaire avec treq.

Pour asyncio, il y a aiohttp qui fait client et serveur.

On met les clés API dans un fichier de conf ou une variable d’env, une auth via token, et yala.

C’est le genre de projet intéressant car pas trop gros, mais suffisamment complexe pour être un challenge surtout qu’il faudra des tests unitaires partout, de la doc, bref un truc propre.

Je laisse les specs là, des fois qu’il y ait quelqu’un qui ait des envies de code cet hiver et cherche un projet open source dans lequel se lancer.

Si on se sent un peut foufou, on peut même transformer ça en bridget HTTP <=> anything, avec des backends pour ce qu’on veut : IRC, XMPP, Redis, Trigger Happy…

]]>
http://sametmax.com/bridge-httpwamp/feed/ 5
Qu’est-ce qu’une API ? 16 http://sametmax.com/quest-ce-quune-api/ http://sametmax.com/quest-ce-quune-api/#comments Sat, 06 Sep 2014 19:13:58 +0000 http://sametmax.com/?p=12184 L’API, pour Application Programming Interface, est la partie du programme qu’on expose officiellement au monde extérieur pour manipuler celui-ci. L’API est au développeur ce que l’UI est à l’utilisateur : de quoi entrer des données et récupérer la sortie d’un traitement.

L’API au sens original

Initialement, une API regroupe un ensemble de fonctions ou méthodes, leurs signatures et ordre d’usage pour obtenir un résultat.

Par exemple, imaginons que je fasse une lib pour botter des culs en Python, bottage.py :

def senerver(moment):
    # ...
 
def botter(cul):
    # ...
 
def main():
    # ...
 
if __name__ == "__main__":
    main()

Je vais l’utiliser ainsi :

from bottage import senerver, botter
 
senerver(now)
botter(le_cul_de_ce_con)

Les deux fonctions senerver() et botter() sont mes points d’entrée pour cette action. Je n’utilise pas main(), qui est un code interne à la lib et ne me regarde pas. Il n’y a rien qui distingue cette fonction des autres dans cet exemple, mais je n’en ai pas besoin pour faire le boulot, c’est ma lib qui l’utilise automatiquement quelque part, je n’ai pas à la connaitre.

Donc leurs noms et leurs paramètres ainsi que leurs types sont l’API de ma lib, ce qui m’est exposé pour l’utiliser.

Si on veut rentrer dans des subtilités, on dira en fait que senerver() et botter() font partie de l’API publique, c’est à dire de ce qui est manipulable par un utilisateur de la lib. A l’inverse, main() fait partie de l’API privée, c’est à dire ce qui est manipulable par les développeurs de la lib. Mais quand on parle d’API sans préciser, on parle de l’API publique.

Changement d’API

En informatique, on peut généralement exposer les choses de plusieurs manières différentes. Je peux changer mon API :

import datetime
 
def senerver(moment=None):
    if not moment:
        moment = datetime.datetime.utcnow()
    # ...
 
def botter(cul):
    # ...
 
def init():
    # ...
 
if __name__ == "__main__":
    init()

Ici, j’ai changé mon API pour rendre le paramètre moment facultatif afin de faciliter la vie des utilisateurs de la libs.

Et là on aborde un point très important du concept : la stabilité d’une API.

Puisque l’API est ce qu’on expose au monde extérieur, le monde extérieur va l’utiliser d’une certaine façon. Si on change cette manière de l’utiliser dans une version suivante, au moment de la mise à jour, on va casser leur code si on ne fait pas attention.

Par exemple, ici je rends un paramètre facultatif : ça ne craint pas grand-chose. Mais si j’avais fait l’inverse ? J’avais un paramètre facultatif, et soudain je le rends obligatoire. Toutes les personnes qui n’ont pas passé le paramètre vont soudain avoir un plantage s’ils passent à la nouvelle version de la lib car l’API a changé.

C’est donc une seconde définition de l’API : l’API est une promesse, un contrat entre l’auteur d’un code et ceux qui vont utiliser ce code. Cette promesse est “voici ce que vous pouvez utiliser sereinement dans votre programme, je ne vais pas tout péter demain”.

Cette promesse est plus ou moins bien respectée selon les projets. Python, par exemple, a un historique exemplaire de stabilité d’API, et n’a cassé la compatibilité qu’une fois, avec Python 3, donnant 10 ans aux développeurs pour s’adapter.

Dans tous les cas, si une lib est beaucoup utilisée et que son développeur a le sens des responsabilités, elle évolue plus doucement. Pour cette raison, il faut faire attention au choix qu’on fait dans le style de son API, sous peine de ne pas pouvoir le changer plus tard.

En effet, on peut tout à faire écrire le même code dans des tas de styles différents. Ainsi, je pourrais botter des culs avec une API orientée objet :

 
class Colere:
 
    @classmethod
    def global_init():
        # ...
 
    def __init__(moment):
        # ...
        self._senerver(moment)
 
    def _senerver():
        # ...
 
    def botter(cul):
        # ...
 
if __name__ == "__main__":
    Colere.global_init()

Mon bottage de cul n’a plus du tout le même goût à l’usage :

from bottage import colere
c = Colere(now)
c.botter(un_aperi_cul)

Mon programme fait la même chose, mais mon API est différente. Notez le _senerver() qui est préfixé d’un underscore, une convention en Python pour dire que cette méthode doit être considérée comme ne faisant pas partie de l’API publique, donc à ne pas utiliser depuis l’extérieur. En effet, il n’y a pas de méthode privée en Python.

Qualités d’une API

On a vu que la stabilité était une qualité importante d’une API. Mais il y en a d’autres. Notamment la performance et l’ergonomie, généralement deux notions qui s’affrontent.

Pour l’ergonomie, il s’agit de rendre facile les usages qu’on fait le plus couramment, et rendre possible les usages les plus ardus. Prenez l’exemple d’une requête HTTP avec paramètre POST sur un site qui a besoin de cookies d’une requête précédente. Pas un usage incroyablement complexe a priori…

Avec la stdlib de Python, ça donne ça :

import urllib
import urllib2
import cookielib
 
cookie_jar = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie_jar))
urllib2.install_opener(opener)
urllib2.urlopen('http://site.com')
 
data = urllib.urlencode({"nom": "valeur"})
rsp = urllib2.urlopen('http://site.com/autre/page', data)
print(rsp.read())

La même chose avec la lib requests :

import requests
 
request.get('http://site.com')
res = request.post('http://site.com/autre/page', data={"nom": "valeur"})
print(res.content)

Il ne s’agit pas juste du fait qu’il y ait beaucoup moins de lignes pour le faire. La facilité à découvrir comment faire dans le deuxième cas est exemplaire : en lisant, on comprend le code. En bidouillant dans le shell, on peut sans doute trouver tout ça. On n’a pas besoin de se poser la question de ce qu’est une jar, l’url encoding, etc.

Le premier exemple est non seulement verbeux, mais très, très difficile à trouver soi-même. En fait, même avec la doc sous les yeux, ce n’est pas évident d’arriver à ce résultat, et on y arrivera après des essais douloureux.

Le deuxième exemple est plus ergonomique que le premier.

Mais l’ergonomie a généralement un coût : celui de la performance.

Imaginez que j’ai la lib de bottage de fion sous forme fonctionnelle :

import datetime
 
def senerver(moment=None):
    if not moment:
        moment = datetime.datetime.utcnow()
    # ...
 
def botter(cul):
    # ...
 
def init():
    # ...
 
if __name__ == "__main__":
    init()

Je veux la rendre plus ergonomique. Je sais qu’il faut obligatoirement s’énerver pour botter un cul, et je décide donc de cacher cette fonction et l’appeler automatiquement :

import moment
 
def _senerver(moment=None):
    if not moment:
        moment = datetime.datetime.utcnow()
    # ...
 
def botter(cul, moment=None):
    _senerver(moment)
    # ...
 
def init():
    # ...
 
if __name__ == "__main__":
    init()

Dans la plupart des cas, ça va aider mon public :

Au lieu de devoir savoir qu’il faut s’énerver avant de botter, ils ont juste à botter :

from bottage import botter
botter(cul_de_jatte)

J’ai identifié que c’était l’usage le plus courant, donc c’est une amélioration. Mais ça a un prix pour une petite partie de mes utilisateurs : les très gros botteurs de cul. Ceux qui bottent des culs par centaine.

Avant, ils pouvaient faire :

from bottage import senerver, botter
senerver()
for cul in rang_doigons:
    botter(cul)

Mais maintenant, faisant :

from bottage import botter
for cul in rang_doigons:
    botter(cul)

Ils ont un appel à _senerver() à chaque tour de boucle, et donc un appel à datetime.utcnow() aussi !

Bien entendu, il est possible de remédier à cette situation, mais cet article n’est pas là pour vous expliquer comment créer une belle API. Ce serait néanmoins un très bon article.

Ici, je vous montre simplement qu’en facilitant, on suppose d’un usage, et ça peut se faire au détriment des autres. L’automatisme a tendance à retirer de la marge de manœuvre.

Une bonne API va donc proposer un moyen automatique de faire les opérations de tous les jours, va lui donner une forme (nom, ordre des actions, organisation, etc) ET un moyen de faire des choses compliquées ou performantes. Ce qui va rendre l’API plus riche, donc plus lourde, avec une plus grosse doc, etc.

Tout a un coût.

API Web

Jusqu’ici vous avez vu l’API d’une bibliothèque, mais il existe d’autres genres d’API. L’un est devenu particulièrement populaire depuis le milieu des années 2000 : l’API Web.

L’API Web est comme l’API précédente ce qui est exposé à l’extérieur pour manipuler un programme. Entrées. Sorties. Mais il y a plusieurs différences :

  • Ce n’est pas une lib qu’on manipule, c’est en général un service complet.
  • On n’utilise pas le langage dudit code pour le manipuler, mais un protocole Web.
  • Les appels passent par le réseau.

Il existe de nombreux protocoles qui permettent de faire une API Web : SOAP, REST, XML RPC, WAMP etc.

Aujourd’hui, les API Web les plus populaires utilisent majoritairement un protocole pseudo-REST (techniquement REST est plus une archi qu’un protocole, mais fuck) avec en encoding JSON.

Hum, je vous vois sourciller.

Oui, c’est clair que la phrase est un peu tordue du cul, comme si elle avait été bottée.

Prenons donc un exemple : un service Web de bottage de cul !

Vous êtes donc asskicker.io, leader mondial du bottage de cul en ligne. Et vous exposez votre processus de bottage de cul exclusif à tous les programmeurs.

Pour ce faire, vous mettez à disposition une API WEB sous forme pseudo REST. Au lieu d’appeler des méthodes, les développeurs vont envoyer des requêtes HTTP Get et Post à des URLs représentant les culs à botter.

Je ne vais pas rentrer dans les détails de ce qu’est du (pseudo) REST ou JSON exactement, mais un exemple de requêtes à faire pour botter des culs via notre API Web serait :

import json
import requests
 
# On fait une requête GET vers l'URL du service pour obtenir de quoi s’énerver
colere = requests.get('http://asskicker.io/colere/')
 
# Je créer un nouveau bottage de cul
data = json.dumps({'cul': 'de bouteille', 'colere': colere['id']})
headers = {'content-type': 'application/json'}
res = requests.post('http://asskicker.io/bottage/', data=data, headers=headers)

Et supposons qu’on veuille connaître le dernier bottage de cul fait :

res = requests.get('http://asskicker.io/bottage/last')
print(res.json()) # et la réponse JSON du service :
# {
#     "bottage": 89080,
#     "cul": "de bouteille",
#     "colere": 99943,
#     "date": "2014-09-06 20:38:11"
# }

Les URLs sont fictives, complètement inventées, et ne correspondent à rien.

Ici, notre API est donc la collections d’URLs (http://asskicker.io/bottage/, http://asskicker.io/bottage/last, etc.) qui permet de manipuler notre service, ainsi que les noms et types des paramètres à envoyer via data et le contenu de la réponse.

Le but de l’API Web est de permettre de manipuler du code sur une machine distante à travers le Web, depuis n’importe quel langage capable d’envoyer une requête HTTP. L’API Twitter permet de de lister des tweets et en envoyer. Par exemple, si on est authentifié, faire une requête GET sur http://api.twitter.com/1.1statuses/show/787998 permet d’obtenir en retour un JSON contenant les informations sur le tweet numéro 787998

L’API Google permet de faire des recherches. L’API flicker permet d’uploader des photos. Toutes les APIS ont des formes différentes, certaines sont plus ou moins faciles, plus ou moins efficaces, utilisent tels ou tels formats, mais au final, c’est la même chose : un moyen de manipuler le service en faisant des requêtes.

La manière classique de créer un site est de générer le HTML final sur le serveur. Or, comme il est possible d’envoyer des requêtes HTTP depuis une page Web en utilisant Ajax, on voit aujourd’hui des sites codés en Javascript qui vont chercher leurs données sur le serveur via l’API du site. Le navigateur reçoit ainsi un HTML incomplet, et le JS appelle l’API pour reconstruire la page.

Ainsi, on code la logique une seule fois : récupérer les informations, effectuer des actions… Et on utilise l’API pour tout ça, que ce soit pour faire le site Web, ou pour laisser d’autres programmeurs utiliser le site.

Évidemment, une vraie API Web est complexe, possède des problématiques de sécurité, d’authentification… Encore un bon article à écrire.

]]>
http://sametmax.com/quest-ce-quune-api/feed/ 16
Comment fonctionne HTTP ? 12 http://sametmax.com/comment-fonctionne-http/ http://sametmax.com/comment-fonctionne-http/#comments Wed, 18 Jun 2014 01:48:36 +0000 http://sametmax.com/?p=10596 il n'y a rien d'évident, donc petit tuto pour expliquer les bases.]]> 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 :

  • L’action demandée sur la ressource.
  • Les headers.
  • Le corps de la requête

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 :

  • Le verbe d’action : GET, POST, PUT, OPTION, HEAD, etc. C’est ce qu’on veut faire sur les données qu’on demande. Le plus courant sur le Web, c’est GET (lire la donnée) et POST (créer la donnée, qu’on utilise souvent pour les formulaires).
  • Le chemin de la ressource. Où se trouve la ressource à laquelle on souhaite accéder. Ici c’est “/”, la racine, mais ça peut être “/user/monique/profile” par exemple.
  • La version du protocole utilisé. Aujourd’hui tout le monde utilise HTTP en version 1.1, donc ça ne change pas vraiment.

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

]]>
http://sametmax.com/comment-fonctionne-http/feed/ 12
Redirection 307 et 308 9 http://sametmax.com/redirection-307-et-308/ http://sametmax.com/redirection-307-et-308/#comments Mon, 16 Jun 2014 00:20:14 +0000 http://sametmax.com/?p=10575 subir une mise à jour. Oui, LE standard HTTP 1.1. La fameuse RFC2616, qui n'a pas été touchée depuis l'année de sortie de The Matrix (coïncidence ? Je ne pense pas...). Bref, qu'est-ce qui change ?]]> Le standard HTTP 1.1 vient de subir une mise à jour.

Oui, LE standard HTTP 1.1. La fameuse RFC2616, qui n’a pas été touchée depuis l’année de sortie de The Matrix (coïncidence ? Je ne pense pas…).

Bref, qu’est-ce qui change ?

Essentiellement la forme, qui permettra à la RFC de HTTP 2.0 de faire référence à des parties de celle de la HTTP 1.1, maintenant divisée en 8, et évitera de se répéter.

Le web ne va donc pas casser.

Mais il y a quand même intronisation officielle de la redirection 308, qui est à la 301 ce que la 307 est à la 302.

Je me doute que ça vous parle autant qu’un personnage de film de Charlie Chaplin en fond d’écran d’un Windows avec driver realtek.

Pour rappel, quand un serveur HTTP répond à un client, il lui renvoie un code pour indiquer la nature de sa réponse. Quand tout va bien, c’est 200, quand la page n’est pas trouvée, c’est le fameux 404, quand le serveur est en erreur, c’est une 50x, etc.

Quand le serveur renvoie un code 30x, c’est généralement une redirection : on dit au client, ce que tu cherches est ailleurs, et le client, recevant ce code, fait donc une seconde requête, sur la nouvelle adresse.

Le code 301 indique une redirection permanente, ce qui signifie que le client peut considérer que c’est la nouvelle adresse qui remplace l’ancienne, pour toujours.

Le code 302 indique une redirection temporaire, généralement on utilise ça pour orienter le client sur le site à la suite d’une action, comme la saisie d’un formulaire.

Mais ces deux codes ont une particularité : le client effectue une simple requête GET vers la nouvelle adresse. Si vous avez fait une requête complexe avant, par exemple une requête POST avec plein de paramètres, le client va se rediriger avec une nouvelle requête GET basique, toute l’information précédente est perdue.

Cela peut être ce que l’on veut. Mais parfois, on souhaite que le client fasse la redirection avec une copie exacte de la requête initiale.

C’est ce que fait le code 307 : une redirection temporaire, mais avec la même requêtes. C’est comme la 302, donc, mais répète la même requête deux fois.

Le code 308 demande aussi une redirection en répétant la requête, mais une redirection permanente, comme la 301.

C’est ce dernier qui a été ajouté officiellement au protocole, puisqu’il est déjà supporté par la plupart des navigateurs : même IE le comprends depuis 2008.

(le support est vraiment aléatoire, voir comments)

Par curiosité, vous pouvez tester la redirection ici.

]]>
http://sametmax.com/redirection-307-et-308/feed/ 9
Le potentiel de WAMP, autobahn et crossbar.io 27 http://sametmax.com/le-potentiel-de-wamp-autobahn-et-crossbar-io/ http://sametmax.com/le-potentiel-de-wamp-autobahn-et-crossbar-io/#comments Sun, 01 Jun 2014 10:09:32 +0000 http://sametmax.com/?p=10380 crossbar et autobahn. Mais ça me tue de ne pas voir plus de monde exploiter cette techno.]]> Je sais, je sais, je vous fais chier avec crossbar et autobahn.

Mais ça me tue de ne pas voir plus de monde exploiter cette techno.

Pendant que Max fait la sieste, j’ai pris mon stylo et j’ai fait la liste des besoins d’une app Web actuelle. Quels sont les composants qu’on utilise presque systématiquement, mais en agrégeant divers bouts de trucs à droite et à gauche ?

Ensuite j’ai regardé les possibilités des outils WAMP :

  • PUB/SUB et RPC.
  • Asynchrone.
  • Gestionnaire de process intégré.
  • Serveur stand alone qui n’a pas besoin d’un proxy pour être en prod.

M’inspirant de cela, et du travail que je suis en train de faire avec l’équipe de Tavendo pour faire une API flaskesque pour autobahn, j’ai prototypé une API d’un framework Web qu’on pourrait coder au dessus de cette techno.

Voilà ce que ça donne…

Une API qui mélange flask et nodejs pour le Web

app = Application('YourProjectName')
 
# Envoyer et recevoir des requêtes HTTP
@app.http.post(r'/form')
def _(req, res):
    res.json({'data': 'pouet'})
 
@app.http.get(r'/user/:id/')
def _(req, res):
    res.render('index.html', {'data': 'pouet'})
 
# Servir des fichiers statiques
@app.http.serve('uri', '/path/to/dir', [allow_index])
 
app.run()

Comme c’est asynchrone, on a de très bonnes perfs. Comme c’est basé sur Twisted, on a pas besoin d’un serveur wsgi (gunicorn, uwsgi, etc) ni d’un proxy (nginx) devant. On peut le mettre en prod tel quel.

Parti de ce principe, on peut ajouter la gestion du PUB/SUB et du RPC pour WAMP :

# Callback attendant l'événement
@app.wamp.event('auth.signedin')
def _(ctx, a, b, c):
    pass
 
# déclenchement de l'événément
app.wamp.pub('auth.signedin')
 
# Déclaration du fonnction appelable à distance
@app.wamp.remote('auth.signin')
def _(ctx, a, b, c):
    pass
 
# appel de la fonnction
app.wamp.call('auth.signin')

On est souvent perdu quand on fait de l’asynchrone pour la première fois avec Python car on ne sait pas comment lancer du code après .run(). On peut régler la question proposant des hooks pour les instants clés de l’app.

# Callback à lancer quand l'app est prête
@app.on('app.ready')
def _(ctx, args):
    pass
 
# Signalement que l'app est prête (fait automatiquement en interne
# pour les moments les plus importants)
app.emit('app.ready')

Et tant qu’on y est, puisqu’on a une event loop, profitons en pour proposer du CRON intégré à l’app. C’est moins chiant à déployer qu’un script CRON, c’est cross plateforme, et on a accès facilement à toute sa stack.

# Lancer du code tous les x temps ou a une date précise
@app.cron(every=seconds)
@app.cron(every=timedelta, overlap=False)
@app.cron(hour=7, minute=30, day_of_week=1)
@app.cron(when=datetime)
def _(ctx, args):
    pass

Pourquoi s’arrêter là ? Event loop + message passing + safe queues + workers = tasks queues !

# Créer une file d'attente
queue = @app.queue('name', [workers], [result_backend])
 
# Callback appelé par un worker quand il depop ce 
# message dans la file
@queue.task('encode.video')
def _(ctx, data):
    pass
 
# Envoie d'une tache dans la queu
queue.append('encode.video', data)

Comme on utilise Twisted, on a accès à une chiée de protocoles, et on peut aussi créer les siens. On peut donc imaginer un système de plugins qui rajoute des protocoles supportés :

app = Application('YourProjectName')
app.plug('lib.ajoutant.sms', [namespace])

Si on en a beaucoup et que le namespace nous convient :

app = Application('YourProjectName', plugins=('lib1', 'lib2', 'etc'))

Exemples de plugins possibles :

# Recevoir et envoyer des SMS (via un service type twilio, une gateway kannel ou
# un modem physique)
@app.sms.receive(r'LOVE \w+ \w+')
def _(ctx, args):
    pass
app.sms.send('test', [contact])
 
 
# Envoyer et recevoir des emails (via un server SMTP ou IMAP)
@app.email.receive(src=r'.*@sametmax.com', dest=r'spam.*@*.')
def _(ctx, args):
    pass
app.email.send('test', [contact, title, attachments])
 
 
# techniquement n'importe quel service de message pour lequel on peut écrire
# un backend
@app.tweet.receive(r'Chat')
@app.fb.receive(r'Like')
@app.instagram.receive(r'Bouffe')
@app.irc.message(r'dtc')
def _(ctx, args):
    pass

Le problème des apps centrées sur un objet, c’est qu’elles ont souvent un design monolithique. Ce n’est pas un problème du concept d’app, c’est juste que les auteurs ont pensé “point d’entrée”, et pas “élément composable”.

Si besoin, on doit pouvoir composer une app via plusieurs sous-app :

app = Application()
app.embed('autre.app')

ou

app = Application(embed=['app1', 'app2', 'app3'])

Il faut des hooks pour overrider la configuration, mais vous avez compris le principe.

Un autre problème avec les plateformes comme NodeJS, c’est qu’il est difficile d’utiliser plusieurs coeurs. C’est une des raisons du succès de Go.

Or, Crossbar encourage la division en plusieurs process qui communiquent entre eux (un peu comme les channels). Créons aussi une API pour ça :

p1 = app.process()
p2 = app.process()
 
# Déclarer et appeler une procédure dans process 1
@p1.wamp.remote('auth.signin')
def _(ctx, args):
    pass
 
# Déclarer et appeler une procédure dans process 2
@p2.wamp.event('auth.signedin')
def _(ctx, args):
    pass

Ainsi on profite enfin de plusieurs CPU. La même chose en plus facile à changer:

# Déclarer et appeler une procédure
@app.wamp.remote('auth.signin')
def _(ctx, args):
    pass
 
# Déclarer et appeler une procédure
@app.wamp.event('auth.signedin')
def _(ctx, args):
    pass
 
app.processes({
    1: ['wamp.remote:auth.signin']
    2: ['wamp.event:auth.signedin']
})

En bonus, on fait la nique au GIL.

Mieux, on peut bouger ses process sur plusieurs machines :

Machine 1 (routeur):

router = Application(endpoint="0.0.0.0:8080")
router.run()

Machine 2 (authentification):

# IP du router
auth = Application('auth', connect_to="182.64.1.15:8080")
 
# Nommage automatique en fonction du nom de la fonction
# et de l'app, avec possibilité d'annuler ou overrider le prefix.
# Ici du coup la fonction s'appellera en RPC via 'auth.signin'
@auth.wamp.remote()
def signin(ctx, args):
    pass
 
auth.run()

Machine 3 (API REST):

web = Application('site', connect_to="182.64.1.15:8080")
 
@web.http.post(r'api/auth/')
def _(req, res):
    user = yield res.wamp.call('auth.signin',
                               req.POST['username'],
                               req.POST['password'])*
    if user
        user = yield res.wamp.pub('auth.signedin', user.userid)
        res.json({'token': user.token})
    else:
        res.json({'error': 'nope'})
 
 
@web.http.get(r'api/stuff/')
def _(req, res):
    res.json(get_stuff())
 
@web.http.serve('uri', '/path/to/dir', [allow_index])
 
web.run()

Et vous savez le plus beau dans tout ça ? En Python on a plein de libs qui sont encore bloquantes. En théorie on ne peut pas les utiliser dans les apps asynchrones. Quand on a toute sa logique métiers dans des classes d’ORM, c’est balot. Mais pas ici ! On met un process avec tous ces appels bloquants, et on les appelle depuis des process non bloquant en RPC de manière asynchrone. Pif, paf, pouf, problème isolé.

Après, libre à son imagination de rajouter des fonctionnalités de confort…

Callback qui sera appelé seulement x fois :

# Déclarer et appeler une procédure
@p1.wamp.event('auth.signedin', options={'limit_calls': x} )
def _(ctx, args):
    pass

Raccourcis pour les opérations courantes :

# Recevoir et envoyer un événement
@app.sub('auth.signin')
def _(ctx, *args):
    # ctx.pub
@app.pub('auth.signedin')
 
# Déclarer et appeler une procédure
@app.proc('auth.signedin')
def _(ctx, args):
    # ctx.call
app.rpc()

Comme je vous l’avais expliqué, crossbar peut gérer le cycle de vie de services externes à votre application au démarrage. Autant exposer cette API programativement :

@app.service(['/urs/bin/nodejs', 'script.js'], [user], [group])

.run(), c’est cool, mais si on veut changer des options via la ligne de commande, faut se taper tout le boulot alors que ça pourrait très bien se générer automatiquement :

@app.cmd_run()

Et si vous faites : python sites.py --debug=true --endpoint=0.0.0.0:5252, ça le prend automatiquement en compte. Y a pas de raison de se faire chier.

En parlant de générer automatiquement des trucs, le fichiers de configs pour les services externes sur lesquels on peut avoir envie de brancher notre app, c’est toujours galère. Autant fournir un exemple de base qui est sûr de toujours marcher, généré avec les paramètres de notre app :

python site.py template centos:nginx
python site.py template ubuntu:upstart
python site.py template bsd:systemd # :D

On peut partir très loin dans le délire “battery included”. Typiquement, on peut fournir des services externes nous même puisque crossbar nous le propose, et coder des versions moins bien, mais compatibles (et suffisantes pour les petits sites), de projets toujours utilses :

  • cache (compatible redis)
  • live settings (compatible etcd) mais avec en prime un event wamp propagé à chaque
  • changement de valeur

  • build (compatible, heu, j’en sais rien) qui s’occupe en tâche de fond de surveiller le >système de fichier et lancer les compilations, les minifications, les copies, les tests unittaires, etc.
  • logging centralisé (compatible sentry).
  • Un bridge WAMP/REST qui permet d’envoyer et recevoir des events WAMP sur votre app Django ou flask en utilisant HTTP.

On plug tout ça a une admin Web.

J’espère que je vous ai donné maintenant l’envie de vous plonger un peu plus dans cette techno, et peut être coder quelque chose avec.

Il n’y a plus d’excuses pour ne pas avoir de framework web next gen, ultime de la mort qui tue en Python. A part le fait qu’on soit des feignasses.

Ah, merde, on est foutus.

]]>
http://sametmax.com/le-potentiel-de-wamp-autobahn-et-crossbar-io/feed/ 27
Le Web n’est plus HTTP + HTML 15 http://sametmax.com/le-web-nest-plus-http/ http://sametmax.com/le-web-nest-plus-http/#comments Fri, 23 May 2014 05:24:53 +0000 http://sametmax.com/?p=10304 Si vous voulez énerver un blogger technophile, utilisez le mot Web là où vous devriez utiliser le mot Internet et inversement.

Internet, c’est beaucoup plus que le Web. C’est SSH, IMAP, TELNET, DNS, POP, SMTP, FTP, RTSP, NNTP, Bittorent, TOR, Freenet, Bitcoin, et quelques centaines d’autres protocoles qui se parlent.

Jusqu’ici, le Web, c’était juste HTTP. Des ressources Web, sur lesquelles on agissait via une requête textuelle verbalisée (GET, POST, PUT, OPTION, HEAD, etc) et qui retournait une réponse, généralement en forme de HTML.

Ça a un peu évolué, on a eu SSL qui s’est rajouté, et donc HTTPS, et AJAX, qui n’a pas changé le protocole, mais rendu la nature des requêtes un peu différente. Rien qui n’empêche de debugger avec CURL.

Mais c’est bientôt fini tout ça.

Aujourd’hui les nouveaux protocoles utilisés dans le cadre du Web sont en passe de prendre le pouvoir. Bien sûr il y a SPDY et QUIC, mais surtout, il a les protocoles basés sur les websockets type WAMP.ws, mais également les nouvelles capacités P2P promises par WebRTC. Et des apps qui utilisent massivement les données hors ligne, le scripting JS pour des features essentielles, de la video, du son…

Et donc adios, l’époque où vous pouviez juste dégainer requests et parler à un site. Bye bye le state less, le human readable, le cycle requête / réponse.

Le nombre de technologies qu’on doit empiler aujourd’hui pour déployer un site devient énorme : un moteur de recherche, un message broker, un gestionnaire de fil d’attente, un gestionnaire de déploiement, des technos d’isolation…

C’est fini la simplicité. C’est fini la transparence. L’ère du hacker amateur qui pouvait s’occuper d’un peu de tout, touche doucement à sa fin.

Au revoir et merci. Je me suis super amusé.

Et désolé pour les mômes qui arrivent maintenant, vous allez en chier. Mais vous avez aussi plus de possibilités qu’il n’y en a jamais eu. Plus qu’un homme ne peut concevoir. Plus que tous les hommes en fait.

Et RIP HTTP. Ou pas, puisqu’on passe notre temps à faire des APIs REST maintenant, mais aussi car on est en train de récréer un peu tout au dessus d’HTTP. Long live HTTP, alors, le nouveau TCP/IP. Sauf quand on fait du real time. Ou du P2P. Changement de status : “c’est compliqué entre moi et mon navigateur”.

Internet, phagocyté par le Web, sur lequel on reconstruit Internet et même le desktop ?

Je ne crois pas qu’il existe un seul métier qui ait autant changé en 10 ans. J’espère qu’on en laisse pas trop derrière en courant comme des fous en riant les yeux mi-clos. Pourvu qu’il y ait pas trop d’arbres en face. Pourvu qu’on aille pas dans la direction de la falaise.

En tout cas, c’est toujours fun. Je crois que je vais descendre la prochaine pente en roulant sur le côté. Et avoir la tête qui tourne. Vomir. Et dire que c’est la faute de Javascript.

Et recommencer.

]]>
http://sametmax.com/le-web-nest-plus-http/feed/ 15