AWS lambdaでステートフルWEBアプリを使う方法


# はじめに
AWS lambdaでWEBアプリを動かしたいけど、API Gatewayの仕様が窮屈で諦めた。
ステートフルなWEBアプリをステートレスに改修するのは面倒。
そのような方に、API Gatewayを使わない構成をご紹介します

# AWS lambdaでWEBアプリは無理？
AWS lambdaの主な特徴

* サーバの面倒をみなくてよい
* 使わないと料金が発生しない
* 急なリクエスト増にも標準で対応できる

この最初の２つの特徴を知って、多くの人が驚き、喜び、そして、

サーバーの面倒みなくていいし、使わない時間の料金は請求されなくて安いし、
月に2, 3回数人が数分使うだけの社内の簡易WEBアプリを動かすのに最適じゃない？

と、ごくごく自然に考えます。

じゃ、さっそく試そうとすると、

![2018-07-24 09_46_37-lambda-ngrok - draw.io.png](https://qiita-image-store.s3.amazonaws.com/0/133603/239416bd-050e-6981-25bf-f25708db824c.png)

AWS lambdaをHTTPSで使うには、図のようにAPI Gatewayを前段に使う必要があることがわかり、
じゃ、API Gatewayを使おうとすると、

* API Gatewayには最大30秒のタイムアウト制約があり、AWS lambdaを30秒しか動かせない
* API Gatewayが、常に同じlambdaインスタンスを使ってくれる保証がない
* API Gatewayがヘッダーを書き換えてしまうので、WEBアプリに実装されているBASIC認証が使えない。API Gateway専用のlambdaを作る必要がある
* websocketが使えない

と、いろいろ面倒（仕様上の制約）な壁にぶつかります。


WEBアプリを改修すればAWS lambda + API Gatewayで動かせますが、
そもそも利用頻度も低く手間をかけたくないという動機なので、
わざわざWEBアプリをステートレスに改修する手間はかけたくありません。

できないじゃん、、、残念

と私も諦めていました。**この方法**を発見するまでは。

# この方法でAWS lambdaのWEBアプリにHTTPSアクセスできる

[Ngrok](https://ngrok.com/)(エングロック)というサービスをご存知でしょうか。
URLのエンドポイントを提供してくれる、開発者に人気のリバースプロキシ―サービスです。
PC上で開発中のアプリを起動し、Ngrokで一時的なURLを取得してインターネットに公開。
インターネットからのアクセスを開発中アプリに流すことができます。

ある時思い立って、AWS lambdaからNgrokに接続しやると、
**Ngrokで取得したURLで、ステートフルなWEBアプリが普通に使える**ではありませんか!!


この図の構成が普通に動くのです。
![2018-07-24 09_59_04-lambda-ngrok - draw.io.png](https://qiita-image-store.s3.amazonaws.com/0/133603/04cd5568-5b50-b8f4-2a42-844897d48ab1.png)

この構成であれば、

* 30秒のタイムアウト制約はありません。300秒AWS lambdaの実行時間最大まで使えます
* 同じインスタンスがリクエストを処理してくれるので、ステートフルWEBアプリがそのまま動きます
* WEBアプリに実装されているBASIC認証がそのまま使えます
* websocketも使えます

今まで、WEBアプリをlambdaで動かすには、次の記事のように

* [Node-REDをAWS API Gateway + lambda + S3で動かす方法]
(https://qiita.com/sakazuki/items/9d55ac14432e73524e04)

[AWS Serverless Express](https://github.com/awslabs/aws-serverless-express)などを使って改修して動かすことができましたが、
この構成では、Ngrokに接続するラッパーだけ書けばいいのでとても簡単です。

# サンプル

この構成のサンプルアプリ(Node.js + express)をGithubに登録しましたので試してみてください。

* [sakazuki/lambda-ngrok-architecture](https://github.com/sakazuki/lambda-ngrok-architecture)

```javascript
'use strict';

const app = require('./app');
const PORT = 3000;
const ngrok = require('ngrok');
const EventEmitter = require('events').EventEmitter;
const ev = new EventEmitter();

const slack_url = process.env.SLACK_URL;
const https = require('https');
const url = require('url');

const postSlack = (msg) => {
  const opts = url.parse(slack_url);
  opts.method = 'POST';
  opts.headers = {'Content-Type': 'application/json'};
  const req = https.request(opts, (res) => {
    console.log("Send a slack message: " + res.statusCode);
  });
  req.on('error', (err) => {console.log(err)});
  req.write(JSON.stringify({text: msg}));
  req.end();
}

module.exports.server = (event, context, callback) => {
  let URL;
  // (1)
  const server = app.start(PORT, context, (req, res) => { // (4)
    ngrok.kill();
    console.log('Receive Bye request');
    postSlack('Closed ' + URL);
    ev.emit('kill');
  })
  // (2)
  ngrok.connect(PORT, (err, url) => {
    if (err) {
      callback(err, null);
      return;
    }
    URL = url;
    ev.once('kill', () => {
      const response = {
        statusCode: 200,
        body: JSON.stringify({ message: 'Good bye' }),
      };
      server.close();
      console.log('App closed.');
      callback(null, response);
    })
    postSlack('Go ' + URL);  //(3)
    console.log('Connect to %s', URL);
  })
};
```

簡単に解説すると

1. アプリを`Port 3000`で起動する
2. Ngrokで`Port 3000`を公開して、一時URLを取得する
3. 一時URLをSLACKで通知する
4. 特定のリクエストがきたら終了する

* 4の終了処理をきれいにやるためにコードが少し多くなっていますが、lambdaのタイムアウト終了でよしと割り切ればシンプルにできます


# おわりに

AWS lambdaの中からリバースプロキシ(Ngrokなど)との間に接続を張り、リバースプロキシ経由でWEBアプリを利用する。これでAPI Gatewayの仕様上の制約から解放されて、AWS lambda上でステートフルなWEBアプリを使うことができました。

![2018-07-24 10_08_49-lambda-ngrok - draw.io.png](https://qiita-image-store.s3.amazonaws.com/0/133603/2cc91860-8624-3c38-32a4-5ae672ea4b56.png)

AWS lambdaの最大実行時間300秒の制約の範囲内でですが、
社内のちょっとした処理など一回2,3分アプリが動けば十分なケースで使える構成かと思います。試してみてください。

# 補足

- この記事は[Serverless Meetup Tokyo #8](https://serverless.connpass.com/event/93939/)のLTで話した内容です
- 参考　[LT資料@SlideShare](https://www.slideshare.net/kazukisaito127/web-106289403)
- 他の言語や他のWEBサーバーでも同様のアプローチでWEBアプリを使う事ができるようになります
- Ngrokはセキュリティーが気になるという場合は、自前で[localtunnel](https://github.com/localtunnel/localtunnel)サーバーを建てるか、SSHポート転送を使う事でも問題を解決できます。