287 votes

Gestion des requêtes post JSON en Go

J'ai donc ce qui suit, qui semble incroyablement bricolé, et je me suis dit que Go avait des bibliothèques mieux conçues que ça, mais je n'arrive pas à trouver un exemple de Go gérant une requête POST de données JSON. Ce sont tous des POST de formulaire.

Voici un exemple de demande : curl -X POST -d "{\"test\": \"that\"}" http://localhost:8082/test

Et voici le code, avec les logs incorporés :

package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type test_struct struct {
    Test string
}

func test(rw http.ResponseWriter, req *http.Request) {
    req.ParseForm()
    log.Println(req.Form)
    //LOG: map[{"test": "that"}:[]]
    var t test_struct
    for key, _ := range req.Form {
        log.Println(key)
        //LOG: {"test": "that"}
        err := json.Unmarshal([]byte(key), &t)
        if err != nil {
            log.Println(err.Error())
        }
    }
    log.Println(t.Test)
    //LOG: that
}

func main() {
    http.HandleFunc("/test", test)
    log.Fatal(http.ListenAndServe(":8082", nil))
}

Il doit y avoir un meilleur moyen, non ? J'ai juste du mal à trouver quelle pourrait être la meilleure pratique.

(Go est également connu sous le nom de Golang par les moteurs de recherche, et mentionné ici pour que d'autres puissent le trouver).

3 votes

Si vous utilisez curl -X POST -H 'Content-Type: application/json' -d "{\"test\": \"that\"}" entonces req.Form["test"] devrait retourner "that"

0 votes

@Vinicius y a-t-il des preuves de cela ?

432voto

Joe Points 1263

Veuillez utiliser json.Decoder au lieu de json.Unmarshal .

func test(rw http.ResponseWriter, req *http.Request) {
    decoder := json.NewDecoder(req.Body)
    var t test_struct
    err := decoder.Decode(&t)
    if err != nil {
        panic(err)
    }
    log.Println(t.Test)
}

95 votes

Pouvez-vous expliquer pourquoi ?

99 votes

Pour commencer, il semble qu'il puisse gérer un flux plutôt que d'avoir à le charger lui-même dans un tampon. (Je suis un autre Joe BTW)

8 votes

Je me demande à quoi ressemblerait un traitement correct des erreurs dans ce cas. Je ne pense pas que ce soit une bonne idée de paniquer sur un json invalide.

91voto

Daniel Points 9676

Vous devez lire de req.Body . Le site ParseForm est lu à partir de la méthode req.Body puis de l'analyser dans le format standard encodé par HTTP. Ce que vous voulez, c'est lire le corps et l'analyser au format JSON.

Voici votre code mis à jour.

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "io/ioutil"
)

type test_struct struct {
    Test string
}

func test(rw http.ResponseWriter, req *http.Request) {
    body, err := ioutil.ReadAll(req.Body)
    if err != nil {
        panic(err)
    }
    log.Println(string(body))
    var t test_struct
    err = json.Unmarshal(body, &t)
    if err != nil {
        panic(err)
    }
    log.Println(t.Test)
}

func main() {
    http.HandleFunc("/test", test)
    log.Fatal(http.ListenAndServe(":8082", nil))
}

0 votes

Merci ! Je vois maintenant où je me trompais. Si vous appelez req.ParseForm() comme je l'ai fait lors de mes précédentes tentatives de résolution de ce problème, avant d'essayer de lire le document req.Body il semble que cela nettoie le corps et unexpected end of JSON input est lancé lorsque vous allez à Unmarshal (au moins dans la version 1.0.2)

1 votes

@Daniel : Quand je fais curl -X POST -d "{\"tes\" : \"that\"}" localhost:8082/test , log.Println(t.Test) renvoie un résultat vide. Pourquoi ? Ou pour cette raison, si je poste n'importe quel autre JSON, il retourne vide.

0 votes

Votre requête POST est incorrecte. tes != test. Appréciez que c'était il y a 5 ans :/

79voto

colminator Points 2707

Il y a deux raisons pour lesquelles json.Decoder doit être préféré à json.Unmarshal - qui ne sont pas abordés dans la réponse la plus populaire de 2013 :

  1. Février 2018, go 1.10 a introduit une nouvelle méthode json.Decoder.DisallowUnknownFields() qui répond au souci de détecter les entrées JSON non souhaitées
  2. req.Body est déjà un io.Reader . Lire l'intégralité de son contenu, puis effectuer json.Unmarshal gaspille des ressources si le flux était, disons, un bloc de 10MB de JSON invalide. L'analyse du corps de la requête, avec json.Decoder comme il ruisseaux en déclencherait une erreur d'analyse précoce en cas de JSON invalide. Le traitement des flux d'E/S en temps réel est la meilleure solution. aller-retour .

Répondre à certains des commentaires des utilisateurs concernant la détection de mauvaises entrées utilisateur :

Pour appliquer les champs obligatoires, et d'autres contrôles d'hygiène, essayez :

d := json.NewDecoder(req.Body)
d.DisallowUnknownFields() // catch unwanted fields

// anonymous struct type: handy for one-time use
t := struct {
    Test *string `json:"test"` // pointer so we can test for field absence
}{}

err := d.Decode(&t)
if err != nil {
    // bad JSON or unrecognized json field
    http.Error(rw, err.Error(), http.StatusBadRequest)
    return
}

if t.Test == nil {
    http.Error(rw, "missing field 'test' from JSON object", http.StatusBadRequest)
    return
}

// optional extra check
if d.More() {
    http.Error(rw, "extraneous data after JSON object", http.StatusBadRequest)
    return
}

// got the input we expected: no more, no less
log.Println(*t.Test)

Terrain de jeux

Sortie typique :

$ curl -X POST -d "{}" http://localhost:8082/strict_test

expected json field 'test'

$ curl -X POST -d "{\"Test\":\"maybe?\",\"Unwanted\":\"1\"}" http://localhost:8082/strict_test

json: unknown field "Unwanted"

$ curl -X POST -d "{\"Test\":\"oops\"}g4rB4g3@#$%^&*" http://localhost:8082/strict_test

extraneous data after JSON

$ curl -X POST -d "{\"Test\":\"Works\"}" http://localhost:8082/strict_test 

log: 2019/03/07 16:03:13 Works

74voto

Steve Stilson Points 827

Je me rendais fou avec ce même problème. Mon JSON Marshaller et Unmarshaller ne remplissaient pas ma structure Go. Puis j'ai trouvé la solution à https://eager.io/blog/go-and-json :

"Comme pour tous les structs en Go, il est important de se rappeler que seuls les champs champs avec une première lettre majuscule sont visibles pour les programmes externes comme le JSON Marshaller."

Après cela, mon Marshaller et Unmarshaller ont fonctionné parfaitement !

22voto

JohnnyCoder Points 454

J'ai trouvé l'exemple suivant tiré de la documentation très utile (source aquí ).

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "log"
    "strings"
)

func main() {
    const jsonStream = `
        {"Name": "Ed", "Text": "Knock knock."}
        {"Name": "Sam", "Text": "Who's there?"}
        {"Name": "Ed", "Text": "Go fmt."}
        {"Name": "Sam", "Text": "Go fmt who?"}
        {"Name": "Ed", "Text": "Go fmt yourself!"}
    `
    type Message struct {
        Name, Text string
    }
    dec := json.NewDecoder(strings.NewReader(jsonStream))
    for {
        var m Message
        if err := dec.Decode(&m); err == io.EOF {
            break
        } else if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("%s: %s\n", m.Name, m.Text)
    }
}

La clé ici étant que l'OP cherchait à décoder

type test_struct struct {
    Test string
}

...dans ce cas, nous laisserions tomber le const jsonStream et remplacer le Message avec la structure test_struct :

func test(rw http.ResponseWriter, req *http.Request) {
    dec := json.NewDecoder(req.Body)
    for {
        var t test_struct
        if err := dec.Decode(&t); err == io.EOF {
            break
        } else if err != nil {
            log.Fatal(err)
        }
        log.Printf("%s\n", t.Test)
    }
}

Mise à jour : J'ajouterais également que ce poste fournit également d'excellentes données sur la façon de répondre avec JSON. L'auteur explique struct tags ce dont je n'étais pas conscient.

Puisque JSON ne ressemble pas normalement à {"Test": "test", "SomeKey": "SomeVal"} mais plutôt {"test": "test", "somekey": "some value"} vous pouvez restructurer votre structure comme ceci :

type test_struct struct {
    Test string `json:"test"`
    SomeKey string `json:"some-key"`
}

...et maintenant votre gestionnaire analysera le JSON en utilisant "some-key" au lieu de "SomeKey" (que vous utiliserez en interne).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X