echo を使って /test
で json を受け入れるサーバを立ち上げます。
package main import ( "context" "fmt" "net/http" "github.com/labstack/echo/v4" ) type Message struct { Name []byte `json:"name"` } func main() { e := echo.New() e.POST("/test", func(ctx echo.Context) error { var m Message if err := ctx.Bind(&m); err != nil { return err } fmt.Println(string(m.Name)) return nil }) go e.Start(":8080") ctx := context.Background() select { case <-ctx.Done(): break } return }
このサーバに対して以下のリクエストを送ると、
curl localhost:8080/test -X POST -d '{"name": "ZnVnYQ=="}' -H "Content-Type: application/json"
サーバは fuga
を標準出力します。元々は base64 encode された文字列 ZnVnYQ==
だったのにどこかで decode されているようです。
結論
ここの Bind
のなかでやっています。
if err := ctx.Bind(&m); err != nil { return err }
詳しく追っていくとこの Bind
では BindBody
を呼び出します。BindBody
では ContentType
Header をみて json である時に JSONSerializer.Deserialize
を実行します。
func (b *DefaultBinder) BindBody(c Context, i interface{}) (err error) { req := c.Request() if req.ContentLength == 0 { return } ctype := req.Header.Get(HeaderContentType) switch { case strings.HasPrefix(ctype, MIMEApplicationJSON): if err = c.Echo().JSONSerializer.Deserialize(c, i); err != nil { switch err.(type) { case *HTTPError: return err default: return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) } }
JSONSerializer.Deserialize
は裏側で json.unmarshal
を呼び出しでデシリアライズをするのですが、その先では以下の処理を行なっています。
ここでは Bind 先の変数の型をチェックしており、reflect.Slice
かつ reflect.Uint8
の場合は base64.StdEncoding.Decode(b, s)
をしています。
switch v.Kind() { default: d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex())}) case reflect.Slice: if v.Type().Elem().Kind() != reflect.Uint8 { d.saveError(&UnmarshalTypeError{Value: "string", Type: v.Type(), Offset: int64(d.readIndex())}) break } b := make([]byte, base64.StdEncoding.DecodedLen(len(s))) n, err := base64.StdEncoding.Decode(b, s) if err != nil { d.saveError(err) break } v.SetBytes(b[:n])
今回の echo サーバーでは Message 構造体の Name は []byte
であり、
type Message struct { Name []byte `json:"name"` }
Go において byte
は uint8
の type alias であるため、Name
はこの if 文の中に入ることができ、結果として ZnVnYQ==
は base64 decode されたのちに Message.Name
に格納されることになります。
// byte is an alias for uint8 and is equivalent to uint8 in all ways. It is // used, by convention, to distinguish byte values from 8-bit unsigned // integer values. type byte = uint8
ついでに試してみる
base64 encode して curl
curl localhost:8080/test -X POST -d '{"name": "ZnVnYQ=="}' -H "Content-Type: application/json"
普通に表示される。
fuga
base64 decode せずに curl
curl localhost:8080/test -X POST -d '{"name": "test"}' -H "Content-Type: application/json"
このような表示になります。(test を無理やり base64 decode しようとする)
��-
application/json
をつけずに curl
curl localhost:8080/test -X POST -d '{"name": "ZnVnYQ=="}'
何も表示されない。(body を json 解釈しないからかな)
ついでに
このような仕様が json や http に規定されているのか?を ChatGPT さんに聞いてみたけど、そんなことはないみたい。(Go の encoding/json に閉じた話とのこと)