Heim >Backend-Entwicklung >Golang >Golang Gin empfängt JSON-Daten und Bilder

Golang Gin empfängt JSON-Daten und Bilder

PHPz
PHPznach vorne
2024-02-09 13:09:17614Durchsuche

Golang gin接收json数据和图像

php-Editor Baicao stellt Ihnen vor, wie Sie JSON-Daten und Bilder im Golang-Gin-Framework empfangen. Während des Entwicklungsprozesses müssen wir häufig vom Frontend übergebene JSON-Daten und Bilddateien verarbeiten. Das Gin-Framework von Golang bietet einfache und benutzerfreundliche Methoden zum Empfangen und Verarbeiten dieser Daten. In der Einführung dieses Artikels erfahren Sie, wie Sie Strukturen im Gin-Framework verwenden, um JSON-Daten zu empfangen, und wie Sie hochgeladene Bilddateien verarbeiten. Lasst uns gemeinsam erkunden!

Frageninhalt

Ich habe den Code für den Anfragehandler:

func (h *handlers) updateprofile() gin.handlerfunc {
    type request struct {
        username    string `json:"username" binding:"required,min=4,max=20"`
        description string `json:"description" binding:"required,max=100"`
    }

    return func(c *gin.context) {
        var updaterequest request

        if err := c.bindjson(&updaterequest); err != nil {
            var validationerrors validator.validationerrors

            if errors.as(err, &validationerrors) {
                validateerrors := base.bindingerror(validationerrors)
                c.abortwithstatusjson(http.statusbadrequest, gin.h{"error": validateerrors})
            } else {
                c.abortwitherror(http.statusbadrequest, err)
            }

            return
        }

        avatar, err := c.formfile("avatar")
        if err != nil {
            c.abortwithstatusjson(http.statusbadrequest, gin.h{
                "error": "image not contains in request",
            })
            return
        }

        log.print(avatar)

        if avatar.size > 3<<20 { // if avatar size more than 3mb
            c.abortwithstatusjson(http.statusbadrequest, gin.h{
                "error": "image is too large",
            })
            return
        }

        file, err := avatar.open()
        if err != nil {
            c.abortwitherror(http.statusinternalservererror, err)
        }

        session := sessions.default(c)
        id := session.get("sessionid")
        log.printf("id type: %t", id)

        err = h.userservice.updateprofile(fmt.sprintf("%v", id), file, updaterequest.username, updaterequest.description)
        if err != nil {
            c.abortwithstatusjson(http.statusbadrequest, gin.h{})
            return
        }

        c.indentedjson(http.statusnocontent, gin.h{"message": "succesfull update"})
    }
}

Ich habe diesen Handler einem Unit-Test unterzogen:

func testuser_updateprofile(t *testing.t) {
    type testcase struct {
        name               string
        image              io.reader
        username           string
        description        string
        expectedstatuscode int
    }

    router := gin.default()

    memstore := memstore.newstore([]byte("secret"))
    router.use(sessions.sessions("session", memstore))

    usergroup := router.group("user")
    repo := user.newmemory()
    service := userservice.new(repo)
    userhandlers.register(usergroup, service)

    testimage := make([]byte, 100)
    rand.read(testimage)
    image := bytes.newreader(testimage)

    testcases := []testcase{
        {
            name:               "request with image",
            image:              image,
            username:           "bobik",
            description:        "wanna be sharik",
            expectedstatuscode: http.statusnocontent,
        },
        {
            name:               "request without image",
            image:              nil,
            username:           "sharik",
            description:        "wanna be bobik",
            expectedstatuscode: http.statusnocontent,
        },
    }

    for _, tc := range testcases {
        t.run(tc.name, func(t *testing.t) {
            body := &bytes.buffer{}
            writer := multipart.newwriter(body)

            imagewriter, err := writer.createformfile("avatar", "test_avatar.jpg")
            if err != nil {
                t.fatal(err)
            }

            if _, err := io.copy(imagewriter, image); err != nil {
                t.fatal(err)
            }

            data := map[string]interface{}{
                "username":    tc.username,
                "description": tc.description,
            }
            jsondata, err := json.marshal(data)
            if err != nil {
                t.fatal(err)
            }

            jsonwriter, err := writer.createformfield("json")
            if err != nil {
                t.fatal(err)
            }

            if _, err := jsonwriter.write(jsondata); err != nil {
                t.fatal(err)
            }

            writer.close()

            // creating request
            req := httptest.newrequest(
                http.methodpost,
                "http://localhost:8080/user/account/updateprofile",
                body,
            )
            req.header.set("content-type", writer.formdatacontenttype())
            log.print(req)

            w := httptest.newrecorder()
            router.servehttp(w, req)

            assert.equal(t, tc.expectedstatuscode, w.result().statuscode)
        })
    }
}

Der folgende Fehler ist beim Testen aufgetreten: Fehler Nr. 01: Ungültiges Zeichen „-“ im numerischen Literal

Dies ist der Anfragetext (ich drucke ihn mit log.print(req)):

&{POST http://localhost:8080/user/account/updateprofile HTTP/1.1 1 1 map[Content-Type:[multipart/form-data; boundary=30b24345de9d8d83ecbdd146262d86894c45b4f3485e4615553621fd2035]] {--30b24345de9d8d83ecbdd146262d86894c45b4f3485e4615553621fd2035
Content-Disposition: form-data; name="avatar"; filename="test_avatar.jpg"
Content-Type: application/octet-stream


--30b24345de9d8d83ecbdd146262d86894c45b4f3485e4615553621fd2035
Content-Disposition: form-data; name="json"

{"description":"wanna be bobik","username":"sharik"}
--30b24345de9d8d83ecbdd146262d86894c45b4f3485e4615553621fd2035--
} <nil> 414 [] false localhost:8080 map[] map[] <nil> map[] 192.0.2.1:1234 http://localhost:8080/user/account/updateprofile <nil> <nil> <nil> <nil>}

Zuerst habe ich nur Zeichenfolgen als JSON-Daten und konvertiere sie in Bytes. Als der Fehler auftrat, habe ich die JSON-Daten mit json.marshal konvertiert, ohne Erfolg. Ich möchte JSON-Daten mit c.bind und ein gegebenes Bild mit c.formfile analysieren. Ist das möglich?

Aktualisiert. Ich habe den Code ersetzt, um zuerst den Avatar und dann den JSON über die Bindungsstruktur abzurufen. Jetzt habe ich einen Eof-Fehler.

Lösung

tl;dr

Wir können eine Struktur definieren, um sowohl JSON-Daten als auch Bilddateien zu empfangen (beachten Sie die Feldbezeichnungen):

var updaterequest struct {
    avatar *multipart.fileheader `form:"avatar" binding:"required"`
    user   struct {
        username    string `json:"username" binding:"required,min=4,max=20"`
        description string `json:"description" binding:"required,max=100"`
    } `form:"user" binding:"required"`
}

// c.shouldbind will choose binding.formmultipart based on the content-type header.
// we call c.shouldbindwith to make it explicitly.
if err := c.shouldbindwith(&updaterequest, binding.formmultipart); err != nil {
    _ = c.abortwitherror(http.statusbadrequest, err)
    return
}
Kann

gin andere Inhaltstypen in multipart/form-data automatisch analysieren?

Zum Beispiel xmlyaml.

Der aktuelle Gin (@1.9.0) löst multipart/form-data 中的 xmlyamljson 很幸运,因为当目标字段是结构体或映射时,gin 恰好使用 json.unmarshal in nicht automatisch auf. json hat Glück, denn Gin verwendet zufällig json.unmarshal, um Formularfeldwerte zu analysieren, wenn das Zielfeld eine Struktur oder Karte ist. Siehe binding.setwithpropertype

.

updaterequest.eventWir können sie selbst so analysieren (

ist der Zeichenfolgenwert im Formular):

var event struct {
    at     time.time `xml:"time" binding:"required"`
    player string    `xml:"player" binding:"required"`
    action string    `xml:"action" binding:"required"`
}

if err := binding.xml.bindbody([]byte(updaterequest.event), &event); err != nil {
    _ = c.abortwitherror(http.statusbadrequest, err)
    return
}
application/xml 请求中的 yamlapplication/x-yaml 请求中的 xml 混淆。仅当 xml 内容或 yaml 内容位于 中时才需要这样做多部分/表单-data (bitte nicht mit

anfragen) .

Andere
  1. c.bindjson 不能用于从 multipart/form-data 读取 json,因为它假定请求正文以有效的 json 开头。但它是从一个边界开始的,看起来像 --30b24345d...。这就是为什么它失败并显示错误消息 invalid character '-' in numeric literalc.bindjson kann nicht zum Lesen von JSON aus
  2. verwendet werden, da davon ausgegangen wird, dass der Anforderungstext mit gültigem JSON beginnt. Aber es beginnt an einer Grenze, die wie folgt aussieht: --30b24345d.... Aus diesem Grund schlägt der Vorgang mit der Fehlermeldung ungültiges Zeichen „-“ im numerischen Literal fehl.
  3. c.formfile("avatar") 之后调用 c.bindjson 不起作用,因为调用 c.formfile 会使整个请求正文被读取。并且 c.bindjsonDanach gibt es nichts mehr zu lesen
  4. . Aus diesem Grund wird der EOF-Fehler angezeigt.

Demo in einer einzigen ausführbaren Datei

go test 运行 ./... -v -count 1Dies ist die vollständige Demo. Verwenden Sie

:

package m

import (
    "bytes"
    "crypto/rand"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "net/http/httptest"
    "testing"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "github.com/stretchr/testify/assert"
)

func handle(c *gin.Context) {
    var updateRequest struct {
        Avatar *multipart.FileHeader `form:"avatar" binding:"required"`
        User   struct {
            Username    string `json:"username" binding:"required,min=4,max=20"`
            Description string `json:"description" binding:"required,max=100"`
        } `form:"user" binding:"required"`
        Event string `form:"event" binding:"required"`
    }

    // c.ShouldBind will choose binding.FormMultipart based on the Content-Type header.
    // We call c.ShouldBindWith to make it explicitly.
    if err := c.ShouldBindWith(&updateRequest, binding.FormMultipart); err != nil {
        _ = c.AbortWithError(http.StatusBadRequest, err)
        return
    }
    fmt.Printf("%#v\n", updateRequest)

    var event struct {
        At     time.Time `xml:"time" binding:"required"`
        Player string    `xml:"player" binding:"required"`
        Action string    `xml:"action" binding:"required"`
    }

    if err := binding.XML.BindBody([]byte(updateRequest.Event), &event); err != nil {
        _ = c.AbortWithError(http.StatusBadRequest, err)
        return
    }

    fmt.Printf("%#v\n", event)
}

func TestMultipartForm(t *testing.T) {
    testImage := make([]byte, 100)

    if _, err := rand.Read(testImage); err != nil {
        t.Fatal(err)
    }
    image := bytes.NewReader(testImage)

    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)

    imageWriter, err := writer.CreateFormFile("avatar", "test_avatar.jpg")
    if err != nil {
        t.Fatal(err)
    }

    if _, err := io.Copy(imageWriter, image); err != nil {
        t.Fatal(err)
    }

    if err := writer.WriteField("user", `{"username":"bobik","description":"wanna be sharik"}`); err != nil {
        t.Fatal(err)
    }

    xmlBody := `<?xml version="1.0" encoding="UTF-8"?>
<root>
   <time>2023-02-14T19:04:12Z</time>
   <player>playerOne</player>
   <action>strike (miss)</action>
</root>`
    if err := writer.WriteField("event", xmlBody); err != nil {
        t.Fatal(err)
    }

    writer.Close()

    req := httptest.NewRequest(
        http.MethodPost,
        "http://localhost:8080/update",
        body,
    )
    req.Header.Set("Content-Type", writer.FormDataContentType())
    fmt.Printf("%v\n", req)

    w := httptest.NewRecorder()
    c, engine := gin.CreateTestContext(w)
    engine.POST("/update", handle)
    c.Request = req
    engine.HandleContext(c)

    assert.Equal(t, 200, w.Result().StatusCode)
}

Vielen Dank fürs Lesen! 🎜

Das obige ist der detaillierte Inhalt vonGolang Gin empfängt JSON-Daten und Bilder. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:stackoverflow.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen