golangでBMIを計算するAPIサーバを実装

身長 [cm]と体重 [kg]を受け取りBMIを返すAPIサーバをつくる

JSON形式の文字列をgolangで読み取る

golangJSON形式の文字列をパースして構造体のフィールド変数に代入したり,構造体からJSON形式の文字列に変換したりする方法.

まずはJSON形式を扱うためのライブラリ"encoding/json"をインポートする.

import (
    "encoding/json"
    "fmt"
    "log"
    "math"
)

次に,身長と体重の値が入っているJSON形式の文字列を考える.

{
    "height": 170,
    "weight": 50
}

そして,この形式のJSONを読み取るための構造体を定義する.

type HeightWeight struct {
    Height int `json:"height"`
    Weight int `json:"weight"`
}

各変数は[変数名] [型名] [文字列]と定義する.[文字列]json:"[key]"とする.

そしてJSON形式の文字列 から構造体に値を代入するコードは以下のようになる.

// JSON形式の文字列を定義
jsontest := `{"height": 170, "weight": 50}`
// HeightWeight型の変数の定義
var heightweight HeightWeight
// jsontestを読み取りheightweightの変数に値を書き込む
if err := json.Unmarshal([]byte(jsontest), &heightweight); err != nil {
    log.Fatal(err)
}

このコードは

  1. jsontestJSON形式の文字列を定義
  2. HeightWeight構造体のインスタンスheightweightをつくる
  3. json.Unmarshalheightweightのフィールド変数にjsontestの値を代入する.

という流れになっている.

構造体からJSON形式の文字列

先ほど読み取ったheightweightを使ってBMIを計算してBMIの値をフィールド変数に持つ構造体のインスタンスを返す関数を作る.Bmisという名前の構造体とcalc_bmiという引数にHeightWeightインスタンスを受け取りBmisを返す関数を定義.

type Bmis struct {
    Bmi float64 `json:"bmi"`
}

func calc_bmi(hw HeightWeight) Bmis {
    var bmi Bmis
    bmi.Bmi = hw.Weight / math.Pow(hw.Height / 100, 2)
    return bmi
}

heightweightを使ってBmisインスタンスbmiを作成し,bmiからJSON形式の文字列を作る.

// heightweightを使ってBmisのインスタンスbmiを定義
bmi := calc_bmi(heightweight)
// bmiを使ってJSON形式のバイト配列jsonBytesを定義
jsonBytes, err := json.MarshalIndent(bmi, "", "    ")
if err != nil {
    log.Fatal(err)
}

byte型配列のjsonBytesを文字列に変換するにはstring(jsonBytes)で可能.

これらをまとめたコード

github.com

これを実行したら以下のようになり,heightweightの各フィールド変数Height, WeightJSON文字列に含まれていた値が格納されていることを確認できる.

HeightWeight構造体
{170 50}

BMIのJSON
{
    "bmi": 17.301038062283737
}

golangでAPIサーバの実装

全体のコード

github.com

handler関数の動作は以下のようになっている.

  1. HTTPリクエストを受け取り,Bodybyte配列を読み込みbodyに格納
  2. bodyからHeightWeightインスタンスhwを生成
  3. hwからcalc_bmi関数でBmisインスタンスbmiを生成
  4. bmiからjson.MarshalIndentを使ってJSON文字列を生成
  5. HTTPレスポンスのBodyにbmiJSON文字列を書き込む

HTTPリクエストのBody読み込み部分

r.ContentLengthでHTTPリクエストのバイト長が分かるので,その長さのbyte配列bodyを用意する.

body := make([]byte, r.ContentLength)

そしてHTTPリクエストのBodyのバイト配列をbodyの要素に代入する.

length, err := r.Body.Read(body)

あとはbodyを使ってjson.Unmarshalできる.

HTTPレスポンス部分

HTTPレスポンスのヘッダーにのcontent-typeapplication/jsonをセットしてJSON形式であることを明示する.そして,BodyBMIJSON形式文字列を書き込めばOK.

// JSON形式であることを明示
w.Header().Set("content-type", "application/json")
// 計算したbmiをJSON形式で返す
fmt.Fprintf(w, string(jsonBytes))
fmt.Fprintf(w, "\n")

curlコマンドで動作確認

go run main.goで実行してサーバを起動して

curlコマンドでAPIをたたく

curl -v -H "Content-Type: application/json" -X POST -d '{"height":140, "weight":50} http://localhost:5000'

すると結果が返ってくる.

Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (#0)
> POST / HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.63.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 27
>
* upload completely sent off: 27 out of 27 bytes
< HTTP/1.1 200 OK
< Date: Sun, 09 Jun 2019 12:30:31 GMT
< Content-Length: 34
< Content-Type: text/plain; charset=utf-8
<
{
    "bmi": 25.510204081632658
}
* Connection #0 to host localhost left intact

bmiJSONが返ってきている.やったぜ.

参考