Обробка запиту на запит JSON у переході


250

Таким чином, у мене є наступне, що здається неймовірно хитким, і я думав про себе, що Go має краще розроблені бібліотеки, ніж ця, але я не можу знайти приклад Go-файлу, що обробляє POST-запит даних JSON. Всі вони утворюють пости.

Ось приклад запиту: curl -X POST -d "{\"test\": \"that\"}" http://localhost:8082/test

А ось код із вбудованими журналами:

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

Має бути кращий спосіб, правда? Я просто наткнувся на пошук того, що може бути найкращою практикою.

(Go також відомий як Golang в пошукових системах, і тут згадується, щоб інші могли його знайти.)


3
якщо ви користуєтесь curl -X POST -H 'Content-Type: application/json' -d "{\"test\": \"that\"}", то req.Form["test"]повертайтеся"that"
Вініцій

@Vinicius чи є докази цього?
diralik

Відповіді:


388

Будь ласка, використовуйте json.Decoderзамість 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
Чи можете ви поясніть, чому?
Райан Бігг

86
Для початку здається, що це може обробляти потік, а не потребувати того, щоб ви все це завантажували в буфер. (Я інший Джо BTW)
Джо,

7
Цікаво, як виглядало б правильне поводження з помилками в цьому випадку. Я не думаю, що панікувати недійсного json не годиться.
codepushr

15
Я не думаю, що вам потрібно defer req.Body.Close()з Документів: "Сервер закриє тіло запиту. Обробник ServeHTTP не потребує." Також відповісти на @thisisnotabus, з docs: "Для запитів сервера Орган запиту завжди не відповідає нулю, але поверне EOF негайно, коли жодного тіла немає" golang.org/pkg/net/http/#Request
Drew LeSueur

22
Я б запропонував не використовувати json.Decoder. Він призначений для потоків об'єктів JSON, а не одного об'єкта. Він не є більш ефективним для одного об'єкта JSON, оскільки він зчитує весь об'єкт у пам'яті. Це має і мінус, що якщо сміття буде включено після об'єкта, воно не скаржиться. Залежно від декількох факторів, json.Decoderорганізм може не повністю прочитати, і з'єднання буде непридатним для повторного використання.
Кале Б

85

Вам потрібно читати з req.Body. ParseFormМетод читання від req.Bodyі потім розбір його в стандартному форматі HTTP закодованої. Те, що ви хочете, - це прочитати тіло та проаналізувати його у форматі JSON.

Ось оновлений ваш код.

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

Дякую! Я бачу, куди я пішов не так. Якщо ви зателефонували req.ParseForm(), що я робив у попередніх спробах вирішити цю проблему, перед тим, як спробувати і прочитати req.Body, здається, очистити тіло і unexpected end of JSON inputвикинути, коли ви переходите Unmarshal(принаймні в 1.0.2)
TomJ

1
@Daniel: Коли я закручую -X POST -d "{\" tes \ ": \" that \ "}" localhost: 8082 / test , log.Println (t.Test) повертається порожнім. Чому? Або з цього питання, якщо розмістити будь-який інший JSON, він повернеться порожнім
Somesh

Ваш запит POST невірний. tes! = тест.
Вдячний,

Це приємний простий приклад!
15412с

Це хороша порада, але щоб бути зрозумілим, відповіді, що стосуються використання json.NewDecoder(req.Body), також є правильними.
Багатий

59

Я зводив з розуму цю точну проблему. Мій JSON Marshaller і Unmarshaller не заповнювали мою структуру Go. Потім я знайшов рішення на https://eager.io/blog/go-and-json :

"Як і у всіх структурах Go, важливо пам’ятати, що зовнішні програми, такі як Marshaller JSON, видно лише поля з першою літерою з великої літери."

Після цього мій Marshaller та Unmarshaller прекрасно працювали!


Додайте кілька фрагментів із посилання. Якщо його застаріло, приклади будуть втрачені.
030

47

Існує дві причини, чому json.Decoderслід віддавати перевагу json.Unmarshalтому, що не розглядається у найпопулярнішій відповіді 2013 року:

  1. Лютий 2018 року go 1.10ввів новий метод json.Decoder.DisallowUnknownFields (), який вирішує проблему виявлення небажаного вводу JSON
  2. req.Bodyє вже io.Reader. Прочитавши весь вміст, а потім виконавши json.Unmarshalресурси відходів, якщо потік був, скажімо, блоком 10 МБ недійсного JSON. Розбір тіла запиту, з json.Decoder, як він потоки в, викликає помилку раннього розбору, якщо виникне недійсний JSON. Обробка потоків вводу / виводу в режимі реального часу є кращим способом .

Звернення до деяких коментарів користувачів щодо виявлення поганого введення користувача:

Щоб застосувати обов'язкові поля та інші перевірки санітарії, спробуйте:

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)

Ігровий майданчик

Типовий вихід:

$ 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
Дякую, що ви пояснили думки, а не просто
заявляли,

ти знаєш, що це не справляється? Я бачив Тест може бути в json двічі, і він приймає 2-е місце
tooptoop4

@ tooptoop4 потрібно було б написати спеціальний декодер, щоб попередити про повторювані поля - додавання неефективності до декодера - все для обробки сценарію, який ніколи не відбудеться. Жоден стандартний кодер JSON ніколи не видасть повторюваних полів.
colm.anseo

20

Наступний приклад із документів дуже корисний (джерело тут ).

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

Ключовим тут є те, що ОП шукала розшифрувати

type test_struct struct {
    Test string
}

... в такому випадку ми би скинули const jsonStreamі замінили Messageструктуру на 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)
    }
}

Оновлення : Я також додам, що ця публікація надає чудові дані про реагування з JSON. Автор пояснює struct tags, чого я не знав.

Оскільки JSON зазвичай не схожий {"Test": "test", "SomeKey": "SomeVal"}, а скоріше {"test": "test", "somekey": "some value"}, ви можете структурувати структуру так:

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

... і тепер ваш обробник буде розбирати JSON, використовуючи "якийсь ключ", на відміну від "SomeKey" (який ви будете використовувати внутрішньо).


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)
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.