Обробка запиту на запит 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.