Google Driveで静的なWebサイトが作れるようになりましたね。
http://www.itmedia.co.jp/news/articles/1302/06/news139.html
今回はせっかく出来たGoogle Driveの静的なページ(以下Drive Host Pageと書きます。正式名称じゃないっす。)とGoogle Apps ScriptのContentServiceを使って簡単な掲示板を作ってみたよって話を書きます。
ゴール
以下が作ったサイトです。
https://googledrive.com/host/0BwzWIlBMCiR9SlZkbkFqZ21YeFk/
Drive Host Pageの作り方
まずDrive Host Pageの作り方
-
Google Drive内にどこでもいいのでフォルダを作ります。
今回はhome/掲示板/webapp
というフォルダを作りました。 -
1.で作った公開したいファイルを置くフォルダの共有設定を
Public on the Web
にします。
誰でも閲覧可の状態 今回はwebappをこの設定にしています。 -
1.のフォルダにサイトとして公開したいhtmlファイルを置きます。
あまりいないかもですがもしこの時、GASなどのツールを使ってアップロードした場合、
Content-Typeをtext/htmlで指定してないとうまくいかない場合があります。 -
置いたhtmlファイルをdrive上で開きます。そうすると
Preview
というボタンが表示されます。それを押下します。 -
多分
https://googledrive.com/host/{公開フォルダのID}/{ファイル名}
のURLに遷移して、webページが表示されます。
こんな感じで、公開したフォルダの配下にwebサイトとして公開したいファイルを置いていきます。
なお、js用のフォルダや、img用のフォルダを切った場合はhttps://googledrive.com/host/{公開フォルダのID}/{作ったフォルダ名}/{ファイル名}
の形でアクセスできます。
GASのContentServiceを使ってwebサービス(Web API)を作る
Google Apps ScriptのContentServiceはtext、json、xml(atomやrssを含む)等のhtml以外のコンテンツをwebサービスとして返却するためのサービスです。
例えば単純なJSONを返したい場合は以下のように書きます。
function doGet(e) {
var jsonObject = {hoge : 'fuga'};
return ContentService.createTextOutput(JSON.stringify(jsonObject)).setMimeType(ContentService.MimeType.JSON);
}
ただしこのサービスが公開されるURLはhttps://script.google.com/macros/s/{id}/exec
となるため、
上ほど作成したDrive Host Pageとは異なるドメインになってしまいます。
なのでJavascriptからは直接アクセスができません。
※GASのContentServiceで返却されるコンテンツにはAccess-Control-Allow-Origin
ヘッダもついてないのでCross Domain XHRも無理っす
そこでGAS側でJSONP形式でも返却するようにしておきます。
function doGet(e) {
var jsonObject = {hoge : 'fuga'};
if(e.parameter.callback) {//callbackはクライアントアプリケーションから指定されるcallback関数名
return ContentService.createTextOutput(e.parameter.callback + '(' + JSON.stringify(jsonObject) + ')').setMimeType(ContentService.MimeType.JSON);
} else {
return ContentService.createTextOutput(JSON.stringify(jsonObject)).setMimeType(ContentService.MimeType.JSON);
}
}
またGASではGETとPOSTのみサポートされており、かつURIに寄るRESTっぽいAPIも作れません。
なので操作(データを取得する
とかデータを追加する
)は全てパラメータとして渡します。
function doGet(e) {
if(!e.parameter.action) { //actionはapiの操作
return createContent(e.parameter.callback , {error :'action is required '});
}
switch(e.parameter.action) { //switchで書いてますが、規模によってはstrategyパターンとかにしたほうがいいかもですね
case 'get':
return createContent(e.parameter.callback , doSomething1(e));
case 'put':
return createContent(e.parameter.callback , doSomething2(e));
default :
return createContent(e.parameter.callback , {error : "unsupported operation"});
}
}
function createContent(callback , returnObject) {
if(callback ) {//callbackはクライアントアプリケーションから指定されるcallback関数名
return ContentService.createTextOutput(callback + '(' + JSON.stringify(returnObject) + ')').setMimeType(ContentService.MimeType.JSON);
} else {
return ContentService.createTextOutput(JSON.stringify(returnObject)).setMimeType(ContentService.MimeType.JSON);
}
}
後はクライアント(Drive Host Pageのjsとか)からJSONPでアクセスすればデータを取得出来ます。
認証とか
GASはGoogle Appsドメインを除き、認証(ユーザを判別する方法)は1種類しか無いです。
GASをWeb Appとして公開するときに指定する「Execute the app as:」を「User accessing the web」にし、
ユーザがAPIに初回アクセスするときに表示されるダイアログを利用してインストール(インストールというよりOAuth認可に近いと思いますが)して貰う方法です。
もし「Execute the app as:」を「me」にしている場合、GASのSession.getActiveUser()
というサービスでユーザは返却されますが、その後取得したUser
オブジェクトのgetEmail
メソッドなどは空を返却します。
※あとでキャプチャ貼る
残念ながらこの方法はContentServiceを利用した場合も同じで、Web APIとして、jsonが還ってくることを期待してjavascriptからアクセスしても、
還ってくるのは上のダイアログ(つまりhtml)になります。
そこで、今回作ったアプリではDrive Host Pageを開いた際にjavascriptで一度jsonpとしてアクセスし、
返却されたテキストをjsonと解釈させ、エラーが発生した場合は認証(GASのインストール)が済んでないと考え、
認証してもらうためのリンクを表示しています。
//クライアントサイドのJS angularjsを利用しています。
$http({
method: 'JSONP',
url: 'https://script.google.com/macros/s/AKfycbylYV5MrVg3d-Yi3IssTzo4_xIGsfGs0fgLY1fMcF5WUc02UKM/exec?callback=JSON_CALLBACK&action=messages'
}).success(function(data, status) {
//successの場合はjson文字列の評価が成功 == 認証済み
$scope.messages = $scope.messages.concat(data);
}).error(function(data,status,headers,config){
//errorの場合はjson文字列の評価が失敗 == 多分認証できてない => 認証してもらうためにリンクを表示
$scope.showAuthentification = true;
});
また認証は 元のアクセスしたGASのURL → Googleの認証URL → 元のアクセスしたGASのURL と遷移するので、
Web APIを表示しているGAS側で元のDrive Host Pageに戻るためのリンクを表示させます。
function doGet(e) {
if(e.parameter.auth) { //認証時はauthというパラメータを付けておく
return HtmlService.createHtmlOutputFromFile("index.html"); //認証後に元のDrive Host Pageに戻ってもらうためのリンクを表示する画面を返却する。
}
//web apiの処理が続く
...
}
なお、Google AppsのGASの場合は「Execute the app as:」を「me」にしていても、GASのSession.getActiveUser().getEmail()
はアクセスした
ユーザのアドレスが返却されるため、上記のような操作は不要になります。
データの保存
ここまで来ればデータの保存はどこにやっても良いです。
今回のアプリでは返却するJSONをラクに作るためにScriptDb
を利用していますが、
サービスの種類によってSpreadsheetにしたり、ScriptPropertiesにしたり、Fusion Tablesにしたりと適切なものにして下さい。
ただし、「Execute the app as:」を「User accessing the web」にしているため、
SpreadsheetやFusion Tablesの場合はユーザ自身が、そのファイルへのアクセス権限を保つ必要があります。
DBを公開したくない場合はScriptDbや、ScriptPropertiesなどScript自体にデータが紐づくサービスを利用したほうが良いです。
P.S.
Google Drive Host Pageが2/6に追加されたって書いてあるんですが仕組み的には昨年ぐらいに追加されているんですよね
なんでGoogleも改めて発表したのか若干気になったりします。