ginでフォーム、JSONのリクエストを受け取る

Go言語はじめたでMacにGoが動く環境を作ったので、今日はGoのwebフレームワークであるginをさわる。
フォーム、JSONのリクエストから取得できる値をたしかめていく。

ginをインストールして動かす

まずはginのインストールから始める。
go mod initでモジュールの初期化をする。

$ go mod init github.com/nansystem/gin-sample

main.goを作成し、go runでwebサーバーをたちあげる。

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run()
}
$ go run main.go 

curlでアクセスすると、JSONでレスポンスが返ってきた。
これでginの起動確認ができた。

$ curl http://localhost:8080/ping \
  -H 'Content-Type:application/json'
{"message":"pong"}

URLクエリ文字列の取得

c.QueryでURLクエリ文字列を取得できる。取得できる値の型はstringである。
c.DefaultQueryの第二引数でデフォルト値を設定できる。

	r.GET("/get", func(c *gin.Context) {
		s := c.Query("str")
		n := c.Query("num")
		b := c.Query("bool")
		l := c.DefaultQuery("limit", "10")
		message := fmt.Sprintf("s: %v, n: %v, b: %v, l: %v", s, n, b, l)
		c.String(http.StatusOK, message)
  })

curlのオプション--getで後続の値をクエリ文字列とし、--data-urlencodeでURLエンコーディングしてリクエストする。
GET /get?str=%E6%96%87%E5%AD%97%E5%88%97&num=123&bool=true HTTP/1.1

$ curl http://localhost:8080/get \
  --get \
  --data-urlencode 'str=文字列' \
  --data-urlencode 'num=123' \
  --data-urlencode 'bool=true'
s: 文字列, n: 123, b: true, l: 10

URLパスの取得

:nameのようにコロンに続けてパス名を指定することでc.Param("name")で値を取得する。
*actionのようにアスタリスクに続けてパス名を指定すると、c.Param("action")で残りの全てのパスを取得する。

	r.GET("/path/:name/*action", func(c *gin.Context) {
		name := c.Param("name")
		action := c.Param("action")
		message := name + " is " + action
		c.String(http.StatusOK, message)
	})
$ curl http://localhost:8080/path/hoge/fuga/piyo/
hoge is /fuga/piyo/

フォームのPOST

c.PostFormでフォームの値を取得できる。取得できる値の型はstringである。
c.DefaultPostFormの第二引数でデフォルト値を設定できる。

	r.POST("/post", func(c *gin.Context) {
		s := c.PostForm("str")
		n := c.PostForm("num")
		b := c.PostForm("bool")
		l := c.DefaultPostForm("limit", "10")
		message := fmt.Sprintf("s: %v, n: %v, b: %v, l: %v", s, n, b, l)
		c.String(http.StatusOK, message)
	})
$ curl -X POST http://localhost:8080/post \
  --data-urlencode 'str=文字列P'  \
  --data-urlencode 'num=1234'  \
  --data-urlencode 'bool=false' \
  --data-urlencode 'limit=20'
s: 文字列P, n: 1234, b: false, l: 20

JSONのPOST

structを用意する。

type JsonRequest struct {
	FieldStr  string `json:"field_str"`
	FieldInt  int    `json:"field_int"`
	FieldBool bool   `json:"field_bool"`
}

JSONからstructへ値をマッピングするのはBindJSONShouldBindJSON関数のいずれかを使う。
BindJSONはHTTPステータスを400にして、Content-Typeヘッダーをtext/plainにするようだ。
application/jsonでレスポンスを返したいから、ShouldBindJSONを使う。
gin.Hmap[string]interface{}のショートカット。

This sets the response status code to 400 and the Content-Type header is set to text/plain; charset=utf-8. https://github.com/gin-gonic/gin#model-binding-and-validation

gin.H is a shortcut for map[string]interface{}
https://github.com/gin-gonic/gin#xml-json-yaml-and-protobuf-rendering

	r.POST("/postjson", func(c *gin.Context) {
		var json JsonRequest
		if err := c.ShouldBindJSON(&json); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		c.JSON(http.StatusOK, gin.H{"str": json.FieldStr, "int": json.FieldInt, "bool": json.FieldBool})
	})

空のJSONを渡してもエラーにならない。

$ curl -X POST http://localhost:8080/postjson \
  -H 'content-type: application/json' \
	-d '{}'
{"bool":false,"int":0,"str":""}

structに存在していないフィールドを渡しても無視されてエラーにはならない。

$ curl -X POST http://localhost:8080/postjson \
  -H 'content-type: application/json' \
	-d '{ "hoge": 1 }'
{"bool":false,"int":0,"str":""}

型が違えばエラーになる。

 $ curl -X POST http://localhost:8080/postjson \
  -H 'content-type: application/json' \
  -d '{ "field_str": 1 }'
{"error":"json: cannot unmarshal number into Go struct field JsonRequest.field_str of type string"}

型があっていればstructに値が無事マッピングされる。

$ curl -X POST http://localhost:8080/postjson \
  -H 'content-type: application/json' \
  -d '{ "field_str": "文字だ", "field_int": 12, "field_bool": true }'
{"bool":true,"int":12,"str":"文字だ"}