JavaScript
GoogleAppsScript
gas

今から10分ではじめる Google Apps Script(GAS) で Web API公開


概要



  • Google Apps Script (GAS)で、Web API(無料、サーバーレス)を公開する方法についてのメモです


  • Google Apps Script (GAS)をローカルで開発したい、Gitでコード管理したい、クラスを使いたい、TypeScriptを使いたい、などのやり方も後半簡単に紹介します


Google Apps Script (GAS) の位置づけを理解してみる

image.png


  • Google Apps ScriptG Suite(GmailやスプレッドシートなどGoogleサービス)を JavaScriptで制御できる仕組み。だから、Microsoft OfficeのVBAのGoogleサービス版みたいな感じ。


  • Google Apps ScriptJavaScriptで書いた処理をWebAPIとして公開できるので、AWS LambdaとかGoogle Functionのようなサーバレスなコード実行環境のようにも使える←今回はこの用途で使いたい


  • Google Apps ScriptJavaScript 1.6(2005年11月)がベースなのでモダンなJavaScriptで記述することはできない。かつブラウザ系機能であるDOM API等は無い。ただし、ツールをこねくり回せば今風なコードで書くこともできる


  • Google Apps Script無料で使える従量課金のような考え方では無いので、お金を払っても無限に使えないということで容量制限がある。



Google Apps Script のプロジェクトを作る


Google Apps Scriptのトップページにいく

Googleアカウントにログインした状態で以下に行く

https://script.google.com

image.png


最初のプロジェクトを作る

image.png

[APPS SCRIPTを作成]をクリックすると、プロジェクトが作成される。

無題のプロジェクト というプロジェクトとコード.gsというファイルができるので、

image.png

画面左上の無題のプロジェクトをクリックして、プロジェクト名をwebapi_example01に変更した。

さらに、↓のように、

image.png

左側ペインにコード.gsというファイルがあるのがわかる。これが最初のファイルとなる。ファイル名の右にある[▼]を押してメニューから名前を変更をクリックしてmain.gsというファイル名に変更した。

image.png


コードを書いてWeb APIを公開する

さっそくコードを書いて、WebAPIを公開する。コードはJavaScriptで書く。

Google Apps Script はJavaScriptの最新をサポートしているわけでも、すべての機能をサポートしているわけでもないが、JavaScriptが書ける人ならなんとかなる。


Web APIをつくる

デフォルトだと以下のようになっているが、Web APIとして公開したいので、GETやPOSTをハンドリングできるようにする。


main.js

function myFunction(){

}

関数myFunctionは消して、以下のようにした


main.js

//HTTP GETをハンドリングする

function doGet(e) {

//リクエストパラメータ名"text"の値を取得する
var text = e.parameter.text;
var value;
if (text) {
value = "You say " + text;
} else {
value = "Please say something!";
}
var result = {
message: value
}

var out = ContentService.createTextOutput();

//Mime TypeをJSONに設定
out.setMimeType(ContentService.MimeType.JSON);

//JSONテキストをセットする
out.setContent(JSON.stringify(result));

return out;
}


シンプルなコードだが、いくつかみておく。

コード中のdoGetで渡される引数eは、リクエストパラメータ等のイベントパラメータが入る。

function doGet(e) {

var text = e.parameter.text;

e.parameterはリクエストパラメータがkey/valueペアとして入るので、

Google Apps ScriptでWeb公開したURLでhttps://xxxxxx/exec?text=Helloとリクエストされたら e.parameter.textには"Hello"が入ることになる。

リクエストパラメータについて詳しくはこちらに書いてある。

以下の ContentService はテキストコンテンツを返すためのサービスで #createTextoutput で Textoutputオブジェクトを生成する。

var out = ContentService.createTextOutput();

Textoutputオブジェクト outに対して #setMimeTypeでJSONを指定し、#setContentで実際に返すテキスト(この場合は JSON形式のテキストにして)をセットする。

out.setMimeType(ContentService.MimeType.JSON);

out.setContent(JSON.stringify(result));
return out;

これでTextoutputオブジェクト outを return すればOK。

セキュリティを考慮してスクリプトがダイレクトにテキストをブラウザに返すことはなく、実際には、ブラウザは googleusercontent.com にリダイレクトされ、そこから結果を受け取る、という構造になっている。

image.png

これで、GETメソッドでアクセスすると、JSONを返すWeb APIができた。

早速公開してみる。


Web APIの公開

上部メニューから公開>Webアプリケーションとして導入 を選択する。

image.png

するとダイアログがでる、

image.png

アプリケーションにアクセス出来るユーザーを 全員(匿名ユーザーを含む) にする

image.png

[導入]をクリックすると、現在のウェブアプリケーションのURLが表示されるので、そのURLをコピーしてブラウザからアクセスしてみる。

image.png

アクセス結果

image.png

無事、レスポンスが返ってきた。

シンプルだけど、これでWeb API公開に成功した。


ブラウザからこのAPIをたたきたい

ブラウザからAPIをたたくということは、まずクロスドメイン大丈夫か?ということが頭をよぎる。

つまりGoogle Apps Script で公開したWeb APIはCORS(Cross-Origin Resource Sharing)を OKにできるのか?

Google Apps Script で公開したWeb APIはCORSできない(以前はでききなかった)

→ Google Apps Script は CORS に対応している。その証拠にヘッダに access-control-allow-origin:*がセットされている。ただし、うまく行かないパターンがあるので注意

うまく行かないパターン

・JSONテキストをまるごと POST するような場合は エラーとなる

 →なぜなら、JSONテキストの content-typeが application/json となるため。

 →なぜ application/json だとエラーとなるかというと、 application/jsonの場合は preflightリクエストが発生するため

 →なぜなら、Google Apps Script は OPTION メソッドをサポートしていないため。

 

つまり異なるドメイン(オリジン)からAjaxはGETもPOSTもできない。

ブラウザ上で動くスクリプトから preflightリクエストが発生するようなリクエストを使いたければ、同じドメインにHTMLを配備するか、別ドメインにHTMLを配備したうえで、JSONPを使うしか手がないようだ。


Web APIをJSONP対応化する

ということで、別ドメインにHTMLを置く前提でさきほどのコードをJSONPに書き換える


main.js

//HTTP GETをハンドリングする

function doGet(e) {

//リクエストパラメータ名"text"の値を取得する
var text = e.parameter.text;

var value;

if (text) {
value = "You say " + text;
} else {
value = "Please say something!";
}

var result = {
message: value
}

var responseText;

var out = ContentService.createTextOutput();

var callback = e.parameter.callback;

if (callback) {
responseText = callback + "(" + JSON.stringify(result) + ")";
//Mime Typeをapplication/javascriptに設定
out.setMimeType(ContentService.MimeType.JAVASCRIPT);

} else {
responseText = JSON.stringify(result);
//Mime Typeをapplication/jsonに設定
out.setMimeType(ContentService.MimeType.JSON);
}

//JSONPテキストをセットする
out.setContent(responseText);

return out;
}


 var callback = e.parameter.callback;

if (callback) {
responseText = callback + "(" + JSON.stringify(result) + ")";
//Mime Typeをapplication/javascriptに設定
out.setMimeType(ContentService.MimeType.JAVASCRIPT);

} else {
responseText = JSON.stringify(result);
//Mime Typeをapplication/jsonに設定
out.setMimeType(ContentService.MimeType.JSON);
}

リクエストパラメータに callbackがついていたら、JSONPで返すようにした。

本家サイトにも注意があるが、JSONPをつかうなら脆弱性に関して注意深く設計する必要がある。提供する情報はリードオンリーかつ誰にでも公開できるような情報のみとし、センシティブな情報はゼッタイ入れない)


公開しているWeb APIを更新する

公開>Webアプリケーションとして導入 を選択していまつくったJSONP対応コードに更新する。

image.png

新しいバージョンを公開したいときは、プロジェクトバージョンを新規作成する。

変更内容は入力しなくてもよい。

さっきのが バージョン1 だったので、次は自動的に 2 になる。


HTMLをつくって、ブラウザからアクセスする

以下のような htmlファイルを作る。

今回は、JSONPへのアクセス用にjQueryを使った。


index.html

<!DOCTYPE html>

<html lang="en">
<head>
<meta charset="UTF-8">
<title>GAS Client Example</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script>
const endpoint = "https://script.google.com/macros/s/xxxxxxxxxx/exec";
$.ajax({
type: 'GET',
url: endpoint,
dataType: 'jsonp',
data: {
text: 'Hi,There!'
},
success: out => {
alert(out.message);
}
});
</script>
</body>
</html>

const endpoint = "https://script.google.com/macros/s/xxxxxxxxxx/exec";

この部分には、さきほどの 公開>Webアプリケーションとして導入で確認できる現在のWebアプリケーションのURLを入力する。

image.png

さて、いまつくった index.html をブラウザで開いてみる。

ローカルファイルでも開ける。

image.png

うまく呼び出すことができた。

これで、シンプルなWeb APIがお手軽に作れた。


デバッグ、テストの方法

デバッグの方法はいろいろあるがいちばん準備が少なくて良いのは、デフォルトで準備されているスクリプトエディタとなる。


スクリプトエディタ上で実行する

ブラウザ上から編集できるスクリプトエディタには、スクリプトの実行機能やデバッグ機能がついているので実行してみる

ここでは、doGetを呼び出すために以下のようなコードを main.gs に追加した。


main.gsへの追加分


function testLogic() {
var e =
{
"parameter": {
"text": "hello",
"callback": "myCallbackFunc"
}
};
var out = doGet(e);
var content = out.getContent();
Logger.log("content=" + content);
}

#doGetのロジックがちゃんと動作しているのか、この#testLogic関数をつかって確かめてみる。

以下のように実行したい関数を選択できるので、 testLogicを選択して、

image.png

実行ボタンimage.pngをクリックすると実行される。

表示>ログでログ画面を表示することができるので、

image.png

ログ画面を表示してみた。

image.png

ちゃんと動作している模様。


デバッグ機能を使う

今のはふつうに実行だったが、今度はデバッグ機能image.pngをつかう。

デバッグ実行するまえに、プログラムの実行を一時停止するブレークポイントを設定する。

コードの行番号のところを、マウスでポチっとすると、赤丸●がついて、ここにブレークポイントがセットされる。

image.png

この状態でimage.pngをクリックする

すると、以下のような感じで、ブレークポイントまで実行され、ウォッチ画面が表示される。

image.png

一時停止や再開、ステップ実行などひととおりの機能がブラウザベースでできるので、なかなか便利。


Google Apps Scriptから自分自身を呼び出す

main.gsに以下を追加する。


main.gs追加分


function testGet() {

var url = ScriptApp.getService().getUrl() + "?text=Hello&callback=myFunc001";
Logger.log("url=" + url);

var options = {
"method": "GET",
"followRedirects": true,
};

var response = UrlFetchApp.fetch(url, options);
Logger.log("response=" + response);
}


このコードは、自分自身を呼び出してみる関数になっている。

var url=ScriptApp.getService().getUrl() ・・・

ScriptApp.getService().getUrl()で自分自身のURLを取得する

    var options = {

"method": "GET",
"followRedirects": true,
};
var response = UrlFetchApp.fetch(url, options);

こちらは、実際に GETメソッドで外部サービス(といっても自分自身)を呼び出しにいっている。外部サービスに接続にいくときは、 UrlFetchAppを使う。

ここまでのコード(main.gs)は以下のようになっている。


main.gs

//HTTP GETをハンドリングする

function doGet(e) {

//リクエストパラメータ名"text"の値を取得する
var text = e.parameter.text;

var value;

if (text) {
value = "You say " + text;
} else {
value = "Please say something!";
}

var result = {
message: value
}

var responseText;

var out = ContentService.createTextOutput();

var callback = e.parameter.callback;

if (callback) {
responseText = callback + "(" + JSON.stringify(result) + ")";
//Mime Typeをapplication/javascriptに設定
out.setMimeType(ContentService.MimeType.JAVASCRIPT);

} else {
responseText = JSON.stringify(result);
//Mime Typeをapplication/jsonに設定
out.setMimeType(ContentService.MimeType.JSON);
}

//JSONPテキストをセットする
out.setContent(responseText);

return out;
}

function testLogic() {
var e = {
"parameter": {
"text": "hello",
"callback": "myCallbackFunc"
}
};
var out = doGet(e);
var content = out.getContent();
Logger.log("content=" + content);
}

function testGet() {

var url = ScriptApp.getService().getUrl() + "?text=Hello&callback=myFunc001";
Logger.log("url=" + url);

var options = {
"method": "GET",
"followRedirects": true,
};

var response = UrlFetchApp.fetch(url, options);
Logger.log("response=" + response);
}


さて、実行対象関数を testGetに指定して、

image.png

実行image.pngしてみると、こんなダイアログがでてきた。

image.png

許可を確認をクリックすると、

image.png

認可画面がでてくるので、内容確認して 許可をクリック。

実行が終わった後に、ログ画面を表示してみると、きちんと動作していた。

image.png

ここまででひとまず基本的な使い方は理解できた。


ローカルで開発したい、Gitでコード管理したい、クラスを使いたい、TypeScriptを使いたい、などの要求(欲求)とやり方

Google Apps Scriptにも効率化やモダン?な開発を促進するための各種ソリューションを先人たちが親切にも準備してくれているのでキーワードだけカンタンに整理しておく。


ローカルで開発したい!

要求:ブラウザ上のエディタじゃものたりません、ローカルで開発したいです


  • そんなときは、ローカルでの開発を助けてくれるclasp。GITに似たコマンド体系で操作できる。

インストール

npm i @google/clasp -g

インストールしたらログインする。Googleのアカウントで入る。

clasp login

ローカルにプロジェクトを落としてくる

clasp clone [scriptId/scriptURL]



clasp clone "15ImUCpyi1Jsd8yF8Z6wey_7cw793CymWTLxOqwMka3P1CzE5hQun6qiC"

clasp clone "https://script.google.com/d/15ImUCpyi1Jsd8yF8Z6wey_7cw793CymWTLxOqwMka3P1CzE5hQun6qiC/edit"

pullする。バージョン指定も可能。

clasp pull

clasp pull -versionNumber 2

pushする。

clasp push

claspでローカルに落としてきたものをGitHubで管理したりできそう。


GitHubでコード管理したい!

要求:Google Apps ScriptのコードはGoogle Driveに保存されるが、あっぱりGitHub使いたい!

chrome拡張として提供されている。

前節でふれたclaspをつかってローカル→GitHubでもいいし、chrome拡張つかってもよさそう。


クラスを使って書きたい!

要求:クラスを使いたい!


  • そんなときは、まずES5のprototypeをつかったクラス風で満足できないか?


ES5時代のクラス風構文


function Greeting(timing) {
this.timing = timing;
}

Greeting.prototype.sayHello = function (name) {
if (!name) {
name = "Man";
}
if (this.timing == 'morning') {
return 'Good morning,' + name;
} else {
return 'Hello,' + name;
}
};

function doGet(e) {

var g = new Greeting("morning");
var hello = g.sayHello("John");

var result = {
message: hello
};

var responseText = JSON.stringify(result);
var out = ContentService.createTextOutput();
out.setMimeType(ContentService.MimeType.JAVASCRIPT);
out.setContent(responseText);

return out;
}


要求:ES5のクラス風とかではなく、TypeScriptを使ってきちんと書きたい


ユニットテストがしたい

要求:Google Apps Scriptでもユニットテストがしたい


まとめ


  • Google Apps Script(GAS)でWeb APIを公開する方法をハンズオン的にみてきました

  • 今回はほんの触りだけでしたが、機能豊富で味わい深い使い方ができそうです