Obsługa żądania JSON Post w Go


250

Mam więc następujące, co wydaje się niezwykle hackerskie, i pomyślałem sobie, że Go ma lepiej zaprojektowane biblioteki niż ta, ale nie mogę znaleźć przykładu Go obsługującego żądanie POST danych JSON. Wszystkie są z POST.

Oto przykładowe żądanie: curl -X POST -d "{\"test\": \"that\"}" http://localhost:8082/test

A oto kod z osadzonymi dziennikami:

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

Musi być lepszy sposób, prawda? Jestem po prostu zaskoczony znalezieniem najlepszej praktyki.

(Go jest również znany jako Golang w wyszukiwarkach i wspomniany tutaj, aby inni mogli go znaleźć.)


3
jeśli użyjesz curl -X POST -H 'Content-Type: application/json' -d "{\"test\": \"that\"}", to req.Form["test"]powróć"that"
Winicjusz

@Vinicius, czy są na to jakieś dowody?
diralik

Odpowiedzi:


388

Użyj json.Decoderzamiast 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)
}

79
Czy możesz wyjaśnić dlaczego?
Ryan Bigg

86
Na początek wygląda na to, że może obsłużyć strumień, zamiast konieczności samodzielnego ładowania go do bufora. (Jestem innym Joe BTW)
Joe

7
Zastanawiam się, jak w tym przypadku wyglądałaby poprawna obsługa błędów. Nie sądzę, że dobrym pomysłem jest wpadać w panikę z powodu nieprawidłowego Jsona.
codepushr

15
Nie sądzę, że musisz defer req.Body.Close()Z Dokumentów: „Serwer zamknie treść żądania. Program ServeHTTP nie musi tego robić”. Również, aby odpowiedzieć na @thisisnotabus, z dokumentów: „W przypadku żądań serwera ciało żądania jest zawsze inne niż zero, ale zwróci natychmiast EOF, gdy ciało nie będzie obecne” golang.org/pkg/net/http/#Request
Drew LeSueur

22
Sugerowałbym nie używać json.Decoder. Jest przeznaczony dla strumieni obiektów JSON, a nie pojedynczego obiektu. Nie jest bardziej efektywny dla pojedynczego obiektu JSON, ponieważ wczytuje cały obiekt do pamięci. Ma to tę wadę, że jeśli śmieci zostaną uwzględnione po obiekcie, nie będzie narzekać. W zależności od kilku czynników json.Decodermoże nie w pełni odczytać treść, a połączenie nie będzie kwalifikować się do ponownego użycia.
Kale B

85

Musisz czytać z req.Body. ParseFormSposób odczytywania z req.Bodyi przetwarzania go w standardowym formacie zakodowanym HTTP. Chcesz przeczytać treść i parsować ją w formacie JSON.

Oto twój kod zaktualizowany.

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

Dzięki! Widzę teraz, gdzie się mylę. Jeśli zadzwonisz req.ParseForm(), co robiłem we wcześniejszych próbach rozwiązania tego problemu, zanim spróbujesz przeczytać req.Body, wydaje się, że unexpected end of JSON inputUnmarshal
usuwa

1
@Daniel: Kiedy wykonuję curl -X POST -d "{\" tes \ ": \" that \ "}" localhost: 8082 / test , log.Println (t.Test) zwraca puste. Czemu ? Lub jeśli o to chodzi, jeśli opublikujesz inny JSON, zwraca pusty
Somesh

Twoje żądanie POST jest nieprawidłowe. tes! = test. Doceń to 5 lat temu: /
Rambatino

To ładny prosty przykład!
15412,

To dobra rada, ale dla jasności odpowiedzi dotyczące użycia json.NewDecoder(req.Body)są również poprawne.
Rich

59

Doprowadzałam się do szału z tym właśnie problemem. Mój JSON Marshaller i Unmarshaller nie wypełniali mojej struktury Go. Następnie znalazłem rozwiązanie na https://eager.io/blog/go-and-json :

„Podobnie jak w przypadku wszystkich struktur w Go, ważne jest, aby pamiętać, że tylko pola z wielką pierwszą literą są widoczne dla programów zewnętrznych, takich jak JSON Marshaller.”

Potem mój Marshaller i Unmarshaller działali doskonale!


Dołącz kilka fragmentów linku. W przypadku utraty wartości przykłady zostaną utracone.
030

47

Są dwa powody, dla których json.Decodernależy być lepszym od json.Unmarshal- które nie zostały uwzględnione w najpopularniejszej odpowiedzi z 2013 roku:

  1. W lutym 2018 r. go 1.10Wprowadzono nową metodę json.Decoder.DisallowUnknownFields (), która rozwiązuje problem wykrywania niepożądanego wejścia JSON
  2. req.Bodyjest już io.Reader. Czytanie całej zawartości, a następnie wykonywanie json.Unmarshalmarnuje zasoby, jeśli strumień był, powiedzmy 10 MB bloku niepoprawnego JSON. Parsowanie treści żądania, wraz z json.Decoderjego strumieniem , spowoduje wystąpienie błędu wczesnej analizy, jeśli napotkamy niepoprawny JSON. Preferowanym sposobem jest przetwarzanie strumieni we / wy w czasie rzeczywistym .

W odpowiedzi na niektóre komentarze użytkowników na temat wykrywania złych danych wejściowych:

Aby wymusić obowiązkowe pola i inne kontrole sanitarne, spróbuj:

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)

Plac zabaw

Typowa wydajność:

$ 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

6
Dziękuję za wyjaśnienie opinii zamiast stwierdzenia, że ​​coś jest nie tak
Fjolnir Dvorak

wiesz co to nie obsługuje? widziałem Test może być w jsonie dwa razy i przyjmuje 2. wystąpienie
tooptoop4

@ tooptoop4 należałoby napisać niestandardowy dekoder, aby ostrzec o zduplikowanych polach - dodając nieefektywność dekodera - wszystko po to, aby obsłużyć scenariusz, który nigdy się nie wydarzy. Żaden standardowy koder JSON nigdy nie wytworzyłby duplikatów pól.
colm.anseo

20

Znalazłem następujący przykład z dokumentów bardzo pomocny (źródło tutaj ).

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

Kluczem tutaj jest to, że OP chciał dekodować

type test_struct struct {
    Test string
}

... w takim przypadku upuszczalibyśmy const jsonStreami zamieniliśmy Messagestrukturę na 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)
    }
}

Aktualizacja : Dodałbym również, że ten post zawiera również świetne dane na temat odpowiadania za pomocą JSON. Autor wyjaśnia struct tags, czego nie byłem świadomy.

Ponieważ JSON zwykle nie wygląda {"Test": "test", "SomeKey": "SomeVal"}, ale raczej {"test": "test", "somekey": "some value"}możesz zrestrukturyzować swoją strukturę w następujący sposób:

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

... a teraz twój program obsługi parsuje JSON używając „some-key” w przeciwieństwie do „SomeKey” (którego będziesz używać wewnętrznie).


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)
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.