Serverlessを使ってブラウザだけでJavaScriptを実行してみる

### はじめに

Serverlessのクラウドサービスを利用して、JavaScriptを実行(ランタイムはNode.js)してみました。常時稼働しなくて良いが、リクエスト投げた時だけ動いて欲しい。定期的に実行したい。そんな時に使えそうです。

ここではIBM Cloud Functions(以下、ICF)を使いました。
当記事は「サーバーレスというものを試してみたい」「IBM Cloud Functionsを使ったことはない」という方向けの記事なので、より深い内容は別記事をあたっていただければと思います。
なお、厳密にはライト(無償)プランではなく、平均実行時間や実行回数などで課金されていくのですが、記事を書いている時点(2020/11)ではライトアカウント(無償)でも使えました。また無料枠があるので、ここに記載されている内容を試すくらいであれば無料で試せると思います。

こちらの[チュートリアル](https://developer.ibm.com/jp/tutorials/no-infrastructure-just-code-see-the-sim-plicity-of-serverless/)を基に少し手を加えたものを記事にしています。

他に参考にした記事
- [マニュアル](https://cloud.ibm.com/docs/openwhisk): ここにチュートリアルがあります。
- [セミナー資料](https://ibm-developer.connpass.com/event/176802/presentation/): ICFの紹介とハンズオン手順があります。
-  なお、IBM Cloudを利用されたことがない方が始めるための手順の記事は[こちら](https://qiita.com/ayatokura/items/42d4bc728112c733c80d)。



では、早速試してみます。順調にいけば1時間足らずで全て試せると思います。

---

### IBM Cloud Functionsのアクションを作成する
アクションとは、「サーバーレスで動かすコード」といったところでしょうか。単体の実行や、単体の実行結果を渡して次につなげる「シーケンス」というものもできます。

1. IBM Cloudにログイン
https://cloud.ibm.com/

2. 左上のナビゲーション・メニューをクリックし、「Functions」を選択

3. (この手順はやらなくても先に進めますが)名前空間(namespace)を作ります。
namespaceとは、ICFで作成・実行するアクションやトリガーなどの入れ物です。ユーザーごとにアクセス権限管理もできるようです。

画面上部の「現在の名前空間」のプルダウンをクリックし、一番下の「名前空間の作成」をクリックします。

- 名前: 任意。ここではfunctions-test
- 以下はそのままで「作成」をクリック

現在の名前空間: functions-test がでてくれば成功です。
以降の手順はこの名前空間で作業していきます。
ちなみにこの名前空間、一旦作ると消すことができないみたいです・・・。

4.「作成の開始」をクリック
5. 「作成」画面で一番左の「アクション」をクリック
6. 「アクションの作成」画面で

- アクション名: 任意。ここでは hello
- 以下はそのまま　で「作成」をクリック


7.コードのエディターが開きます。Hello Worldのサンプルコードが入っています。右上の「起動」をクリックして試してみます。
8. 以下のように実行できました。
![icf_helloworld.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135937/7318925f-7245-8d59-cd6d-be2edd550d0d.png)


### パラメータを使ってアクションを呼び出す

9.少しアプリを変えてみましょう。エディターで、コードから、function main(params) {...} 部分を削除するか、同じ部分をコメントアウト /* ... */で囲みます。その後、以下をコピペし、右上の「保存」をクリック。
このコードは、引数にnameとplaceがあり、これを渡すことで文を出力します。


```
 function main(params) {
    return { message: "Hello, " + params.name + " from " + params.place };
 }
```

10.「パラメータを付けて起動」をクリックするとポップアップが現れるので、以下のように{}で値を囲むようにコピペ(nameとplaceの値(sirotan, Japan)は適当なので、適宜変更してください)し、「適用」。


```
{
   "name": "sirotan",
   "place": "Japan"
}
```

11.その後「起動」すると、以下のように結果が変わりました。
![icf_helloworld2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135937/19d2d372-8314-dbcb-daa4-75f2c8857648.png)



### トリガーでアクションを呼び出す
トリガーとは、先ほど作成したアクションを定期的に実行したり、何らかのイベントをきっかけに実行するためのものです。

12.以下のように左上の「Actions」をクリックして元のメニューに戻ります。
※ このWebサイトのツリー構造を示すものを[パンくずリスト](https://ja.wikipedia.org/wiki/%E3%83%91%E3%83%B3%E3%81%8F%E3%81%9A%E3%83%AA%E3%82%B9%E3%83%88)っていうらしい。筆者は初めて知りました(汗
![pankuzu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135937/c4251d80-6acc-9fb0-7698-f474e0eae122.png)

13.もう一つアクションを作成します。手順5~6と同様です。アクション名は先ほどのもの(hello)とは違うものにしてください。
        - アクション名: 任意。ここでは gettime
        - 以下はそのまま　で作成をクリック


14.以下のコードをコピペします。エディターで、コードの中身の function main {...} 部分を削除するか、コメントアウト /* ... */で囲みます。その後、以下をコピペし、「保存」します。
このコードでは、実行時の日時と、何回目の実行かを表示します。

```
 var counter = 0; // global variable
 function main(msg) {
     var date = new Date(Date.now() + ((new Date().getTimezoneOffset() + (9 * 60)) * 60 * 1000));
     var time = date.getHours() + ":" + date.getMinutes() + ":" +
     date.getSeconds();
     counter++;
     return { message: "It is " + time + ". This action has been called " + counter + " time(s)." };
 }
```

15.このgettimeアクションを繰り返し実行させたいと思います。コードエディターの画面左「接続されたトリガー」をクリック。その後、右上の「トリガーの追加」をクリック
16. 「Periodic」をクリック
17. 以下を設定し、「作成&接続」をクリック
  
- トリガー名: 任意。ここでは minute alarm
- タイマー設定: 一番下の「UTC分」の右上のパターン選択から「毎分」を選択

18.左上のパンくずリストから「Action」->左メニューの「モニター」を選択。トリガーに接続されたアクション(minute alarm)が、赤枠のように約1分おきに呼び出され、アクション「hello」を実行されていることが確認できるかと思います。
また、画面右の「アクティベーション・ログ」に実行ログが出ていますが、青枠のアクション名(helloやminute alarm)の下にある文字列は実行時のID(Activation ID)です。クリックするとブラウザの別ウィンドウが開き、実行内容が表示されます。

![icf_monitor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135937/e8d8c81c-6d67-e30e-c9cd-88e6136e0058.png)

19.作成したトリガーを停止します。
左メニューの「トリガー」-> 該当のトリガー(ここでは、minute alarm)をクリック ->右の「接続」列にある「有効」のチェックを外す。
これを忘れるとずっと実行され続け、意図せずに料金が発生する恐れがありますのでご注意ください。

### Webアクションを作成する
これまではICFの画面内での実行結果表示でしたが、HTMLで実行結果を表示したいと思います。

20.先ほど作成したアクション: helloのコードを以下のようにupdateし、右上の「保存」をクリック。
これをHTML形式でメッセージを表示します。

```
function main(params) {
    let html = '<html style="color:red"><body><p>' +
    'Hello, ' + params.name + ` from ` + params.place +
    '</p></body></html>'
    return { headers: { "Content-Type": "text/html" },
             body: html };
}
```

21.左メニューの「エンドポイント」をクリックし、「Webアクションとして有効化」にチェックして、「保存」。
22. 画面中程の「HTTP メソッド」にあるURLリンクを開くとメッセージが表示されます。
23. 引数を渡すには、ブラウザのURLの最後(/helloの後)に以下のように値を追加します

```
?name=sirotan&place=Japan
```

HTMLで表示されました。
![icf_helloworld_web.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135937/38241862-02b3-cba5-f3e2-254a06a3d28a.png)


以上です。
同様に、gettimeアクションでも可能です。コードを以下のようにしてみてください。

```
var counter = 0; // global variable 
function main(msg) {
     var date = new Date(Date.now() + ((new Date().getTimezoneOffset() + (9 * 60)) * 60 * 1000));
     var time = date.getHours() + ":" + date.getMinutes() + ":" +
     date.getSeconds();
     counter++;
 
    let html = '<html style="color:red"><body><p>' +
    'It is ' + time + '. This action has been called ' + counter + ' time(s).'
    '</p></body></html>'
    return { headers: { "Content-Type": "text/html" },
             body: html };
}
```
---
### まとめ

ここまで読んでいただきありがとうございます。
割と簡単に、ブラウザだけでJavascriptを実行し、コード変更や定期実行、実行結果をHTML表示できました。
ちょっとでも興味を持っていただければ幸いです。

他の言語、CLIでの実行、アクションのAPI呼び出しや、複数のアクションの実行(シーケンス)も可能ですが、それはまた別の機会に試そうと思います。