「API」カテゴリーアーカイブ

Ginを使ったGo API開発の初歩(cookie編)

以下の記事でGinをインストールしてGo APIの実装を始めました。今日はcookieを実装します。

Ginを使ったGo API開発の初歩

  • cookieとは
  • cookieの実装
  • APIの確認

cookieとは


cookieとは、ウェブブラウザに保存される情報のことを指します。

サイトにログイン後、再度同じサイトにアクセスするとログインした状態になっていることがあります。これはcookieとしてユーザー情報が保存されているからです。他にも閲覧したページに関連する広告を出したりと、様々な場面で利用されています。

本記事では、Ginによるcookie操作を確認します。

cookieの実装


Ginによるcookieの設定と取得には以下のメソッドを利用します。それぞれのメソッドの引数・戻り値の型については、gin-GoDocに記載されています。

  • cookieの設定・・・gin.Context.Cookie
  • cookieの取得・・・gin.Context.SetCookie

上記のメソッドを使ったcookie.goを以下に示します。

package main

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

func main() {

    router := gin.Default()

		authorized := router.Group("/admin", gin.BasicAuth(gin.Accounts{
			"koratta":    "koratta",
			"koratta2":   "koratta2",
		}))

		authorized.GET("/login", func(c *gin.Context) {
			user := c.MustGet(gin.AuthUserKey).(string)
			c.SetCookie("user", user, 3600, "/", "localhost", false, true)
		})

		authorized.GET("/hello-cookie", func(c *gin.Context) {
			user, user_cookie_err := c.Cookie("user")
			if user_cookie_err != nil {
				user = "Guest"
			}
			c.JSON(200, gin.H{ "message": "Hello " + user })
		})

    router.Run()
}

/loginにログインしたユーザー情報をcookieに保存し、/hello-cookieにアクセスすると”user”cookieの値を表示します。違いがわかるように、直接/hello-cookieにアクセスした場合は”Guest”が表示されるようにしています。

16行目c.SetCookie("user", user, 3600, "/", "localhost", false, true)の引数について解説します。

  • 第一引数・・・cookieのKeyを指定します。ここでは”user”を指定しています。
  • 第二引数・・・第一引数のKeyに対応するValueを設定します。ここでは、string型変数userを設定しています。userは、15行目のc.MustGet(gin.AuthUserKey).(string)で、ログインしたユーザー名を代入しています。
  • 第三引数・・・cookie情報を保存する時間を指定します。ここでは1時間で設定しています。
  • 第四引数・・・cookie情報が扱える範囲のパスを指定します。ここではルートパスを指定しています。
  • 第五引数・・・cookie情報が扱えるドメインを指定します。ここではlocalhostで指定しています。
  • 第六引数・・・httpsでもcookieを利用するか指定します。trueの場合はhttpsでも利用できます。
  • 第七引数・・・httpリクエストのみcookieを利用できるようにするか指定します。trueの場合JavaScriptからcookieを利用することができません。trueにすることで、クロスサイトスクリプティング(XSS)攻撃を緩和することができます。

20行目のuser, user_cookie_err := c.Cookie("user")は、”user” KeyのValue値を取得しています。Cookieメソッドの戻り値はstring, errorと二つあります。

APIの確認


実際にAPIの挙動を確認してみます。

まずはlocalhost:8080/hello-cookieに直接アクセスします。以下の通り、cookieに保存していないので”Hello Guest”が返ってきます。

$ curl -u koratta:koratta localhost:8080/admin/hello-cookie
{"message":"Hello Guest"}

次に、localhost:8080/loginに一度アクセスしてから、localhost:8080/hello-cookieにアクセスします。/loginでユーザー情報をcookieに設定しているので、以下の通り”Hello koratta”が表示されます。

以上です。

Ginを使ったGo API開発の初歩(ルーターグループ化編)

以下の記事でGinをインストールしてGo APIの実装を始めました。ルーティングをグループ化することができるので、今日はその機能を実装してみたいと思います。

Ginを使ったGo API開発の初歩

  • ルーターグループ化とは
  • ルーターグループ化機能の実装
  • APIの確認

ルーターグループ化とは


Ginにはルーティングをグループ化する機能があります。

この機能によって、同じAPIでもルーティング毎に仕様を変更することができます。よく見かけるのは、API毎にバージョンを分ける使い方です。APIの仕様を突然変更すると、そのAPIを使って自動化しているシステムは影響を受けてしまいます。ユーザーが任意のタイミングで切り替えられるように、バージョン毎に提供することがよくあります。

ルーターグループ化機能の実装


サンプルコードを参考に、ルーターグループ化機能を実装します。

Groupメソッドを使用してv1, v2のルーテンググループを作成し、それぞれが返すmessageの値を変更します。クライアントは、Groupに指定されたURL(以下の場合は/v1,/v2)でルーティングを使い分けます。

package main

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

func helloV1() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.JSON( 200, gin.H{ "message": "hello v1", } )
	}
}

func helloV2() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.JSON( 200, gin.H{ "message": "hello v2", } )
	}
}

func main() {
	r := gin.Default()
	v1 := r.Group("/v1")
	{
		v1.POST("/hello", helloV1() )
	}

	v2 := r.Group("/v2")
	{
		v2.POST("/hello", helloV2() )
	}
	r.Run()
}

APIの確認


ルーティング毎にAPIの出力が変わることを確認します。

$ go run router_group.go

別ターミナルで確認すると、以下の通り/v1/helloと/v2/helloでmessageの値が変わっていることがわかります。

$ curl --request POST localhost:8080/v1/hello
{"message":"hello v1"}
$ curl --request POST localhost:8080/v2/hello
{"message":"hello v2"}

ちなみに、GETリクエストは定義していないのでNotFoundが返ってきます。

$ curl --request GET localhost:8080/v2/hello
404 page not found

以上です。

Ginを使ったGo API開発の初歩(BindQuery編)

以下の記事でGinをインストールしてGo APIの実装を始めました。今日はBindQueryを導入し、フォームに応じて出力を変更するAPIを実装します。

Ginを使ったGo API開発の初歩

  • BindQueryとは
  • BindQueryの実装
  • APIの確認

BindQueryとは


BindQueryとは、golangにおいてクエリパラメータをバインドする機能です。

この機能によってフォームに応じた返答を行うことができます。例えば、日付をフォームに入力したら、その日付のデータのみ返すことができます。このようにBindQueryを使うことで、より使い勝手のいいAPIを作成できるようになります。

BindQueryの実装


BindQueryを実装します。

グループに所属するメンバーを表示するbind_query.goを作成します。存在するグループはgroup1とgroup2を用意し、クライアントはnameフォームの値にグループ名を入力することで表示するグループを切り替えます。指定したグループ名が存在しない場合は、httpリクエストステータスを400にして「There are no groups」を返します。

package main

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

type Group struct {
	Name    string `form:"name"`
}

var GROUP1_MEMBER_LIST = [...] gin.H{ gin.H{"name": "koratta01"}, gin.H{"name": "koratta02"} }
var GROUP2_MEMBER_LIST = [...] gin.H{ gin.H{"name": "ratta01"}, gin.H{"name": "ratta02"} }

var GROUP_MEMBER_LIST = gin.H{
	"group1":  gin.H{ "member": GROUP1_MEMBER_LIST },
	"group2":  gin.H{ "member": GROUP2_MEMBER_LIST },
}

func main() {
	route := gin.Default()
	route.Any("/member", getGroupMember)
	route.Run(":8080")
}

func getGroupMember(c *gin.Context) {
	var group Group
	if c.BindQuery(&group) == nil {
		if member_list, exists := GROUP_MEMBER_LIST[group.Name]; exists {
			c.JSON(200, member_list)
		} else {
			c.String(400, "There are no groups")
		}
	}
}

APIの確認


作成したbind_query.goを実行してAPIを確認します。

$ go run bind_query.go

別ターミナルで以下のコマンドを実行すると、グループ名に所属するメンバーリストが取得できることを確認できます。

$ curl --request GET 'http://localhost:8080/member?name=group1'
{"member":[{"name":"koratta01"},{"name":"koratta02"}]}
$ curl --request GET 'http://localhost:8080/member?name=group2'
{"member":[{"name":"ratta01"},{"name":"ratta02"}]}
$ curl --request GET 'http://localhost:8080/member?name=group3'
There are no groups

bind_query.goのログを確認すると以下の通り、所属するグループだけステータスが200となっています。

[GIN] 2020/07/14 - 22:38:15 | 200 |     199.543µs |             ::1 | GET      "/member?name=group1"
[GIN] 2020/07/14 - 22:38:21 | 200 |      51.177µs |             ::1 | GET      "/member?name=group2"
[GIN] 2020/07/14 - 22:38:26 | 400 |      57.617µs |             ::1 | GET      "/member?name=group3"

Postで送信したときの挙動も確認すると、Getではメンバーリストが返ってきたグループ名でも「There are no groups」が返ってきています。これはBindQueryがPostデータをバインドしないからです。

$ curl --request POST 'http://localhost:8080/member?name=group3'
There are no groups
[GIN] 2020/07/14 - 22:45:10 | 400 |      56.774µs |             ::1 | POST     "/member?name=group3"

以上です。

Ginを使ったGo API開発の初歩(Basic認証編)

以下の記事でGinをインストールしてGo APIの実装を始めました。今日はBasic認証を導入し、ユーザーIDとパスワードが必要なAPIを実装します。

Ginを使ったGo API開発の初歩

  • Basic認証とは
  • Basic認証の実装
  • APIの確認

Basic認証とは


Basic認証とは、httpリクエストにおける認証システムです。以下の画面のようにユーザーIDとパスワードを求められたことがあると思います。あらかじめ登録されたユーザーIDとパスワードを入力するとアクセスできる仕組みです。

Basic認証の実装


実装サンプルを参考に、Basic認証を実装します。

リンク先のサンプルコードでは、認証されたユーザーの秘匿情報を返すように実装されていますが、今回はわかりやすく認証されたら適当な値を返すだけのbasic_auth.goを作成します。

package main

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

func main() {
  r := gin.Default()

  authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
    "koratta":    "koratta",
    "koratta2":   "koratta2",
  }))

  authorized.GET("/hello", func(c *gin.Context) {
    user := c.MustGet(gin.AuthUserKey).(string)
    c.JSON(200, gin.H{ "message": "Hello " + user })
  })

  r.Run(":8080")
}

gin.Accountsでは、[ユーザー名]:[パスワード]のJson形式で認証情報を定義しています。その認証情報と/adminプレフィックスによってauthorizedグループを作成します。このGroupからauthorized.Getで作成されたエンドポイントは、定義された認証情報を持つユーザーのみがアクセスできます。admin/helloエンドポイントは、ユーザー名をメッセージに追加してJsonフォーマットで表示します。

APIの確認


basic_auth.goを実行して、APIを確認します。

$ go run basic_auth.go 
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /admin/hello              --> main.main.func1 (4 handlers)
[GIN-debug] Listening and serving HTTP on :8080

curlコマンドを使ってAPIにリクエストを行います。-uオプションを使うことでユーザー名とパスワードを入力することができます。以下のように、basic_auth.goに登録されているkoratta, koratta2のみリクエストが返ってきてることがわかります。

$ curl -u koratta:koratta localhost:8080/admin/hello
{"message":"Hello koratta"}
$ curl -u koratta2:koratta2 localhost:8080/admin/hello
{"message":"Hello koratta2"}
$ curl -u koratta3:koratta3 localhost:8080/admin/hello
$ 

Ginのログを確認しても、3回目にリクエストを行ったkoratta3のみ、ステータスコード401を返していることがわかります。

[GIN] 2020/07/12 - 16:23:51 | 200 |     116.147µs |             ::1 | GET      "/admin/hello"
[GIN] 2020/07/12 - 16:24:19 | 200 |        67.2µs |             ::1 | GET      "/admin/hello"
[GIN] 2020/07/12 - 16:24:28 | 401 |      52.325µs |             ::1 | GET      "/admin/hello"

 

Ginを使ったGo API開発の初歩

今日はGinを使って、簡単なGo APIを動かすところまでやってみたいと思います。初歩ということで、まずはインストールとQuick Startを実施します。

  • Ginとは
  • Ginのインストール
  • Ginを動かす

Ginとは


Ginとは、Golangで開発されたWebフレームワークで、パフォーマンスに優れたAPI機能を提供します。(GinのGitHubから引用しています。)

Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to httprouter. If you need performance and good productivity, you will love Gin.

引用元:https://github.com/gin-gonic/gin#contents

日本語訳されたドキュメントが以下リンク先に用意されています。非常にありがたいです。。。

https://gin-gonic.com/ja/docs/

Ginのインストール


以下のコマンドでGinの必要なパッケージをインストールします。インストール後、ソースコードにimport "github.com/gin-gonic/gin"を記述して使えるようになります。

$ go version
go version go1.14.3 darwin/amd64
$ go get -u github.com/gin-gonic/gin

Ginを動かす


GinのQuick Startを実施して、httpリクエストを受け付けるとJsonを返すAPIを作成します。

リンク先のソースコードをコピーして、getting_start.goを作成します。

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() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

実行すると以下のエラーが発生しました。

$ go run getting_start.go 
getting_start.go:3:8: cannot find module providing package github.com/gin-gonic/gin: working directory is not part of a module

ワークディレクトリにmoduleがないというエラーなので、以下のコマンドでモジュールを作成します。

$ go mod init
go: creating new go.mod: module gin
$ ls
getting_start.go	go.mod

再度実行します。

$ go run getting_start.go 
go: finding module for package github.com/gin-gonic/gin
go: found github.com/gin-gonic/gin in github.com/gin-gonic/gin v1.6.3
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

localhost:8080/pingにhttpリクエストを送信すると、Jsonを返します。

$ curl localhost:8080/ping
{"message":"pong"}

getting_start.goの出力結果にレスポンスタイムとステータスが記録されていました。

[GIN] 2020/07/12 - 01:45:41 | 200 |      38.361µs |             ::1 | GET      "/ping"

以上です。まずは動かしてみたということで、これから色々触ってみたいと思います。