php小编百草为您介绍Golang gin框架中如何接收JSON数据和图像。在开发过程中,我们经常需要处理前端传递过来的JSON数据以及图像文件。Golang的gin框架提供了简单易用的方法来接收和处理这些数据。通过本文的介绍,您将了解到如何在gin框架中使用结构体来接收JSON数据,以及如何处理上传的图像文件。让我们一起来探索吧!
问题内容
我有请求处理程序的代码:
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"})
}
}
我对此处理程序进行了单元测试:
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)
})
}
}
在测试过程中出现以下错误: 错误#01:数字文字中的无效字符“-”
这是请求正文(我用 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--
} 414 [] false localhost:8080 map[] map[] map[] 192.0.2.1:1234 http://localhost:8080/user/account/updateprofile }
首先,我只有字符串作为 json 数据并将其转换为字节。当出现错误时,我使用 json.marshal 转换了 json 数据,但没有成功。我想用 c.bind 解析 json 数据并用 c.formfile 解析给定图像,这可能吗?
更新。我替换了代码先获取头像,然后通过bind结构获取json。现在我有 eof 错误。
解决方法
tl;dr
我们可以定义一个结构体来同时接收json数据和图像文件(注意字段标签):
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
}
gin可以自动解析multipart/form-data
中的其他内容类型吗?
例如,xml
或 yaml
。
当前的 gin (@1.9.0) 不会自动解析 multipart/form-data
中的 xml
或 yaml
。 json
很幸运,因为当目标字段是结构体或映射时,gin 恰好使用 json.unmarshal
解析表单字段值。请参阅 binding.setwithpropertype。
我们可以像这样自己解析它们(updaterequest.event
是表单中的字符串值):
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
请求中的 yaml
或 application/x-yaml
请求中的 xml
混淆。仅当 xml
内容或 yaml
内容位于 中时才需要这样做多部分/表单-data
请求) .
其他
c.bindjson
不能用于从multipart/form-data
读取 json,因为它假定请求正文以有效的 json 开头。但它是从一个边界开始的,看起来像--30b24345d...
。这就是为什么它失败并显示错误消息invalid character '-' in numeric literal
。- 在
c.formfile("avatar")
之后调用c.bindjson
不起作用,因为调用c.formfile
会使整个请求正文被读取。并且c.bindjson
后面没有什么可读的。这就是您看到 eof 错误的原因。
单个可运行文件中的演示
这是完整的演示。使用 go test 运行 ./... -v -count 1
:
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 := `
playerOne
strike (miss)
`
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)
}
感谢您的阅读!
以上就是Golang gin接收json数据和图像的详细内容,更多请关注编程网其它相关文章!