การจัดการคำขอโพสต์ JSON ใน Go


250

ดังนั้นฉันจึงมีสิ่งต่อไปนี้ซึ่งดูเหมือนว่าแฮ็คอย่างไม่น่าเชื่อและฉันคิดกับตัวเองว่า Go มีการออกแบบห้องสมุดที่ดีกว่านี้ แต่ฉันไม่พบตัวอย่างของ Go ที่จัดการคำขอ POST ของข้อมูล JSON พวกเขาทั้งหมดฟอร์ม POST

นี่คือตัวอย่างคำขอ: 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

@Vinicius มีหลักฐานอะไรบ้างไหม?
diralik

คำตอบ:


389

โดยใช้แทนjson.Decoderjson.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
คุณช่วยอธิบายได้ไหม
Ryan Bigg

86
สำหรับการเริ่มต้นดูเหมือนว่านี่สามารถจัดการกระแสแทนที่จะต้องการให้คุณโหลดมันทั้งหมดลงในบัฟเฟอร์ด้วยตัวเอง (ฉันคือ Joe BTW คนอื่น)
Joe

7
ฉันสงสัยว่าการจัดการข้อผิดพลาดที่เหมาะสมจะเป็นอย่างไรในกรณีนี้ ฉันไม่คิดว่าเป็นความคิดที่ดีที่จะตื่นตระหนกกับ json ที่ไม่ถูกต้อง
codepushr

15
ฉันไม่คิดว่าคุณต้องไปdefer req.Body.Close()จากเอกสาร: "เซิร์ฟเวอร์จะปิดเนื้อหาคำขอ ServeHTTP Handler ไม่จำเป็นต้องทำ" นอกจากนี้เพื่อตอบ @thisisnotabus จากเอกสาร: "สำหรับคำขอเซิร์ฟเวอร์เซิร์ฟเวอร์คำขอเนื้อหาไม่ใช่ศูนย์ แต่จะส่งคืน EOF ทันทีเมื่อไม่มีเนื้อหา" golang.org/pkg/net/http/#Request
Drew LeSueur

22
json.Decoderผมจะแนะนำไม่ได้ใช้ มันมีไว้สำหรับสตรีมของวัตถุ JSON ไม่ใช่วัตถุเดียว มันไม่ได้มีประสิทธิภาพมากขึ้นสำหรับวัตถุ JSON เดียวเพราะอ่านวัตถุทั้งหมดในหน่วยความจำ มันมีข้อเสียว่าถ้าขยะถูกรวมไว้หลังจากวัตถุมันจะไม่บ่น ขึ้นอยู่กับปัจจัยบางประการjson.Decoderอาจไม่สามารถอ่านเนื้อหาได้อย่างสมบูรณ์และการเชื่อมต่อจะไม่สามารถนำมาใช้ซ้ำได้
Kale B

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 / ทดสอบ log.Println (t.Test) จะกลับมาว่างเปล่า ทำไม หรือสำหรับเรื่องนั้นถ้าโพสต์ JSON อื่นมันจะว่างเปล่า
Somesh

คำขอ POST ของคุณผิด tes! = ทดสอบ ขอบคุณที่ 5 ปีที่ผ่านมา: /
Rambatino

นี่เป็นตัวอย่างง่ายๆที่ดี!
15412s

นี่เป็นคำแนะนำที่ดี แต่เพื่อความชัดเจนคำตอบที่อ้างถึงการใช้json.NewDecoder(req.Body)ก็ถูกต้องเช่นกัน
Rich

59

ฉันกำลังขับรถบ้าด้วยปัญหาที่แน่นอนนี้ JSON Marshaller และ Unmarshaller ของฉันไม่ได้เติมโครงสร้าง Go ของฉัน จากนั้นฉันพบวิธีแก้ปัญหาที่https://eager.io/blog/go-and-json :

"เช่นเดียวกับ structs ทั้งหมดใน Go เป็นสิ่งสำคัญที่ต้องจำไว้ว่ามีเพียงเขตข้อมูลที่มีอักษรตัวใหญ่ตัวแรกที่ปรากฏให้เห็นในโปรแกรมภายนอกเช่น JSON Marshaller"

หลังจากนั้น Marshaller และ Unmarshaller ของฉันทำงานได้อย่างสมบูรณ์แบบ!


โปรดรวมตัวอย่างบางส่วนจากลิงค์ หากได้รับการคัดค้านตัวอย่างจะหายไป
030

47

มีเหตุผลสองประการที่json.Decoderควรได้รับความนิยมมากกว่าjson.Unmarshalเพราะไม่ได้กล่าวถึงในคำตอบที่ได้รับความนิยมมากที่สุดในปี 2013:

  1. กุมภาพันธ์ 2018 go 1.10แนะนำวิธีการใหม่json.Decoder.DisallowUnknownFields () ซึ่งเน้นความกังวลของการตรวจหา JSON-input ที่ไม่ต้องการ
  2. req.Bodyio.Readerมีอยู่แล้ว อ่านเนื้อหาทั้งหมดแล้วทำการjson.Unmarshalสิ้นเปลืองทรัพยากรหากสตรีมพูดให้เป็นบล็อกขนาด 10MB ของ JSON ที่ไม่ถูกต้อง การแยกวิเคราะห์เนื้อหาคำร้องขอด้วยjson.Decoderขณะที่สตรีมเข้ามาจะทำให้เกิดข้อผิดพลาดในการแยกวิเคราะห์ก่อนหากพบ JSON ที่ไม่ถูกต้อง การประมวลผลสตรีม I / O แบบเรียลไทม์เป็นวิธีที่ต้องการ

การพูดถึงความคิดเห็นของผู้ใช้เกี่ยวกับการตรวจสอบอินพุตของผู้ใช้ที่ไม่ดี:

หากต้องการบังคับใช้ฟิลด์บังคับและการตรวจสอบด้านสุขาภิบาลอื่น ๆ ให้ลอง:

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
ขอบคุณสำหรับการอธิบายความคิดเห็นแทนที่จะระบุว่ามีบางอย่างไม่ดี
Fjolnir Dvorak

คุณรู้ไหมว่ามันไม่ได้จัดการอะไร ฉันเห็นการทดสอบสามารถเป็น 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)
    }
}

กุญแจสำคัญในที่นี้คือ OP ต้องการถอดรหัส

type test_struct struct {
    Test string
}

... ในกรณีนี้เราจะดรอปconst jsonStreamและแทนที่Messagestruct ด้วย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 โดยใช้ "some-key" ซึ่งตรงข้ามกับ "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)
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.