Open8

Cloud Functionsに触れてみる

kurehajimekurehajime
  1. Cloud Functionsを作る

トリガーはHTTPリクエスト。
Github Pagesからカジュアルに呼び出したいので認証は行わない。

kurehajimekurehajime
  1. ソースをアップロードする(完成版)
function.go
// Package p contains an HTTP Cloud Function.
package p

import (
	"encoding/json"
	"net/http"
	"strings"

	"github.com/kurehajime/dajarep"
)

func Dajareper(w http.ResponseWriter, r *http.Request) {
	var d struct {
		Messages []string `json:"messages"`
	}
	// Set CORS headers for the preflight request
	if r.Method == http.MethodOptions {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "POST")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
		w.Header().Set("Access-Control-Max-Age", "3600")
		w.WriteHeader(http.StatusNoContent)
		return
	}
	if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
		http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	input := strings.Join(d.Messages, "\n")
	dajares, _ := dajarep.Dajarep(input)
	d.Messages = dajares
	out, err := json.Marshal(d)
	if err != nil {
		http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
		return
	}

	// Set CORS headers for the main request.
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Content-Type", "application/json,;charset=UTF-8")
	w.Write(out)
}

go.mod
module example.com/cloudfunction

go 1.16

require github.com/kurehajime/dajarep v1.9.5

require (
	github.com/ikawaha/kagome-dict v1.0.2 // indirect
	github.com/ikawaha/kagome-dict/ipa v1.0.2 // indirect
	github.com/ikawaha/kagome/v2 v2.4.4 // indirect
)
kurehajimekurehajime
  1. Github Pagesからアクセスする(完成版)
index.html
<html>

<body>
    <h1>ダジャレ抽出器</h1>
    <textarea id="input" rows="10" cols="50">人民の人民による人民のための政治
アルミ缶の上にあるミカン
トンネルを抜けるとそこは雪国であった
智代子のチョコ
布団が吹っ飛んだ
我輩は猫である
猫が寝転んだ
その意見にはついていけん
靴を靴箱に入れる
傘を貸さない
イカは如何なものか
親譲りの無鉄砲で子供の時から損ばかりしている</textarea>
    <br>
    <button id="do">ダジャレだけを抽出</button><br>
    <textarea id="output" rows="10" cols="50"></textarea>
<footer><a href="https://github.com/kurehajime/dajarep">by @kurehajime</a></footer>

    <script defer>
        async function postData(url = '', data = {}) {
            const response = await fetch(url, {
                method: 'POST',
                mode: 'cors',
                credentials: 'same-origin',
                headers: {
                    'Content-Type': 'application/json'
                },
                redirect: 'follow',
                body: JSON.stringify(data)
            })
            return response.json();
        }
        document.querySelector("#do").onclick = () => {
            const input = document.querySelector("#input").value;
            const output = document.querySelector("#output");
            const lines = input.split("\n");
            postData('https://asia-east1-xiidec.cloudfunctions.net/dajareper',
                { "messages": lines })
                .then(data => {
                    if (data.messages) {
                        output.value = data.messages.join("\n");
                    } else {
                        output.value = "";
                    }
                });
        }
    </script>
</body>

</html>
kurehajimekurehajime

つまづいたところ

文字化けする

out, err := json.Marshal(d)でJSON化したものを
fmt.Fprint(w, out)で書き込んで返却すると、文字化けして困った。文字化けというより、Unicodeのバイト列がそのまま返ってきた感じ。

サンプルはFprintだったが、書き込みを素直にw.Write(out)でやると解消した。

cors地獄

curlでは繋がるのに、ブラウザはcorsエラーで全然繋がらない。
普段Webフレームワークに甘やかされてるので結構苦戦した。

ヘッダを加える

w.Header().Set("Access-Control-Allow-Origin", "*")を付け加えた。
これだけでは駄目だった。

preflightに配慮する。

公式ドキュメントを読むと、preflightというCORSの下準備のリクエストが事前に飛ぶのでその対処をする必要があるらしい。

	if r.Method == http.MethodOptions {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "POST")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
		w.Header().Set("Access-Control-Max-Age", "3600")
		w.WriteHeader(http.StatusNoContent)
		return
	}

…が駄目だった。

preflightに配慮する②

このコード、リクエストを処理するメソッドの最後の方に書いていた。

	if r.Method == http.MethodOptions {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "POST")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
		w.Header().Set("Access-Control-Max-Age", "3600")
		w.WriteHeader(http.StatusNoContent)
		return
	}

しかしそれではBad Requestのチェックに引っかかり、preflightが失敗する。preflightが失敗すると本Requestも失敗する。

preflight避けをリクエストのチェックより先に移すことで、ちゃんとAPIを叩くことが出来る。