Dans une application Go, il existe un contexte qui rend les choses plus intéressantes. Un contexte transporte les délais, les signaux d'annulation et d'autres valeurs liées aux requêtes à travers les limites de l'API et entre les processus. Lors de l'utilisation d'une requête, il peut être utilisé pour annuler ou temporiser la requête. Une annulation peut se produire pour de nombreuses raisons différentes. L'utilisateur peut fermer son navigateur ou cliquer sur "Annuler" pendant le traitement de la demande. Il peut y avoir une mauvaise connexion internet ou un autre problème de réseau. Un client peut décider que la requête prend trop de temps et la fermer. Quelle que soit la raison, le contexte signalera qu'il est terminé. Cela signifie que le travail qui n'a pas encore été effectué peut être ignoré.
Certains paquets pour les bases de données, les clients http et d'autres nécessiteront qu'un contexte leur soit transmis. Les autres auront probablement un moyen de le définir. Au départ, je n'ai pas compris pourquoi. Il y avait déjà un contexte défini dans la requête, je l'ai donc simplement transmis. Lorsqu'un contexte était annulé, nous avons commencé à voir des erreurs de contexte annulé. C'est parce que ces paquets agissent sur ce signal de contexte terminé en arrêtant tous leurs traitements et en renvoyant une erreur.
ctx := request.Context()
// recherche d'une personne dans la base de données
person, err := databaseCall(ctx)
...
// databaseCall Recherche une personne dans la base de données.
funcdatabaseCall(ctxcontext.Context) (Person, error) {
logInfo(ctx, "Making the database call")
var person Personne
// pause pour permettre au contexte d'être annulé
err := pause(ctx)
if err != nil {
return person, err
}
// le paquetage de base de données populaire pgxpostgres exige qu'un contexte soit défini dans la plupart des opérations
connection, err := pgx.Connect(ctx,
"postgres://postgres:postgres@localhost:5432/postgres")
if err != nil {
// en plus des erreurs habituelles, si le paquet pgx remarque que le contexte est terminé, il renverra une erreur
return person, err
}
defer connection.Close(ctx)
// demande une personne à la base de données et remplit les valeurs de sa structure
err = connection.QueryRow(ctx, "select name from
people").Scan(&person.Name)
return person, err
}
Pour voir le code complet, consultez mon application d'exemple sur GitHub à l'adresse suivante https://github.com/paul-ferguson/the-go-context
Remarquez que cette application vérifie si l'appel à la base de données renvoie une erreur. S'il y a une erreur, nous vérifions si le contexte est terminé. Si c'est le cas, l'application ne fait aucun traitement supplémentaire et retourne simplement. Elle n'a même pas besoin de renvoyer un code d'erreur http ou une réponse JSON.
Si vous voulez qu'une application Go fonctionne comme une application Java, vous devez utiliser le contexte approprié. Disons que nous avons un point de terminaison REST qui traite un post-call pour sauvegarder des données. Nous voulons que ces données soient entièrement traitées et transférées dans la base de données, même si le contexte de la demande est annulé. Dans ce cas, nous pouvons utiliser context.Background() pour créer un nouveau contexte qui ne pourra jamais être annulé. J'en ai montré un exemple ci-dessous. J'ai également inclus dans le code commenté la méthode context.WithCancel()
,
context.WithDeadline()
ou context.WithTimeout()
. Elles définissent respectivement une méthode d'annulation et une date limite ou un délai d'attente/durée qui déclenchera le signal context done. A context.Background()
ne devrait être utilisé que dans certains scénarios : fonctions principales, initialisation et tests, et en tant que contexte de premier niveau pour les demandes entrantes. Notre cas d'utilisation est ce dernier. Dans la plupart des cas, il est préférable d'utiliser l'une des autres options.
ctx:= context.Background()
/* c'est ce qu'il faut faire : Essayez plutôt ces autres options :
// ceci renvoie un contexte et une fonction que nous avons appelée cancel
ctx, cancel := context.WithCancel(request.Context())
// l'appel à cancel déclenchera le signal done
cancel()
// ceci déclenchera un timeout après 2 secondes
ctx, cancel := context.WithTimeout(request.Context(), time.Second * 2)
// d'après la documentation de Go : Même si ctx sera expiré, c'est une bonne pratique d'appeler sa fonction d'annulation dans tous les cas. Sinon, le contexte et son parent peuvent rester en vie plus longtemps que nécessaire. defer cancel() // ceci déclenchera un timeout après un délai de 2 secondes dans le futur ctx, cancel := context.WithDeadline(request.Context(), time.Now().Add(time.Second * 2)) defer cancel()
*/
...
// restCall Recherche une personne en effectuant un appel de repos.
funcrestCall(ctxcontext.Context) (Personne, erreur) {
logInfo(ctx, "Making the rest call")
var personne Personne
// créer la requête get vers le point de terminaison côté serveur
request, err := http.NewRequestWithContext(ctx, "GET",
"http://localhost:8080/server-side-get", nil)
/*
essayez ceci : Si nous ne transmettons pas le contexte, la requête ne sera pas annulée lorsqu'un signal "done" se produira. La requête
demande sera entièrement traitée, ce qui gaspillera des ressources.
request, err := http.NewRequest("GET", "http://localhost:8080/server-side-get", nil)
*/
// transmet l'identifiant de la requête dans l'en-tête, ce qui nous permet de tracer cette requête
request.Header.Add(requestIDHeaderKey, ctx.Value(requestIDContextKey).(string))
// effectuer la requête
response, err := http.DefaultClient.Do(request)
if err != nil {
//todo
return person, err
}
// lire le corps complet de la réponse
body, err := io.ReadAll(response.Body)
if err != nil {
return person, err
}
// fermer le corps de la réponse
err = response.Body.Close()
if err != nil {
return person, err
}
// décomposer le contenu du corps de la réponse en une structure de personne
err = json.Unmarshal(body, &person)
return person, err
}
Pour voir le code complet, consultez mon exemple d'application sur GitHub à l'adresse https://github.com/paul-ferguson/the-go-context
Enfin, pour être complet, je devrais montrer comment le contexte peut être utilisé pour passer des valeurs à l'échelle de la requête à travers les frontières de l'API et entre les processus.J'ai déjà utilisé cela pour enregistrer des valeurs communes, comme l'identifiant d'une requête.Voici un exemple.
const requestIDHeaderKey= "request-id"
const requestIDContextKey= contextKey(requestIDHeaderKey)
...
// définir l'identifiant de la demande comme valeur dans le contexte
requestId:= request.Header.Get("request-id")
if requestId== "" {
// aucun identifiant de demande n'a été défini, il faut donc en créer un unique
requestId= uuid.New().String()
}
ctx= context.WithValue(ctx, requestIDContextKey, requestId)
logInfo(ctx, "Get was called")
...
// il existe de nombreux paquets de journalisation que nous aurions pu utiliser, mais nous avons créé le nôtre pour plus de clarté dans cet exemple.
funclogInfo(ctxcontext.Context, message string) {
fmt.Println("info", message, ctx.Value(requestIDContextKey))
}
Pour voir le code complet, consultez mon application d'exemple sur GitHub à l'adresse https://github.com/paul-ferguson/the-go-context