Nous avons présenté le fonctionnement général de l’API de .fr, et présenté comment l’utiliser en pratique dans le langage de programmation Python. Continuons avec le langage Go. Attention, pour simplifier cet article, on omettra une partie du code, notamment le traitement des erreurs. Cela ne serait évidemment pas acceptable dans un programme réel. Une version plus complète du programme figure en https://gitlab.rd.nic.fr/afnic/code-samples/-/tree/main/API/Go.
Choix techniques
Go dispose, dans sa bibliothèque standard, de moyens de faire des requêtes HTTP, que nous utiliserons ici. Les programmes doivent tous être compilés avec le fichier afnic.go, par exemple, pour la liste des domaines :
% go build -o list-domains list-domains.go afnic.go
L’authentification
values := url.Values{"client_id": {"registrars-api-client"},
"username": {login}, "password": {password},
"grant_type": {"password"}}
response, err := http.PostForm("https://login-sandbox.nic.fr/auth/realms/fr/protocol/openid-connect/token",
values)
body, err := ioutil.ReadAll(response.Body)
type Result struct {
Access_token string /* We ignore the other members */
}
var result Result
err = json.Unmarshal(body, &result)
Ce code définit une fonction qui va faire une requête HTTP avec la méthode POST pour obtenir le jeton. Elle va pour cela passer une série de couples clé-valeur1 indiquant notamment le mot de passe du client2. Le résultat est analysé avec les fonctions du module json de la bibliothèque standard. Une fois obtenu le jeton (c’est un des membres de l’objet JSON renvoyé), on peut faire un dictionnaire des champs nécessaires pour les en-têtes des futures requêtes HTTP :
return map[string]string{"Content-Type": "application/json",
"Accept": "application/json",
"Extensions": "FRNIC_V2",
"Authorization": fmt.Sprintf("Bearer %s", result.Access_token)}
Les erreurs qui peuvent se produire
En cas d’erreur, on obtient un code de retour HTTP qu’on peut tester, par exemple avec :
if response.StatusCode != 200 {
panic(fmt.Sprintf("Wrong HTTP response for token: %s", response.Status))
}
On a aussi des messages plus détaillés, encodés en JSON, dans le corps de la réponse.
Liste de nos domaines
request, err := http.NewRequest(http.MethodGet, "https://api-sandbox.nic.fr/v1/domains", nil)
for name, value := range headers() {
request.Header.Add(name, value)
}
response2, err := http.DefaultClient.Do(request)
body, err := ioutil.ReadAll(response2.Body)
type Domains struct {
Name string /* We ignore the other members */
}
type Result struct {
Content []Domains /* We ignore the other members */
}
var result Result
err = json.Unmarshal(body, &result)
for _, domain := range result.Content {
fmt.Printf("%s\n", domain.Name)
}
On fait une requête GET à l’URI de gestion des domaines, en ajoutant les en-têtes nécessaires. Il n’y a pas d’arguments à cette requête. Le résultat est du JSON, qu’on analyse. Go étant statiquement typé, contrairement à Python, l’analyse est plus complexe, nécessitant de déclarer des types pour les variables qui accueilleront le résultat de l’analyse (ici, lka variable result). On n’a plus ensuite qu’à itérer sur cet te variable affichant la liste des noms de domaines que nous gérons.
Recherche de disponibilité
On nomme souvent ce service DAS, pour Domain Availability Service.
type Request struct {
Names []string `json:"names"`
}
var list Request
list.Names = make([]string, flag.NArg())
for i := 0; i < flag.NArg(); i++ {
list.Names[i] = flag.Arg(i)
}
json_list, err := json.Marshal(list)
request, err := http.NewRequest(http.MethodPost,
"https://api-sandbox.nic.fr/v1/domains/check",
bytes.NewReader(json_list))
for name, value := range headers() {
request.Header.Add(name, value)
}
response2, err := http.DefaultClient.Do(request)
body, err := ioutil.ReadAll(response2.Body)
type DomainAvailability struct {
Name string
Available bool
Reason string /* Not present if the domain is available */
}
type Response struct {
Response []DomainAvailability
}
var response Response
err = json.Unmarshal(body, &response)
for _, domain := range response.Response {
var avail string
if domain.Available {
avail = "available"
} else {
avail = fmt.Sprintf("NOT available (%s)", domain.Reason)
}
fmt.Printf("%s: %s\n", domain.Name, avail)
}
La liste des noms de domaine qui nous intéressent est obtenue sur la ligne de commande (le flag.Arg). On en fait une variable Go, list. Le pacquetage json l’encodera plus tard à la syntaxe JSON. Comme les règles de syntaxe Go ne sont pas les mêmes que celles de JSON, la déclaration du type Request est accompagnée d’annotations pour indiquer le nom que doivent porter les champs en JSON3.
La requête est cette fois de méthode HTTP POST. La réponse est un objet JSON, qu’on analyse, avant d’itérer et d’afficher les informations sur chaque nom :
% ./das toto.fr nic.fr truc.com
truc.com: NOT available (ZONE_UNKNOWN)
toto.fr: available
nic.fr: NOT available (IN_USE)
Un nom est enregistrable, un autre est déjà utilisé, le dernier étant dans un autre domaine de tête.
Création d’un domaine
type Contact struct {
ClientID string `json:"clientId"`
Role string `json:"role"`
}
type Request struct {
Name string `json:"name"`
AuthInfo string `json:"authorizationInformation"`
Registrant string `json:"registrantClientId"`
Contacts []Contact ` json:"contacts"`
}
var my_request Request
my_request.Name = flag.Arg(0)
my_request.AuthInfo = "Vachement1234sur"
my_request.Registrant = contact
my_request.Contacts = make([]Contact, 2)
my_request.Contacts[0].ClientID = contact
my_request.Contacts[0].Role = "ADMINISTRATIVE"
my_request.Contacts[1].ClientID = contact
my_request.Contacts[1].Role = "TECHNICAL"
json_list, err := json.Marshal(my_request)
request, err := http.NewRequest(http.MethodPost,
"https://api-sandbox.nic.fr/v1/domains",
bytes.NewReader(json_list))
for name, value := range headers() {
request.Header.Add(name, value)
}
response2, err := http.DefaultClient.Do(request)
body, err := ioutil.ReadAll(response2.Body)
type DomainCreation struct {
Name string
CreationDate string
}
var response DomainCreation
err = json.Unmarshal(body, &response)
fmt.Printf("%s created on %s\n", response.Name, response.CreationDate)
Ici, le nom de domaine à créer est indiqué sur la ligne de commande (flag.Arg). La création d’un nom nécessite de passer un objet JSON plus complexe, où on indique, outre ce nom, le « authinfo » qui servira pour authentifier les transferts, et surtout le titulaire du nom et les contacts. Ici, on a choisi de mettre le même identificateur pour le titulaire, le contact technique, et le contact administratif. Cet identificateur est celui obtenu lors de la création d’un contact (non montrée ici), comme « CTC65093 ».
Suppression d’un domaine
request, err := http.NewRequest(http.MethodDelete,
fmt.Sprintf("https://api-sandbox.nic.fr/v1/domains/%s", domainname),
nil)
for name, value := range headers() {
request.Header.Add(name, value)
}
response2, err := http.DefaultClient.Do(request)
body, err := ioutil.ReadAll(response2.Body)
type DomainDeletion struct {
Name string
}
var response DomainDeletion
err = json.Unmarshal(body, &response)
fmt.Printf("%s deleted\n", response.Name)
Les seules nouveautés ici sont l’utilisation de la méthode HTTP DELETE qui, comme son nom l’indique, sert à détruire l’objet visé, et le fait que l’identificateur de cet objet, le nom de domaine, est dans l’URI.
Ajout d’un serveur de noms à tous les domaines
On va enfin ajouter un serveur de noms à tous les domaines de notre portefeuille, ce qui permet d’illustrer la puissance de l’API4.
request, err := http.NewRequest(http.MethodGet, "https://api-sandbox.nic.fr/v1/domains", nil)
response2, err := http.DefaultClient.Do(request)
body, err := ioutil.ReadAll(response2.Body)
for _, domain := range list_result.Content {
my_request.NameServer = make([]string, 1)
my_request.NameServer[0] = ns
my_request.Name = domain.Name
json_list, err := json.Marshal(my_request)
request, err := http.NewRequest(http.MethodPatch,
"https://api-sandbox.nic.fr/v1/domains/",
bytes.NewReader(json_list))
response2, err := http.DefaultClient.Do(request)
if response2.StatusCode != 200 {
fmt.Fprintf(os.Stderr, "Wrong HTTP response for update of domain %s: %s\n", my_request.Name, response2.Status)
continue
}
body, err := ioutil.ReadAll(response2.Body)
err = json.Unmarshal(body, &patch_response)
fmt.Printf("%s updated on %s\n", patch_response.Name, patch_response.UpdateDate)
}
}
Ici, on a récupéré la liste des domaines comme dans notre premier exemple puis, pour chaque domaine, on utilise la méthode HTTP PATCH pour mettre à jour le domaine, en lui passant une liste de serveurs de noms à ajouter (ici, la liste n’a qu’un élément)5.
1 – Le paquetage Go url se chargeant de l’encoder proprement.
2 – Comme cette requête véhicule des secrets, il faut bien sûr employer HTTPS, et ne pas l’affaiblir, par exemple en débrayant les vérifications du certificat. Protéger la confidentialité de ces mots de passe est important.
3 – Ici, la documentation de l’API nous indique qu’il doit y avoir un champ names (tout en minuscule) alors que Go ne considère comme champs publics, exportables, que ceux dont le nom commence par une majuscule.
4 – Mais aussi ses dangers : cette possibilité permet également de faire des erreurs en grand. Vérifiez donc bien votre code, et testez-le d’abord sur le bac à sable.
5 – C’est avec la même méthode HTTP et le même URL qu’on ferait d’autres modifications sur le domaine comme de changer son état ou de lui ajouter/retirer des clés DNSSEC.