LoginSignup
18

More than 5 years have passed since last update.

Protocol BuffersでAPI通信 サーバー編【golang】 

Last updated at Posted at 2017-05-11

protocol buffersとは

API通信などで、Jsonのようなテキスト形式で通信を行わずに、protocol buffersはバイナリ形式で通信を行います。
proto file(事前にスキーマを定義するファイル)から、サーバー側とクライアント側に、シリアライズ・デシリアライズ用のファイルを書き出し、それらを利用してデータの送受信を行います。

Proto fileの定義

今回は都道府県のリストを返すAPIを作成して行きたいと思います。
まず、以下のようにproto fileを定義して行きいます。

prefectures.proto

syntax = "proto3";

option java_package = "com.takusemba.gouda.protos";
option java_outer_classname = "Prefectures";
option go_package = "proto";

package prefectures;

message Prefecture {
  int64 id                   = 1; // ID
  string name                = 2; // 名前
  string romaji              = 3; // 読み方(ローマ字)
}

message GetPrefecturesResponse {
  repeated Prefecture prefectures = 1;
}

実際のprotoのコードはこちらに上がっています。

protoからgoのファイルを書き出す

先ほど定義したproto fileから、server側のシリアライズ・デシリアライズ用のファイルを書き出します。

今回はgoのfileを書き出したいますが、どの言語でも書き出すことができます。

$ brew install protobuf
$ go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
$ protoc --proto_path=protos --go_out=proto protos/*.proto //<- fileの書き出し

実行するとこのようなgoファイルが書き出されているのがわかると思います。

APIを実装する

Prefectureのモデルを定義します。

models/prefecture.go

type Prefecture struct {
    Id       int         `json:"id"        bson:"_id"`
    Name     string      `json:"name"      bson:"name"`
    Romaji   string      `json:"romaji"    bson:"romaji"`
}

type Prefectures struct {
    Prefectures     []Prefecture   `json:"prefectures"`
}

APIの実装はechoを使って実装したいと思います。

services/prefecture.go

package main

import (
    "github.com/labstack/echo"
    "github.com/labstack/echo/middleware"
    proto1 "github.com/golang/protobuf/proto"
    "net/http"
    "github.com/TakuSemba/camembert/models"
    "github.com/TakuSemba/camembert/proto"
)

func main() {
    e := echo.New()
    //middleware
    e.Use(middleware.Logger())
    e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
        AllowOrigins: []string{"*"},
        AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
    }))

    e.GET("/prefectures", func(c echo.Context) error {

        // DBからPrefectureのリストと取ってくる
        prefectures, _ := models.GetPrefectures()

        res := ToGetPrefecturesResponse(prefectures)
        data, _ := proto1.Marshal(&res)

        return c.Blob(http.StatusOK, "application/protobuf", data)
    })

    e.Start(":80")
}



func ToGetPrefecturesResponse(prefectures []models.Prefecture) (res proto.GetPrefecturesResponse) {
    prefecturesProto := ToPrefectures(prefectures)
    res = proto.GetPrefecturesResponse{
        Prefectures: prefecturesProto,
    }
    return
}

func ToPrefectures(prefectures []models.Prefecture) (res []*proto.Prefecture) {
    for _, pref := range prefectures {
        prefProto := ToPrefecture(&pref)
        res = append(res, &prefProto)
    }
    return
}

func ToPrefecture(pref *models.Prefecture) (chProto proto.Prefecture) {
    chProto = proto.Prefecture{
        Id:        int64(pref.Id),
        Name:      pref.Name,
        Romaji:    pref.Romaji,
    }
    return
}

以下でgo serverを起動するとhttp://your_url/prefecture で都道府県リストのバイナリを返すようになります。

$ go run main.go

テストコードを書く

実際にAPIを叩いて見て、意図した通りのバイナリが返却されているかをテストします。

prefecture_test.go

package services

import (
    "io/ioutil"
    "log"
    "net/http"
    "testing"

    proto1 "github.com/golang/protobuf/proto"
    "github.com/k0kubun/pp"
    "github.com/TakuSemba/camembert/proto"
)

const (
    reqUrl = "http://your_url"
)

func TestGetPrefectures(t *testing.T) {

    url := reqUrl + "/prefectures"
    log.Println("[TEST] ", url)

    resp, _ := http.Get(url)
    defer resp.Body.Close()

    resData, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        t.Log("error", err)
    }

    log.Println("Response:", resp.StatusCode)
    if resp.StatusCode == 200 {
        resProto := proto.GetPrefecturesResponse{}
        if err = proto1.Unmarshal(resData, &resProto); err != nil {
            log.Println("unmarshaling error :", err)
        }
        pp.Println(resProto)
    } else {
        resProto := proto.ErrorResult{}
        if err = proto1.Unmarshal(resData, &resProto); err != nil {
            log.Println("unmarshaling error :", err)
        }
        pp.Println(resProto)
    }
}


以下のコマンドでテストを走らせることができます。

$ go test prefecture_test.go -v

最終的に、mongo + docker + golangで実装して、こちらのレポジトリ
で公開しているので、ぜひ見て見てくだい。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18