Google Apps ScriptでCloud Functionsの関数を実行する【GAS】

前回までで、Google Cloud Functionsを利用して、すでにCloud Storageに配置してあるPDFについて、パスワード付PDFにして、再度Cloud Storageに格納するところまで成功しました。これで、Cloud Functions側にGASからの受け入れ窓口と、取得したデータをバイナリで返してくれさえすれば、Google Apps Script + Google Cloud Functionsの合わせ技で、パスワードPDFを一気につくれます。

ようやくこれで、5年越しに低価格で高速なパスワードPDFをGASから作る環境が作れました。Cloud FunctionsはGAS単体やGCP単体では難しい今回のようなケースで、Node.jsとモジュールの力を借りて、機能拡張するには非常に良い選択肢であると、検証できたと思います。

※これにてパスワードPDFは作れるようになりましたが、同じ要領で暗号化ZIPも作れるかもしれません。また、複数のユーザで使う場合には、ファイル名+シート名+日付といったようなファイル名を付けて処理をすると良いでしょう。

※また、この関数実行URLは知っていれば誰でも実行できてしまうので、課金でCloud死してしまいます。独自にAPIキー的なものを実装して、APIキーが無いアクセスは弾くようにしてしまえば良いでしょう。

今回使用するスプレッドシート

今回は単純に1枚だけ、PDF化するためだけのシートを加えています。スクリプトエディタの中にて、run_gcffunction関数を実行すると、GCFにパラメータを送り、パスワードPDFをバイナリで取得して、ドライブに生成します。もちろん、そのままメールに添付して送る事も可能です。

事前準備

Cloud Storage

今回の処理で必要な手順は以下の通り。

  1. Cloud Storage(バケットの名前は、drive2gcsにしておきました)に、フォルダ(publicという名前にしました)を作成する
  2. 今回は特にファイルはアップしません。

Cloud Storage側はこれだけです。

Cloud Functions

Cloud Functions側の準備もシンプルです。事前にサービスアカウントの作成が必要ですので、作成をしておきましょう。前回の記事で作成について触れています。もちろん請求先アカウントの設定も必要です。

  1. 関数を作成する
  2. 割当メモリは512MB、トリガーはHTTPとしました。ランタイムはNode.js V8を選んでいます。
  3. リージョンは、デフォルトのus-central1にしてあります。
  4. 関数名は、spreadmanという名前にしました。しかし、実際に呼び出す関数はコードの中にある「sspdfget」になりますので、注意。
  5. Cloud Functions上では、npmでインストールではなく、package.jsonに記述すると裏でモジュールを用意してくれます。ですので、以下のような記述を追加しました。追加モジュールは2個です。
  6. hummus-recipeは、PDFをパスワード付にするだけでなく、様々な加工のできるスグレモノです。
  7. google-cloud/storageは、Google作成のNode.jsからCloud Storageへのアクセスを容易にするモジュールです。
  8. 今回は、GCF側でスプレッドシートのPDF化を行わせるので、requestモジュールを追加しています。
  9. 実行する関数名は、sspdfgetとして設定しました。
{
  "name": "sample-http",
  "version": "0.0.1",
  "dependencies": {
    "hummus-recipe": "^1.8.9",
    "@google-cloud/storage": "^2.5.0",
    "request": "^2.88.0"
  }
}

コード:package.jsonには以上のような記述を追加

ソースコード

GAS側コード

//cloud functionsの関数のURL
var url = "ここにGoogle Cloud Functionsの実行用URLを入力する";

//スプレッドシートURLを入力
var ssid = "PDF化するスプレッドシートのIDを入力する";

//PDF生成先フォルダーのID
var folder = "ここにパスワードPDFを生成するGoogle DriveのフォルダのIDを入力する";

//Cloud Functionsの関数を実行する
function run_gcffunction() {
  //uiを取得
  var ui = SpreadsheetApp.getUi();
  
  //Access Tokenを取得
  var accessToken = ScriptApp.getOAuthToken();
  
  //スプレッドシート情報を取得する
  var sheetname = "ここにPDF化するシート名を入力する";
  var sheetid = SpreadsheetApp.openById(ssid).getSheetByName(sheetname).getSheetId();
  
  //送信パラメータを組み立てる
  var payload = {
      "key": ssid,
      "sheetid" : sheetid,
      "sheetname" : sheetname,
      "accesstoken": accessToken,
      "passwd": "tomatoman",   //PDFのパスワードを設定します。
  };
  
  //POSTで関数を実行する
  var response = UrlFetchApp.fetch(url, {
    method: 'POST',
    contentType: "application/json",
    payload : JSON.stringify(payload),
    muteHttpExceptions: true
  });
  
  //サーバーレスポンスコードを取得する
  var resCode = response.getResponseCode();

  //リターンされて来たバイナリデータをドライブに作成する
  if (resCode === 200) {
    //バイナリデータを取得する(ファイル名も付けておく)
    var binary = response.getBlob().setName(sheetname + ".pdf");
    
    //Google DriveにパスワードPDFを生成する
    DriveApp.getFolderById(folder).createFile(binary);
    
    //終了メッセージ
    ui.alert("パスワードPDFファイルが生成されましたよ!!");
    
  }else{
    //エラーメッセージ
    ui.alert(resCode + "エラーが発生しました。");
  }
}
  • GCF側の関数実行用URL、スプレッドシートのID、ドライブのフォルダのIDなどを変数として入力しておきます。
  • GAS側でPDFは生成させないので、今回はGCF側関数にJSON形式でパラメータを渡してあげます。
  • その際に、Access Tokenも必要なので、ScriptApp.getOAuthToken()にて取得しておきましょう。
  • 必ず、POSTで送信します。UrlfetchApp1回だけでパス付PDFが作れるので、時間コストはこれまでのPDF生成と同じです。(GCFのお金は掛かるけれどね)
  • サーバレスポンスコードが200ならば、GCF側からパス付PDFのバイナリデータを受け取り、DriveAppのcreateFileメソッドにて、ファイルを生成します。

今回の開発で300回くらい実行していますが、果たしてコストどれくらいかかったのかな。多分GCF無料枠とGCPの無料枠で済んでいるはず!!

GCF側コード

//標準モジュールを読み込み
var fs = require('fs');

//追加モジュールの読み込み
const {Storage} = require('@google-cloud/storage');
const request = require('request');
const HummusRecipe = require('hummus-recipe');

//バケットとtmpフォルダの指定
const gcs = new Storage();
const bucket = gcs.bucket('drive2gcs');
const temp = "/tmp/"

exports.sspdfget = (req, res) => {
  //JSONリクエストパラメータを取得する
  const json =req.body;

  //個別パラメータを取得する
  var key = json.key;
  var sheetid = json.sheetid;
  var sheetname = json.sheetname;
  var token = json.accesstoken;
  var pw = json.passwd;
  
  //PDF化する為のURLを組み立てる
  var url = "https://docs.google.com/spreadsheets/d/" + key + "/export?gid=" + sheetid + "&format=pdf&portrait=false&size=A4&gridlines=false&fitw=true"
  
  //リクエストオプション
  var options = {
    url: url,
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + token,
      "Content-type": "applcation/pdf"
    },
    encoding: "binary"
  };
  
  //requestにてスプレッドシートをPDF化して受け取る
 request(options, function(error, response, body) {
       if (!error && response.statusCode == 200) {
         new Promise(function(resolve, reject) {
             //ファイルを一時フォルダに保存
             fs.writeFileSync(temp +  sheetname + ".pdf", body, 'binary');
             resolve("OK");
          }).then((result) => {
           	 //ダウンロード完了
             console.log(result);
           
             //パスワード化を実施する
             passwordpdf([sheetname,pw],function (ret){
                 //パスワード化完了
                 console.log(ret);

                 //GCSへアップロードする
                 var options = {
                      destination: "/public/" + sheetname + "_encrypt.pdf"
                 };

                 bucket.upload(temp + sheetname + "_encrypt.pdf", options, function(err, file) {
                      console.log("File Uploaded");
                 }).then(()=>{
                      return;
                 });
             });
           }).then((result) => {
               //ファイルサイズを取得する
               var stat = fs.statSync(temp + sheetname + "_encrypt.pdf");
               console.log(stat.size + "バイト");

              //PDFファイルを取得する
              var pdf = fs.readFileSync(temp + sheetname + "_encrypt.pdf");
           
               //バイナリデータを返却する
              res.writeHead(200, {"Content-Type": "application/pdf"});
              res.write(pdf, "binary");
              res.end();

         });

       } else {
           // エラーハンドラー
          res.status(503).send("エラー");
       }
  });
};

//PDFにパスワード化を実施する
function passwordpdf(args,callback){
    //引数を取得する
    var sheetname = args[0];
    var pass = args[1];
  
    const pdfDoc = new HummusRecipe(temp + sheetname + ".pdf", temp + sheetname + "_encrypt.pdf");
 
    pdfDoc.encrypt({
      userPassword: pass,
      ownerPassword: pass,
      userProtectionFlag: 4
    }).endPDF(function(ret){
      //5秒間スリープを入れる
      sleep(5000, function() {
        callback("Encryption End");
      });
    });
}

//sleep関数
function sleep(waitSec, callback) {
    setTimeout(callback, waitSec);
}

実行してみる

コードを記述したら、

  1. メイン画面の一番下にある「デプロイ」をクリックする
  2. モジュールの準備やコードの精査などが開始されます。問題がある場合にはエラー表示がなされ、ログはStackDriver Loggingに記録されています。
  3. 完全に完了すると、待機する状態になります。
  4. 完全に完了したら、トリガーをクリックし、URLが表示されるのでこれを控えておいて、GAS側コードへ追記する。クリックしただけでは、今回は動作しません(パラメータが無いため)。
  5. 今回のプログラムでは、最後にパスワードPDFのバイナリファイルを返信しています。GAS側でこれを受けてPDFを生成しています。
  6. きちんと実行が完了すると、GCS側のバケット内にシート名_encrypt.pdfが生成されます(少し時間差があります)。
  7. 今回のコードではGCS側にもアップロードしていますが、API的な考えならば、GCSの部分は削除しても問題ありません。

図:encryptなPDFが生成されました。

図:Google Drive側にも生成されました。

関連リンク

Google Apps ScriptでCloud Functionsの関数を実行する【GAS】” に対して2件のコメントがあります。

  1. hajimechan より:

    package.jsonの記述ですが、「”request”: “^2.88.0”,」の最後のカンマは不要でした。
    これのエラー内容が非常に分かりづらかったので、このように記載を直してほしいです。

    ——–
    {
    “name”: “sample-http”,
    “version”: “0.0.1”,
    “dependencies”: {
    “hummus-recipe”: “^1.8.9”,
    “@google-cloud/storage”: “^2.5.0”,
    “request”: “^2.88.0”
    }
    }
    ——–

    1. akanemaru2017 より:

      失礼しました。直しました。
      カンマ削りを忘れておりました。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)