今回はGASをAPIとして機能させれる「実行可能API」としてデプロイし、外部アプリケーション(以降外部アプリ)から呼び出して利用する機会がありましたので手順を残します。
- 紹介すること
- GASの実装とデプロイ
- GCPでのトークン取得する
- 外部アプリからのリクエストサンプル(TypeScript)
- 紹介しないこと
- GCPプロジェクトの立ち上げ方
- 外部アプリのデプロイ方法等
- セキュリティについての諸々
基礎的なところを書いているので、「とりあえず最速で実装したい」という方に向けたものだと思ってください。
なお、本手順ではGCPのプロジェクトが作成されていることが前提となっています。
プロジェクトの作成手順は本手順では説明しないので、作成していない方は事前に作成しておいてください。
GASの処理を書く
まずはAPIとして呼び出される側のGASを実装します。
前提として、このGASでは以下のようなリクエストが送られてきます。
curl -X POST https://script.googleapis.com/v1/scripts/{SCRIPT_ID}:run
-H 'Content-Type: application/json'
-d '{"function": "callableFunction", "parameters": {"userName":"NekoLove", "userEmail":"inumotamaran@example.com"}'
bodyの中身としては、
- function:呼び出す関数名
- parameters:GASに渡す任意の関数名
という構成になります。
このリクエストを受け取るサンプルは以下のとおりです。
function callableFunction(params){
try{
// 渡されたパラメータからuserName, userEmailを取得しログ出力
console.log(params.userName)
console.log(params.userEmail)
// 問題なく処理が完了した場合のレスポンス例
let ret = JSON.stringify({
message: "Success", // エラーメッセージ
code: 200 // エラーコード
})
return ret
}catch(e){
// エラー発生時のレスポンス例
let ret = JSON.stringify({
error: "Error",
code: 500
})
return ret
}
}
呼び出されたら、渡されたパラメータからuserNameとuserEmailをログ出力し、エラーがなければその旨をレスポンスするだけの処理です。
一点気をつけたいのが、APIとして呼び出させたい関数以外は関数名の末尾にアンダースコア(_)をつけることです。
これをしていないとfunctionで指定すればどの関数でも実行させられるようになりかねません。
GASに慣れている方であれば知っているかと思いますが、Googleワークスペース外から呼び出される場合でも同じことが言えます。
任意のステータスコードは返せない
タイトルのとおり、実行可能APIでは任意のステータスコードは返せません。
(2024年12月現在の仕様では、です。)
GASの処理が中断されることなく完了したら200を返すようになっています。
ContentService.createTextOutput().setResponseCode(200)でステータスコードを返します
みたいな情報を見るかもしれませんが、これはウェブアプリケーションとしてGASをデプロイした際に使うもので、現時点の実行可能APIではこれをレスポンスデータに入れると不適切な形式の情報として破棄されます。
そのため、実行結果としてエラーメッセージとステータスコードに該当する情報をレスポンスデータの中に入れて返すのが良いかと思います。
let ret = JSON.stringify({
message: "Success", // エラーメッセージ
code: 200 // エラーコード
})
サンプルコードの上記部分がそれにあたります。
そうなると、クライアント側はステータスコードが200であることを確認したうえ、レスポンスデータに含まれるcodeも200のとき正常にリクエストできたと判定するような実装が必要です。
ここについては、あとの方でサンプルコードを記載します。
実行可能APIとしてデプロイする
デプロイするにあたり、作成したGASをGCPプロジェクトに紐づけておく必要があります。
とは言ってもやり方は簡単で、スクリプトエディタの設定画面を開き、Google Cloud Platform(GCP)プロジェクト欄に紐づけるGCPプロジェクト番号を入力するだけです。
プロジェクト番号はGCPプロジェクトのダッシュボードから確認できますので、そこからコピーしましょう。
設定できたら、スクリプトエディタの画面右上のデプロイボタンをクリック。
新しいデプロイを選択
ポップアップが出るので、左の歯車マークから[ 実行可能API ]を選択。
説明、アクセスできるユーザー欄を設定したらデプロイ。
完了画面が表示されたら、実行可能APIのURLをコピーして控えておく(あとからでも確認できます)
実行可能APIを呼び出せるように認証情報を設定する
ここは以下のページで手順を紹介していますので、参照してください。
もちろんGASでプロジェクト番号を指定したプロジェクトでやる必要があるので、間違えないようにしてください。
ここでは、上記手順で取得したRefresh Tokenを使って外部アプリからリクエストします。
トークンを使って外部アプリからリクエストする
今回は以前Typescriptとして書いたものをサンプルコードとして記載します。
(多分この書き方ならTypescriptである必要ない。身につけるため少しずつ触っているところなんです……)
また、言うまでもありませんがセキュリティには気をつけてください。
今回サンプルコードでは便宜上定数で設定する形にしていますが、認証情報をコード上に載せるとか絶対ダメです。
import axios from 'axios'
const TOKEN_URL = 'https://oauth2.googleapis.com/token'; //これは固定
const CLIENT_ID = 'GCPのOAuthクライアントID';
const CLIENT_SECRET = 'GCPのOAuthクライアントシークレット';
const REFRESH_TOKEN = '取得したRefresh Token';
const API_URL = 'デプロイした実行可能APIのURL';
export async function requestGasAPI() {
try {
// アクセストークンを取得する(Promise)
const accessToken = await getAccessToken().then(function (res) {
return res
})
// APIへリクエストする
const url = API_URL;
let params = {
// GASに渡したいパラメータをここに入れる
userName: 'NekoLove',
userEmail: 'inumotamaran@example.com'
}
const data = {
function: 'callableFunction', //実行したいGASの関数名
parameters: params
};
const options = {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
},
};
const response = await axios.post(url, data, options);
if(response.status == 200){
// 内部の処理はともかく、GASの処理が完了したケース
const result = response.data.response.result //GASでセットしたレスポンスデータを取得
let jsonResult = JSON.parse(result.toString())
// GASでセットしたcodeをログに出力
console.log(jsonResult.code)
}else{
// 実行可能APIでExceptionが起きたり、リクエスト自体が通らなかった場合のケース
}
} catch (e) {
throw e
}
}
/**
* アクセストークンを取得する関数
* @return string
*/
async function getAccessToken(){
return new Promise(function (resolve) {
const data = JSON.stringify({
client_id: CLIENT_ID,
client_secret: ,CLIENT_SECRET
refresh_token: getSecretInfo(REFRESH_TOKEN),
grant_type: 'refresh_token',
});
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
};
const result = axios.post(TOKEN_URL, data, options);
result.then(response => {
let token = response.data.access_token
resolve(token)
})
})
}
これで一通りの実装は完了です。
あとはrequestGasAPI()をどこかから呼び出せば処理が確認できます。
GASは便利。セキュリティだけは気をつけたい
GoogleWorkspaceのユーザからしたら本当に気軽に色々できるGASは便利です。
Googleの各種ツールも自動化できますし、発展させた処理がしたければGCPと紐づければ良いし、AWSと連携させることも難しくありません。(API Gatewayをつかったり、GASをAPIとしてデプロイしたり)
GCPのWorkload Identityを使えば、AWSからの認証も最近は楽なのかなと思ったりしています。
とはいえGASにもConsはあって、機能はCloud FunctionsやLambdaに比べると弱いですし、ブラックボックスなGoogleのサーバに依存していたりといった点で少し不安もあります。
少なくともセキュリティ部分だけは考えつつ、社内ツールとして使い倒していきたいところです。