Patrice Ferlet
Patrice Ferlet
Créateur de ce blog.
Publié le sept. 12, 2014 Temps de lecture: 5 min

Me suivre sur Mastodon

Créer une API en GO avec Gorilla

Bon, on va parler Go ou Golang (faudra me dire un jour quel est le bon nom) (en fait je sais, je vais le dire dans l’article tiens)… Après la naissance de mon outils “idok”, on discutait au boulot de la création d’une API et de savoir quelle techno utiliser. Vous me connaissez… j’ai répondu “Go”. Mais comment on fait ? Y’a des packages qui aident ? Hooo oui…

Bon en premier, on règle la question de suite:

  • Go = la technologie, c’est le compilo, les outils…
  • Golang = le langage

C’est réglé, on passe à la suite.

Commençons par le commencement

En premier lieu, on regarde la base d’un projet HTTP en Go. C’est finalement assez simple, on utilise le package “http” et on crée des handlers:

package main
import (
    "fmt"
    "net/http"
)

// Un handler qui répondrait à /api/v1/user

func GetUser(w http.ResponseWriter, r *http.Request) {
    // du blabla
    // et on répond
    w.Write("Un appel à GetUser...")
}

// la fonction principale (et pas de jeu de mot avec main et pied)
func main(){
    http.HandleFunc("/api/v1/user, GetUser)
    http.ListenAndServe(":8080", nil)
}

Pour l’exemple, je ne fais qu’écrire la réponse en texte. Vous pourrez utiliser le package “encoding/json” ou “encoding/xml” pour répondre comme il se doit en REST :)

Bon le “blabla” de la fonction GetUser est pas expliqué, tout va dépendre de votre API, de votre base de données et des traitements à effectuer.

Mais vous allez vite comprendre que tout ça c’est bien joli, mais ça reste limité. Pourquoi ?

Problème 1 - Le verbe HTTP

J’aimerai d’abord pouvoir répondre à un GET de “api/v1/user/12345”, “12345” étant un id d’utilisateur. Mais aussi à un PUT, ou PATCH sur la même URL… Sauf que là, la fonction GetUser(), mal nommée en plus, prend toutes les requêtes. Il faudrait alors tester “r.Method”, et pour chaque cas appeler d’autres fonctions.

Problème 2 - Les paramètre dans le path

Et en plus, récupérer le “12345” en fin d’URL, ça va qu’en on a que ça à récupérer, mais quand on commence à jouer un peu ça devient vite problématique.

On pourrait commencer à coder un autre Muxer et créer la méthode “ServeHTTP” comme le dit la doc. Mais honnêtement, on va réinventer une roue sans être sûr de bien l’avoir fait circulaire.

Conclusion, faut une surcouche…

Bref, pour quelque chose de très simple, ça suffit amplement. Mais un projet qui demande un peu plus de cuisine va vite vous condraindre à vous battre avec du code en veux tu en voilà. Le module HTTP de Go n’est pas fait pour proposer des règles d’écritures strictes. C’est un module simple pour faire des choses simples. Pour faire plus compliqué, il faut coder… ça vous ennui ? Heureusement il y a Gorilla.

Gorilla, le couteau suisse

Voyez la page: http://www.gorillatoolkit.org/

Gorilla est un ensemble de packages utltra intéressant qui rend un nombre de services vraiment conséquent. On va regarder gorilla/mux à la page: http://www.gorillatoolkit.org/pkg/mux

Ce muxer est vraiment cool et nous permet de faire:

package main

import (
    "fmt"
    "net/http"
    "github.com/gorilla/mux"
)

//... je vous passe les détail de fonctions, on va y revenir
func User(w http.ResponseWriter, r *http.Request) {
    // maintenant, je peux avoir:
    vars := mux.Vars(r) // récupère les valeurs d'url

    // vars["id"] contient l'id utilisateur... 
    fmt.Fprintf(w, "On demande l'utilisateur %s", vars["id"]) 

    // id => string, pas le choix, sinon on le converti avec strconv
}

func main(){
    // on crée un muxer
    r := mux.NewRouter() 

    // remarquez le "id" qu'on utilise dans User()...
    r.HandleFunc("/api/v1/user/{id:[0-9]*}", User)

    // cette fois on passe le muxer à ListenAndServe, et non pas "nil"
    http.ListenAndServe(":8080", r)
}

Maintenant, on peut faire joujou avec l’id, mais on ne sait pas encore faire un choix entre GET et PUT… Et bien c’est toujours aussi simple:

func main() {
    // on crée un muxer
    r := mux.NewRouter() 

    // on attache les fonctions en fonction des methodes HTTP
    r.HandleFunc("/api/v1/user/{id:[0-9]*}", GetUser).Methods("GET")
    r.HandleFunc("/api/v1/user/{id:[0-9]*}", PutUser).Methods("PUT")

    // cette fois on passe le muxer à ListenAndServe, et non pas "nil"
    http.ListenAndServe(":8080", r)

}

Reste alors à créer les deux fonctions GetUser() et PutUser(). En réalité il faut comprendre comment ça se passe:

  • r.HandleFunc() => enregistre la route (mux.Route) dans le muxer et retourne un mux.Route puis…
  • .Methods(…) => la route retournée par HandleFunc ne fonctionnera qu’avec la ou les méthodes spécifiées (on donner plusieurs verbes HTTP à Methods())

Il existe aussi des manières de gérer les domaines, des groupes de Routes, de nommer une route pour créer les urls… la doc est très complète et je vous invite à la lire.

Bon, c’est une introduction succinte, je sais, mais ça vous donne des billes pour commencer.

Si vous voulez non pas créer une API (qui répond en json ou xml par exemple) mais plutôt faire une des pages WEB (en html), il faudra vous pencher sur les packages “html/template” (dans le SDK de Go) ou sur d’autres moteurs comme Pongo2 (https://github.com/flosch/pongo2) semblable à Jinja (python) ou Twig (pour php).

Et coté ORM, base données… on en reparlera… J’utilise “mgo” le paquet pour MongoDB (https://labix.org/mgo) et j’ai beaucoup aimé l’ORM (“O” étant sujet à discussion puisque Go n’a pas d’objet…) nommé “gorm” (https://github.com/jinzhu/gorm) que j’intègre à mon framework.

En bref, ne venez pas me dire que Go est “pauvre en package pour se simplifier le travail”, parce que là… franchement…

Dans le prochain épisode, je vous montrerai comment on peu “packager” nos handlers, et faire un fichier de configuration pour mapper les requêtes. Avec ça, on peut rendre sympathique la configuration de notre API.

Et rien ne vous empêche de créer un service qui crée automatiquement l’API… en fait tout est possible :)

comments powered by Disqus