GCP Cloud Functions<GET/POST> の使い方【初心者】

[AWS Lambda + API Gateway ＜GET＞ の使い方【初心者】](https://qiita.com/koshi_an/items/fd1b97b35e52bab5f7e9)と
[AWS Lambda + API Gateway ＜POST＞ の使い方【初心者】](https://qiita.com/koshi_an/items/98b12216a69258358d37)の
GCP Cloud Function版です。

#Cloud Functions の作成
AWSの場合、API Gateway と Lambda で WEB API を作成しましたが
GCPの場合、トリガーに「HTTP」を選択することにより、Cloud Functions のみで対応が可能です。

ランタイムは、「Go 1.11」を選択しています。
ポイントは、packageに「main」を指定せずに、「main」関数も指定しません。

以下のソースを「インラインエディタ」で記述します。
実行する関数は、「HelloWorld」です。

```Go:function.go
package p

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
)

type Calc struct {
	Value1 int `json:"Value1"`
	Value2 int `json:"Value2"`
}

type ReturnCalc struct {
	Calc     // 埋め込み(embedded) 継承的な意味合い
	Ans  int `json:"Ans"`
}

// メソッド定義
func (c Calc) Add() int {
	return c.Value1 + c.Value2
}
func (c *ReturnCalc) Add() {
	c.Ans = c.Value1 + c.Value2
}

func HelloWorld(w http.ResponseWriter, r *http.Request) {
	// クエリパラメータ取得してみる
	var err_flg = 0
	var data Calc

	if r.Method == http.MethodGet {
		for key, values := range r.URL.Query() {
			for _, v := range values {
				switch key {
				case "Value1":
					i, err := strconv.Atoi(v)
					if err != nil {
						fmt.Fprintln(w, "Value1が数値じゃない")
						err_flg = 1
						break
					}
					data.Value1 = i
				case "Value2":
					i, err := strconv.Atoi(v)
					if err != nil {
						fmt.Fprintln(w, "Value2が数値じゃない")
						err_flg = 1
						break
					}
					data.Value2 = i
				default:
					fmt.Fprintln(w, "パラメータエラー")
					err_flg = 1
					break
				}
			}
		}
		if err_flg == 0 {
			fmt.Fprintf(w, "%d + %d = %d \n", data.Value1, data.Value2, data.Add())
		} else {
			w.WriteHeader(http.StatusInternalServerError)
		}

	} else if r.Method == http.MethodPost {
		// CORS対策
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
        
		// Bodyデータを扱う場合には、事前にパースを行う(POSTの場合、必ず実施する)
		r.ParseForm()

		if r.Header.Get("Content-Type") != "application/json" {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintln(w, "json形式じゃない")
			return
		}

		//parse json
		var data Calc
		jsondata, err := ioutil.ReadAll(r.Body)
		// 構造体に格納しているが、 data をmap[string]int や　map[string]interfase{}にしても格納可能
		err = json.Unmarshal(jsondata, &data)
		if err != nil {
			fmt.Fprintf(w, "json変換できない %v \n", err)
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		//返信用データ作成
		var rdata ReturnCalc
		rdata.Value1 = data.Value1
		rdata.Value2 = data.Value2
		rdata.Add()
		v, _ := json.Marshal(rdata)
		fmt.Fprint(w, string(v))

	} else if r.Method == http.MethodOptions {
		// CORS対策
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
	}
}
```

#GETの作成

`r.Method == http.MethodGet`内がGETの処理です。

以下のページを参考に、URLパラメータを取得します。
[golangでURLをパース](https://qiita.com/nyamage/items/80ca5480baad8da581df)

#POSTの作成

## OPTIONSの指定とCORS対策
POSTの場合、[AWS Lambda + API Gateway ＜POST＞ の使い方【初心者】](https://qiita.com/koshi_an/items/98b12216a69258358d37)にあったように
`OPTIONS`の指定と`CORS対策`が必要です。

POSTすると、`r.Method == http.MethodOptions`が先に実行され
`r.Method == http.MethodPost`が実行されます。

CORS対策は、以下のヘッダー情報を付加することにより対応できます。

```go:
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
```

##r.ParseForm()
POST処理の場合、`ParseForm()`でパース処理が必要らしいです。

##jsonデータ読み取り・書き込み
`ioutil.ReadAll(r.Body)`で、リクエストボディを読み取ることが可能です。
`Unmarshal`で、jsonデータをGo言語の構造体などに格納することができます。
`Marshal`は、構造体などのデータをjsonデータに変換します。

##メソッド

```go:
func (c *ReturnCalc) Add() {
    c.Ans = c.Value1 + c.Value2
}
```
上記のメソッドで加算した結果を Ansに格納していますが
以下のようにポインタレシーバを値レシーバのメソッドにした場合、正しい結果になりません。

```go:
func (c ReturnCalc) Add() {
    c.Ans = c.Value1 + c.Value2
}
```

ポインタレシーバと値レシーバの違いは、構造体の値の変更が、メソッド呼び出し後にも反映されるか否かです。
関数内で構造体の値を変更する場合には、ポインターメソッドで定義する必要があります。

以下が参考になりました。
[[Go] 構造体で、値メソッドとポインタメソッドを使い分ける](https://www.yoheim.net/blog.php?q=20170902)

#テスト
Cloud Functions にもテストはありますが、POSTとして実行されるようです。

![スクリーンショット 2019-09-26 0.26.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125499/ab5e80a4-03dc-4300-b741-bf416679c968.png)



#参考
[golangでURLをパース](https://qiita.com/nyamage/items/80ca5480baad8da581df)
[[Go] 構造体で、値メソッドとポインタメソッドを使い分ける](https://www.yoheim.net/blog.php?q=20170902)