Cloud FunctionsのHTTPトリガーをGolang + Echoでさばいてみた

GCPのCloud FunctionsのHTTPトリガーをGolangで使っているときにEcho使えるんじゃないかと思って試したメモです。

# プロジェクト構成
Cloud Functionsで使えるように以下のようにファイルとディレクトリを作成します。

```
プロジェクトルート/
├── cmd
│   └── main.go
├── functions.go
├── go.mod
├── go.sum
└── shared
    └── echo.go
```

## コード
### go.mod
使った依存ライブラリはこんな感じです。

```
module github.com/sert-uw/go_echo_functions

go 1.13

require (
	github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
	github.com/labstack/echo v3.3.10+incompatible
	github.com/labstack/gommon v0.3.0 // indirect
	golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 // indirect
	golang.org/x/text v0.3.2 // indirect
)
```

### shared/echo.go
Echoの初期化を`InitEcho`で行うよう実装します。

```go

package shared

import (
	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
	"net/http"
)

var EchoServer *echo.Echo

func InitEcho() {
	EchoServer = echo.New()

	EchoServer.Use(middleware.CORSWithConfig(middleware.CORSConfig{
		AllowOrigins: []string{"*"},
	}))

	EchoServer.GET("/hello/:name", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello "+c.Param("name"))
	})
}

```

### functions.go
`init`で`shared`パッケージの`InitEcho`を呼び出すことでCloud Functionsの関数インスタンス作成時にEchoの設定を行います。
`Function`で受け取ったHTTPリクエストはそのままEchoの`ServeHTTP`に渡します。
あとはEchoでリクエストを処理してくれるので、`Function`の役割はこれだけです。

```go

package functions

import (
	"github.com/sert-uw/go_echo_functions/shared"
	"net/http"
)

func init() {
	shared.InitEcho()
}

func Function(w http.ResponseWriter, r *http.Request) {
	shared.EchoServer.ServeHTTP(w, r)
}
```

### cmd/main.go
Cloud Functionsではmain関数は使わないですが、ローカル環境で試すのに便利なので実装します。
`init`で`shared`パッケージの`InitEcho`を呼び出してEchoの設定を行い、`main`でEchoサーバーを起動するだけです。

```go

package main

import (
	"github.com/sert-uw/go_echo_functions/shared"
	"log"
)

func init() {
	shared.InitEcho()
}

func main() {
	log.Fatalln(shared.EchoServer.Start(":8080"))
}
```

# 動作確認
## ローカル環境
main関数を実行して、curlを投げてみます。

```
$ go run cmd/main.go

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v3.3.10-dev
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:8080
```

```
$ curl 'http://localhost:8080/hello/Sert' -v
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /hello/Sert HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: *
< Content-Type: text/plain; charset=UTF-8
< Vary: Origin
< Date: Sun, 31 May 2020 16:40:27 GMT
< Content-Length: 10
<
* Connection #0 to host localhost left intact
Hello Sert
```

## Cloud Functions
ソースコードはGCPのリポジトリにpushして以下のように設定しました。
![スクリーンショット 2020-06-01 1.37.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69502/ae232122-34f9-3597-b138-2a1d46772b0d.png)

GCPのコンソール上でトリガーのエンドポイントを確認してcurlを投げてみます。

```
$ curl 'https://asia-northeast1-<プロジェクト名>.cloudfunctions.net/go-echo-functions/hello/Sert' -v
*   Trying 216.239.xxx.xxx...
* TCP_NODELAY set
* Connected to asia-northeast1-<プロジェクト名>.cloudfunctions.net (216.239.xxx.xxx) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-ECDSA-CHACHA20-POLY1305
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=US; ST=California; L=Mountain View; O=Google LLC; CN=misc.google.com
*  start date: May  5 08:23:30 2020 GMT
*  expire date: Jul 28 08:23:30 2020 GMT
*  subjectAltName: host "asia-northeast1-<プロジェクト名>.cloudfunctions.net" matched cert's "*.cloudfunctions.net"
*  issuer: C=US; O=Google Trust Services; CN=GTS CA 1O1
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fbd30006c00)
> GET /go-echo-functions/hello/Sert HTTP/2
> Host: asia-northeast1-<プロジェクト名>.cloudfunctions.net
> User-Agent: curl/7.54.0
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
< HTTP/2 200
< access-control-allow-origin: *
< content-type: text/plain; charset=UTF-8
< function-execution-id: sdtxd0pn5lgn
< vary: Origin
< x-cloud-trace-context: 42b67e55066cded12f14db38b8da2855;o=1
< date: Sun, 31 May 2020 16:42:41 GMT
< server: Google Frontend
< content-length: 10
< alt-svc: h3-27=":443"; ma=2592000,h3-25=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
<
* Connection #0 to host asia-northeast1-<プロジェクト名>.cloudfunctions.net left intact
Hello Sert
```

# まとめ
予想以上に簡単にEchoを動かせたので、App Engineを別のアプリケーションに使いたい場合にAPIをCloud Functionsで動かすなど、コストを抑えるためにGCPの無料枠をうまく使う選択肢の1つになるかもしれません。