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

SwaggerEditorで生成したGo API Serverの処理を実装する

  • はじめに
  • APIの全体像
  • MongoDBの作成
    • Dockerfileの作成
    • Dockerビルド
  • Go-Serverの作成
    • APIの実装
      • CreateUserメソッドの実装
      • GetUserByNameメソッドの実装
      • go/api_user.goの全体
    • Dockerfileの作成
    • Dockerビルド
  • APIの確認
    • docker-compose.yamlの作成
    • コンテナの起動
    • curlによるAPIの確認

はじめに


SwaggerEditorはOpenAPI specを様々な言語に変換することができます。以下の記事では、SwaggerEditorを使って簡単なOpenAPI specを作成し、Go-Serverに変換するところまで紹介しました。

MacにSwaggerEditorをインストールしてOpenAPI specを実装する

しかし、あくまでもAPIの雛形であって、中身の処理が実装されているわけではありません。本記事では、上記の記事で作成したGo-Serverに処理を実装します。

APIの全体像


本記事で作成するAPIの全体像について紹介します。

APIの仕様は以下の通りです。DBにはMongoDBを利用し、apiとdbはそれぞれコンテナで起動します。

  • POST /v2/user/create・・・Context-TypeでJson形式のユーザー情報を受け付け、DBに保管します。
  • GET /v2/user/{username}・・・URLに指定したusernameとマッチするレコードをDBから取得し、表示します。

MongoDBの作成


Dockerfileの作成


MongoDBのDockerfileを作成します。

ディレクトリ構造は以下の通りです。

$ tree mongo/
mongo/
├── Dockerfile
├── init.sh
└── users.json

今回は揮発性のDBコンテナにします。コンテナ起動時に初期データ(users.json)を流し込むinit.shも併せて作成します。

1. 初期データの作成


コンテナ起動時に流し込む初期データをjson形式で作成します。

[
  { "username":	"koratta", "email": "koratta@example.com" },
  { "username":	"koratta2", "email": "koratta2@example.com" }
]

2. 初期実行ファイルの作成


1で作成したJsonファイルをMongoDBに保管するためのスクリプトinit.shを作成します。

mongoimport --authenticationDatabase admin --username root --password password --db api --collection users --drop --file /docker-entrypoint-initdb.d/users.json --jsonArray

3. Dockerfileの作成


init.shをコンテナ起動時に実行するmongoベースのDockerfileを作成します。コンテナ起動時に実行するため、/docker-entrypoint-initdb.dにコピーします。

FROM mongo:latest
COPY users.json /docker-entrypoint-initdb.d/
COPY init.sh /docker-entrypoint-initdb.d/

Dockerビルド


上記で作成したDockerfileをビルドし、コンテナイメージを作成します。

$ docker build -t api-mongo-db:latest mongo/

Go-Serverの作成


APIの実装


APIリクエストのルーティング情報は、go/routers.goに記述されています。

~~go/router.goより抜粋~~
var routes = Routes{ Route{ "Index", "GET", "/v2/", Index, }, Route{ "CreateUser", strings.ToUpper("Post"), "/v2/user/create", CreateUser, }, Route{ "GetUserByName", strings.ToUpper("Get"), "/v2/user/{username}", GetUserByName, }, }

/v2/user/createに対してPOSTされたらCreateUserメソッドを、/v2/user/{username}に対してGETされたらGetUserBynameメソッドを呼び出すようになっています。それぞれのメソッドはgo/api_user.goで定義されているので、それぞれを実装します。

CreateUserメソッドの実装


CreateUserメソッドを以下に示します。

func CreateUser(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//ヘッダー情報の取得
	if r.Header.Get("Content-Type") != "application/json" {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	length, err := strconv.Atoi(r.Header.Get("Content-Length"))
	body := make([]byte, length)
	length, err = r.Body.Read(body)
	var insert_record User
	err = json.Unmarshal(body[:length], &insert_record)

	//DBへのInsert
	connect := getConnect()
	_, err = connect.Database("api").Collection("users").InsertOne(context.Background(), insert_record)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusOK)
}

9~11行目でPOSTされたJson形式のユーザー情報を取得しています。

	length, err := strconv.Atoi(r.Header.Get("Content-Length"))
	body := make([]byte, length)
	length, err = r.Body.Read(body)

12~13行目でUser構造体のinsert_recordに変換しています。

	var insert_record User
	err = json.Unmarshal(body[:length], &insert_record)

このUser構造体は、OpenAPI specから生成されていて、go/model_user.goに記述されています。

package swagger

type User struct {

	Username string `json:"username,omitempty"`

	Email string `json:"email,omitempty"`
}

16~17行目でMongoDBに保管しています。getConnectメソッドはMongoDBとの接続を確立するメソッドで、go/api_user.goの全体に載せています。

	connect := getConnect()
	_, err = connect.Database("api").Collection("users").InsertOne(context.Background(), insert_record)

GetUserByNameメソッドの実装


GetUserByNameメソッドを以下に示します。

func GetUserByName(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//検索ユーザー情報の取得
	user := strings.Replace(r.URL.Path, "/v2/user/", "", 1)

	//user情報の取得
	var output User
	connect := getConnect()
	filter := bson.D{{"username", user }}
	err := connect.Database("api").Collection("users").FindOne(context.Background(), filter).Decode(&output)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	//Jsonの出力
	json.NewEncoder(w).Encode(output)

	w.WriteHeader(http.StatusOK)
}

5行目で検索条件となるユーザー名を取得しています。今回のAPIの仕様は、URLのパスに指定されたユーザー名の情報を表示するので、URLパスからユーザー名を取得しています。

user := strings.Replace(r.URL.Path, "/v2/user/", "", 1)

9~11行目で、URLに指定されたユーザー名を基にMongoDBから情報を取得しています。

	connect := getConnect()
	filter := bson.D{{"username", user }}
	err := connect.Database("api").Collection("users").FindOne(context.Background(), filter).Decode(&output)

go/user_api.goの全体


user_api.goの全体は以下の通りです。

/*
 * Koratta Test API
 *
 * This is sample api
 *
 * API version: 1.0.0
 * Contact: koratta@example.com
 * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
 */

package swagger

import (
	"net/http"
	"log"
	"time"
	"encoding/json"
	"context"
	"strconv"
	"strings"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/bson"
)

func CreateUser(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//ヘッダー情報の取得
	if r.Header.Get("Content-Type") != "application/json" {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	length, err := strconv.Atoi(r.Header.Get("Content-Length"))
	body := make([]byte, length)
	length, err = r.Body.Read(body)
	var insert_record User
	err = json.Unmarshal(body[:length], &insert_record)

	//DBへのInsert
	connect := getConnect()
	_, err = connect.Database("api").Collection("users").InsertOne(context.Background(), insert_record)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusOK)
}

func GetUserByName(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	//検索ユーザー情報の取得
	user := strings.Replace(r.URL.Path, "/v2/user/", "", 1)

	//user情報の取得
	var output User
	connect := getConnect()
	filter := bson.D{{"username", user }}
	err := connect.Database("api").Collection("users").FindOne(context.Background(), filter).Decode(&output)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	//Jsonの出力
	json.NewEncoder(w).Encode(output)

	w.WriteHeader(http.StatusOK)
}

func getConnect() *mongo.Client{
	//MongoDBの認証情報
	credential := options.Credential{
		Username: "root",
		Password: "password",
	}
	//MongoDBへの接続
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	connect, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://db:27017").SetAuth(credential))
	if err != nil { log.Fatal(err) }

	return connect
}

Dockerfileの作成


Dockerfileを作成します。

ディレクトリ構造は以下の通りです。

$ tree api
api
├── Dockerfile
└── src
    ├── api
    │   └── swagger.yaml
    ├── go
    │   ├── README.md
    │   ├── api_user.go
    │   ├── logger.go
    │   ├── model_user.go
    │   └── routers.go
    └── main.go

src配下のファイルは、MacにSwaggerEditorをインストールしてOpenAPI specを実装するで作成したソースコードです。

Dockerfileは以下の通りです。

FROM golang:latest
# gouserの作成
RUN useradd -m -s /bin/bash gouser
# コンテナ作業ディレクトリの変更
WORKDIR /go/src/api
RUN chown gouser:gouser /go/src/api
# モジュールのダウンロード
RUN go get -u github.com/gorilla/mux &&\
    go get -u "go.mongodb.org/mongo-driver/mongo"
# ホストOSの ./src の中身を作業ディレクトリにコピー
COPY --chown=gouser:gouser ./src .
# go build
RUN go build -o koratta-api &&\
    chown gouser:gouser api
# gouserに変更
USER gouser
# API実行コマンドの実行
CMD ["/go/src/api/koratta-api"]

必要なモジュールのgorilla/muxとmongo-driver/mongoをインストールし、gouserに実行権限を与えてgouserで実行するようにしています。root権限でも当然実行できます。

注意点は、main.goのimportパッケージのsw "go"部分です。コンテナの場合デフォルトで$GOPATH=/go/srcなので、上記のDockerfileの場合sw "api/go"になります。

APIの確認


docker-compose.yamlの作成


MongoDBとAPIのコンテナを起動するためのdocker-compose.yamlを作成します。

version: '3'
services:
  db:
    container_name: db
    image: api-mongo-db:latest
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: password
    ports:
      - "27017:27017"
  api:
    container_name: api
    image: api:latest
    depends_on:
      - db
    links:
      - db
    ports:
      - "8080:8080"

コンテナの起動


docker-composeを使ってコンテナを起動します。

$ docker-compose up -d

curlによるAPIの確認


作成したAPIの挙動を確認します。

初期データとして保管されているレコードをAPI経由で確認すると、以下の通り、指定したユーザー名に該当する情報を取得できます。

$ curl -X GET "http://localhost:8080/v2/user/koratta"
{"username":"koratta","email":"koratta@example.com"}

保管されていないユーザーは、当然取得できません。

$ curl -X GET "http://localhost:8080/v2/user/koratta5
$

従って、APIを使ってユーザー情報を保管します。

$ curl -X POST "http://localhost:8080/v2/user/create" -H "accept: application/xml" -H "Content-Type: application/json" -d "{ \"username\": \"koratta5\", \"email\": \"koratta5@example.com\"}"

再度確認すると、ユーザー情報を確認することができます。/v2/user/createのAPI経由でDBに保管できていることが確認できました。

$ curl -X GET "http://localhost:8080/v2/user/koratta5"
{"username":"koratta5","email":"koratta5@example.com"}

以上です。

MacにSwaggerEditorをインストールしてOpenAPI specを実装する

  • Swaggerとは
  • Swaggerの利用方法
  • SwaggerEditorのインストール
  • OpenAPI specの実装
  • OpenAPI specからGo Serverの実行

Swaggerとは


Swaggerは、API開発フレームワークのオープンソースツールです。OpenAPI仕様のREST API設計・構築・ドキュメント化を行うことができます。

OpenAPIとは、REST APIを記述するためのフォーマットです。OpenAPI specとして、APIの雛形をJsonもしくはYaml形式で記述することができます。OpenAPIの仕様については、こちらに記載されています。

Swaggerには、以下のツールが含まれています。

  • Swagger Editor – ブラウザベースでOpenAPI specを実装することができるツールです。
  • Swagger UI – OpenAPI specからドキュメントをレンダリングするツールです。
  • Swagger Codegen – OpenAPI specからサーバースタブとクライアントライブラリを生成するツールです。

本記事では、Swagger Editorで簡単なOpenAPI specを記述し、Goで実行するところまで紹介します。

Swaggerの利用方法


Swaggerを利用するには、以下の方法があります。

  • オンライン利用・・・リンク先にアクセスしてSwaggerEditorを利用する方法です。
  • ローカル利用・・・SwaggerEditorをダウンロードして利用する方法で、以下の二通りがあります。
    • ローカルファイルによる実行・・・ダウンロードしたSwaggerファイルをブラウザで表示する方法です。
    • Dockerによる実行・・・DockerHubからコンテナイメージをプルして実行する方法です。手順はこちらです。

本記事では、ローカルのSwaggerEditorファイルを利用する方法を紹介します。

SwaggerEditorのインストール


1. GitHubからソースをダウンロードする


$ curl -L -O https://github.com/swagger-api/swagger-editor/archive/v3.11.7.zip

ダウンロードのバージョン情報はこちらです。

2. ファイルを解凍する


$ unzip v3.11.7.zip 

3. index.htmlをブラウザで表示する


$ open ./swagger-editor-3.11.7/index.html

以下の画面が表示されます。

OpenAPI specの実装


SwaggerEditorを利用してOpenAPI specを作成します。デフォルトで作成されているyamlファイルはボリュームがあるので、一部を抜き取って編集しました。

swagger: "2.0"
info:
  description: "This is sample api"
  version: "1.0.0"
  title: "Koratta Test API"
  contact:
    email: "koratta@example.com"
host: "localhost:8080"
basePath: "/v2"
tags:
- name: "user"
  description: "User's API"
schemes:
- "http"
paths:
  /user/create:
    post:
      tags:
      - "user"
      summary: "Create user"
      description: "This can only be done by the logged in user."
      operationId: "createUser"
      produces:
      - "application/xml"
      - "application/json"
      parameters:
      - in: "body"
        name: "body"
        description: "Created user object"
        required: true
        schema:
          $ref: "#/definitions/User"
      responses:
        default:
          description: "successful operation"
  /user/{username}:
    get:
      tags:
      - "user"
      summary: "Get user by user name"
      description: ""
      operationId: "getUserByName"
      produces:
      - "application/xml"
      - "application/json"
      parameters:
      - name: "username"
        in: "path"
        description: "The name that needs to be fetched. "
        required: true
        type: "string"
      responses:
        "200":
          description: "successful operation"
          schema:
            $ref: "#/definitions/User"
        "400":
          description: "Invalid username supplied"
        "404":
          description: "User not found"
definitions:
  User:
    type: "object"
    properties:
      username:
        type: "string"
      email:
        type: "string"
    xml:
      name: "User"

/user/createにユーザー名とメールアドレスをPOSTし、/user/[ユーザー名]でユーザー情報をGETするAPIです。

yamlを作成すると、画面右側にドキュメントが作成されます。

OpenAPI specからGo Serverの実行


上記で作成したOpenAPI specからGo Serverを実行します。

Swaggerには各言語への変換機能がついており、golangも対応しています。

1.「Generate Server」タブから「go-server」を選択する


2. ダウンロードしたファイルを解凍して実行する


$ go run go-server-server/main.go

そのまま実行すると以下のエラーが出ます。

unexpected directory layout:

main.goのインポートパッケージに相対ディレクトリが使用されているので、$GOPATHからのディレクトリに変更する必要があります。以下の場合は、$GOPATH/go-server-server/goになります。

main.goの24行目
変更前: sw "./go"
変更後: sw "go-server-server/go"

3. API用curlコマンドを生成する


swaggerはAPIを使用するためのcurlコマンドを生成することができます。

3-1. 使用したいAPIをクリックし、「Try it out」をクリックする


3-2. 使用したいAPIをクリックし、「Try it out」をクリックする


3-3. 生成されたコマンドをコピーする


同じ要領でGETのAPIについても生成しておきます。

4. APIを利用する


手順3で生成したcurlコマンドで、APIを利用します。

$ curl -X POST "http://localhost:8080/v2/user/create" -H "accept: application/xml" -H "Content-Type: application/json" -d "{ \"username\": \"koratta\", \"email\": \"koratta@example.com\"}"
$ curl -X GET "http://localhost:8080/v2/user/koratta" -H "accept: application/xml"
$ curl -X GET "http://localhost:8080/v2/user/koratta2" -H "accept: application/xml"
$

Go Server側でAPIのログが確認できます。

$ go run src/go-server-server/main.go 
2020/07/21 23:43:37 Server started
2020/07/21 23:45:20 POST /v2/user/create CreateUser 3.095µs
2020/07/21 23:48:19 GET /v2/user/koratta GetUserByName 4.464µs
2020/07/21 23:49:55 GET /v2/user/koratta2 GetUserByName 2.33µs

あれ、作成していないkoratta2も同じ結果だ。。と一瞬思いましたが、そもそもAPIの雛形を作成しただけなので、当たり前でした。CreateUserとGetUserByNameのメソッドを確認すると、以下のようにステータス200を返すだけとなっていました。ここからAPIの中身を開発していくようなイメージですかね。

~~go/api_user.goより抜粋~~
func CreateUser(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
}

func GetUserByName(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(http.StatusOK)
}

以上です。なんて便利なツールなんだ。。。

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"

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