Bearbeitung von JSON-Post-Anfragen in Go

250

Ich habe also Folgendes, was unglaublich hackig erscheint, und ich habe mir gedacht, dass Go besser gestaltete Bibliotheken als diese hat, aber ich kann kein Beispiel für Go finden, das eine POST-Anforderung von JSON-Daten verarbeitet. Sie sind alle Form POSTs.

Hier ist eine Beispielanfrage: curl -X POST -d "{\"test\": \"that\"}" http://localhost:8082/test

Und hier ist der Code mit den eingebetteten Protokollen:

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))
}

Es muss einen besseren Weg geben, oder? Ich bin nur ratlos darüber, was die beste Praxis sein könnte.

(Go ist in den Suchmaschinen auch als Golang bekannt und wird hier erwähnt, damit andere es finden können.)

TomJ
quelle
3
Wenn Sie verwenden curl -X POST -H 'Content-Type: application/json' -d "{\"test\": \"that\"}", dann req.Form["test"]sollte zurückkehren"that"
Vinicius
@ Vinicius gibt es Beweise dafür?
Diralik

Antworten:

388

Bitte verwenden Sie json.Decoderanstelle von 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)
}
Joe
quelle
79
Könnten Sie bitte erklären, warum?
Ryan Bigg
86
Zunächst sieht es so aus, als ob dies einen Stream verarbeiten kann, anstatt dass Sie alles selbst in einen Puffer laden müssen. (Ich bin ein anderer Joe BTW)
Joe
7
Ich frage mich, wie die richtige Fehlerbehandlung in diesem Fall aussehen würde. Ich denke nicht, dass es eine gute Idee ist, auf einen ungültigen json in Panik zu geraten.
Codepushr
15
Ich glaube nicht, dass Sie defer req.Body.Close()aus den Dokumenten Folgendes benötigen: "Der Server schließt den Anforderungshauptteil. Der ServeHTTP-Handler muss dies nicht tun." Zur Beantwortung von @thisisnotabus aus den Dokumenten: "Bei Serveranfragen ist der Anfragetext immer nicht null, gibt jedoch EOF sofort zurück, wenn kein Text
Drew LeSueur
22
Ich würde vorschlagen, nicht zu verwenden json.Decoder. Es ist für Streams von JSON-Objekten vorgesehen, nicht für ein einzelnes Objekt. Es ist für ein einzelnes JSON-Objekt nicht effizienter, da es das gesamte Objekt in den Speicher liest. Es hat den Nachteil, dass sich Müll nicht beschwert, wenn er nach dem Objekt enthalten ist. Abhängig von einigen Faktoren json.Decoderwird der Text möglicherweise nicht vollständig gelesen, und die Verbindung kann nicht wiederverwendet werden.
Grünkohl B
85

Sie müssen aus lesen req.Body. Die ParseFormMethode liest aus dem req.Bodyund analysiert es dann im Standard-HTTP-codierten Format. Sie möchten den Text lesen und im JSON-Format analysieren.

Hier ist Ihr Code aktualisiert.

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))
}
Daniel
quelle
Vielen Dank! Ich sehe, wo ich jetzt falsch liege. Wenn Sie anrufen req.ParseForm(), was ich in früheren Versuchen getan habe, dieses Problem zu lösen, bevor Sie versuchen, das zu lesen req.Body, scheint es den Körper unexpected end of JSON inputUnmarshal
auszuräumen
1
@ Daniel: Wenn ich -X POST -d "{" tes ":" that "}" localhost: 8082 / test locke, gibt log.Println (t.Test) leer zurück. Warum ? Oder wenn Sie einen anderen JSON posten, wird dieser leer zurückgegeben
irgendwann
Ihre POST-Anfrage ist falsch. tes! = test. Schätzen Sie, dass vor 5 Jahren war: /
Rambatino
Dies ist ein schönes einfaches Beispiel!
15412s
Dies ist ein guter Rat, aber um klar zu sein, sind die Antworten, die sich auf die Verwendung von json.NewDecoder(req.Body)beziehen, auch richtig.
Rich
59

Ich habe mich mit genau diesem Problem verrückt gemacht. Mein JSON Marshaller und Unmarshaller haben meine Go-Struktur nicht gefüllt. Dann fand ich die Lösung unter https://eager.io/blog/go-and-json :

"Wie bei allen Strukturen in Go ist es wichtig zu bedenken, dass nur Felder mit einem Großbuchstaben für externe Programme wie den JSON Marshaller sichtbar sind."

Danach haben mein Marshaller und Unmarshaller perfekt funktioniert!

Steve Stilson
quelle
Bitte fügen Sie einige Ausschnitte aus dem Link hinzu. Wenn es veraltet ist, gehen die Beispiele verloren.
030
47

Es gibt zwei Gründe, json.Decoderdie bevorzugt werden sollten json.Unmarshal- die in der beliebtesten Antwort aus dem Jahr 2013 nicht angesprochen werden:

  1. Im Februar 2018 wurde go 1.10eine neue Methode json.Decoder.DisallowUnknownFields () eingeführt , mit der das Problem der Erkennung unerwünschter JSON-Eingaben behoben wird
  2. req.Bodyist schon ein io.Reader. Wenn Sie den gesamten Inhalt json.Unmarshallesen und dann ausführen, werden Ressourcen verschwendet, wenn der Stream beispielsweise ein 10-MB-Block mit ungültigem JSON war. Das Parsen des Anforderungshauptteils mit json.Decoder, während es einströmt, würde einen frühen Analysefehler auslösen, wenn ungültiger JSON festgestellt wird . Die Verarbeitung von E / A-Streams in Echtzeit ist der bevorzugte Weg .

Adressierung einiger Benutzerkommentare zum Erkennen fehlerhafter Benutzereingaben:

Versuchen Sie Folgendes, um Pflichtfelder und andere Hygienekontrollen durchzusetzen:

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)

Spielplatz

Typische Ausgabe:

$ 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
colm.anseo
quelle
6
Vielen Dank für die Erklärung der Meinungen, anstatt nur zu sagen, dass etwas schlecht ist
Fjolnir Dvorak
Weißt du, was es nicht macht? Ich habe gesehen, Test kann zweimal in
JSON sein
@ tooptoop4 Man müsste einen benutzerdefinierten Decoder schreiben, um vor doppelten Feldern zu warnen - was dem Decoder Ineffizienzen hinzufügt -, um ein Szenario zu bewältigen, das niemals eintreten würde. Kein Standard-JSON-Encoder würde jemals doppelte Felder erzeugen.
colm.anseo
20

Ich fand das folgende Beispiel aus den Dokumenten wirklich hilfreich (Quelle hier ).

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)
    }
}

Der Schlüssel hier war, dass das OP dekodieren wollte

type test_struct struct {
    Test string
}

... in diesem Fall würden wir die löschen const jsonStreamund die MessageStruktur durch die test_structfolgende ersetzen :

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)
    }
}

Update : Ich möchte auch hinzufügen, dass dieser Beitrag einige großartige Daten zur Antwort mit JSON enthält. Der Autor erklärt struct tags, was mir nicht bewusst war.

Da JSON normalerweise nicht so aussieht {"Test": "test", "SomeKey": "SomeVal"}, sondern wie folgt {"test": "test", "somekey": "some value"}, können Sie Ihre Struktur folgendermaßen umstrukturieren:

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

... und jetzt analysiert Ihr Handler JSON mit "some-key" im Gegensatz zu "SomeKey" (das Sie intern verwenden werden).

JohnnyCoder
quelle
1
type test struct {
    Test string `json:"test"`
}

func test(w http.ResponseWriter, req *http.Request) {
    var t test_struct

    body, _ := ioutil.ReadAll(req.Body)
    json.Unmarshal(body, &t)

    fmt.Println(t)
}
Angelica Payawal
quelle