Aller au contenu Aller au menu principal Aller au menu secondaire Aller au pied de page

Using the .fr API with Go (3/4)

Home > Observatory and resources > Expert papers > Using the .fr API with Go (3/4)
11/07/2022

We’ve presented the general workings of the .fr API and we’ve seen how to use it in practice with the Python programming language. Let’s continue with the Go language. Warning, to simplify this article we will omit part of the code, particularly the handling of errors. That would not of course be acceptable with a real program. A more complete version of the program can be found at https://gitlab.rd.nic.fr/afnic/code-samples/-/tree/main/API/Go.

Technical choices

Go has, in its standard library, means of making HTTP requests, which we will use here. The programs must all be compiled with the afnic.go file, for example, for the list of domains:

% go build -o list-domains list-domains.go afnic.go

Authentication

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)

This code defines a function that will make a HTTP request using the POST method to obtain the token. For this it will pass a series of key-value pairs1 indicating in particular the client’s password2. The result is analysed with the functions of the json module from the standard library. Once the token has been obtained (it is one of the members of the JSON object sent), we can make a dictionary of the fields necessary for the headings of the future HTTP requests:

return map[string]string{"Content-Type": "application/json",

            "Accept": "application/json",

            "Extensions": "FRNIC_V2",

            "Authorization": fmt.Sprintf("Bearer %s", result.Access_token)}

Errors that may occur

In the event of an error, we obtain a HTTP response status code that we can test, for example with:

if response.StatusCode != 200 {

            panic(fmt.Sprintf("Wrong HTTP response for token: %s", response.Status))

      }

There are also more detailed messages, encoded in JSON, in the body of the response.

List of our domains

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)

}

We make a GET request to the domain management URI, adding the necessary headers. There are no arguments to this request. The result is in JSON, which will be analysed. Go being statically typed, unlike Python, the analysis is more complex, requiring declaration of the types for the variables that will receive the result of the analyse (here, the variable result). We have then only to iterate this variable displaying the list of the domain names we manage.

Availability enquiry

This service is often referred to as DAS, 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)

}

The list of domain names we’re interested in is obtained from the command line (flag.arg). We make a Go variable, a list. The json package will later encode it into JSON syntax. Since the syntax rules for Go are not the same as for JSON, the declaration of type of Request is accompanied by annotations to indicate the names to be associated with the fields in JSON3.

The request this time uses the HTTP POST method. The response is a JSON object, which is analysed, before iterating and posting the information on each name:

% ./das foobar.fr nic.fr stuff.com

stuff.com: NOT available (ZONE_UNKNOWN)

foobar.fr: available

nic.fr: NOT available (IN_USE)

One name is available for registration, another is already in use, the last one being in another TLD.

Creation of a domain

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)

Here, the domain name to be created is indicated on the command line (flag.arg). The creation of a name requires passing a more complex JSON object where we indicate, in addition to this name, the “authinfo” that will serve to authenticate the transfers, and above all the holder of the name and the contacts. Here, we have chosen to use the same identifier for the holder, the technical contact and the administrative contact. This identifier is the one obtained when creating a contact (not shown here), such as “CTC65093”.

Deletion of a domain

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)

The only new features here are the use of the HTTP DELETE method which, as its name suggests, is used to destroy the object concerned, and the fact that the identifier of this object, the domain name, is in the URI.

Adding a name server to all the domains

Lastly we add a name server to all the domains in our portfolio, allowing us to illustrate the power of the 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)

      }

}

Here, we’ve recovered the list of domains as in our first example and then, for each domain, we use the HTTP PATCH method to update the domain, passing it a list of name servers to be added (here, the list has only one element)5.


1 – The Go url package takes charge of encoding cleanly.

2 – Since this request contains secrets, we must of course use HTTPS and not weaken it for example by disabling the certificate verifications. Protecting the confidentiality of these passwords is important.

3 – Here, the API documentation indicates that there must be a names field (all lower case) whereas Go considers only fields whose name starts with a capital letter to be public, exportable fields.

4 – But also its dangers: this possibility also allows major errors to be committed. So check your code well and test it first in the sandbox.

5 – It is with this same HTTP method and the same URL that we would make other changes to the domain such as changing its status or adding or withdrawing DNSSEC keys.