AWS API Gateway を使ってサーバレスな ifconfig API を作ってみる

サーバが自分自身のグローバルIPアドレスを調べるとき面倒なことがよくあるので、API Gateway を使って ifconfig のようなことをする。単純に、リクエストしてきたIPアドレスを返すだけ。(こういうサービスは他にもあるけど、レスポンスが遅めだったり、信用できるか分からなかったりするので、自分で作るのが安全そう)

curl https://ip.ecp.plus

としたら自分のIPアドレスが返るというのがゴール。ブラウザで見ても同じ。ちなみに↑はもう動いているのでご自由にお使いください。

最初、API Gateway -> Lambda として、Lambda に IPアドレスを渡して、Lambda はそれを返して、API Gateway はレスポンスをそのままスルーする。というのをやったが、実は Lambda なしで出来たようだ。

はじめ Lambda を使ったバージョンをやったので、両方書く。

API Gateway の設定

Create Method

URLは短くしたいので、ドメイン直下をGETしたときに返したい。 / に対して Create Method で GET を指定する。

Method Request は認証などしないのでそのまま。

/ – GET – Method Execution

API Gateway にアクセスしてきた IP アドレスは、下記のようにして取れる。

{
"ipaddress" : "$context.identity.sourceIp"
}

のだが、API Gateway のデフォルトで割り振られる URL は非常に長く、自分のドメインで使いたい。Custom Domain Names を使うためには、TLS の証明書が必要なのだが、お手軽さが無いのでやめた。既に動いている Caddy でリクエストを転送することにした。

ただ、そうするとリクエスト元のIPアドレスが Caddy が動いているサーバの IPアドレスになってしまうため、ヘッダーの X-Forwarded-For を見ることにする。ここの先頭に、オリジナルのIPアドレスが入ってる。

{
"ipaddress" : "$input.params().header.get('X-Forwarded-For')"
}

Lambda

ipaddress として渡ってきたデータの中から、一番最初のIPアドレスを返すだけの処理を書く。

exports.handler = function(event, context) {
context.succeed([event.ipaddress.split(/\s*,\s*/)[0], "\n"].join(''));
};

/ – GET – Integration Response

再び API Gateway に戻ってきて、レスポンスの加工をする。一見、Output passthrough で良さそうだが、これだとダブルクオーテーションで囲まれたものが返ってしまう。

Content-Type: application/json で Mapping Template として下記のようにする。

$input.path('$')

これでAWS側の実装は完了。

Caddy の設定

ip.ecp.plus に来たものを API Gateway に飛ばす。API Gateway 側で GET / を叩くために、転送先の末尾にスラッシュを忘れず付ける必要がある。

ip.ecp.plus {
proxy / https://EXAMPLE.execute-api.ap-northeast-1.amazonaws.com/prod/
}

とりあえずここまでで完成。

だが、Lambda がどう見てもあまり意味がなさそうだったので、API Gateway だけで出来ないか見ていたところ、開発中に使う用途と思っていた Mock Integration を使えば出来た。

API Gateway の設定(再)

/ – GET – Method Execution

Integration type として Mock Integration を選択。Mapping template として

{
"statusCode" : 200
}

のようなものが定義されてるが、これがないと勝手にエラーにされてしまうのでそのままにしておく。

/ – GET – Integration Response

どうやら、Response 側でも、リクエストで渡ってきた値がそのまま取れるようなので、こちらにIPアドレスを返す処理を書けば良い。

#set($ipAddresses = $input.params().header.get('X-Forwarded-For').split(','))
$ipAddresses[0]
#set($dummy = "dummy")

最後の行でよくわからないことをしているが、レスポンスの末尾に改行を入れたいためこうしている。”\n” をどうにか入れたかったが、うまく行かずバッドノウハウ的な解決方法になっている…。本当は良い方法があるはずだ。

ここまでで完成。

尤も、こんな単純なものなら Caddy 動かしてるサーバで処理しろよという感じだけれど、最近 API Gateway にハマっているので作ってみた。API Gateway 単体で動かないと、Caddy のサーバが落ちたときに使えなくて激しく意味がないが、Custom Domain Names の設定までやるのは面倒であった。Route53 使ってたらシームレスに連携出来たりすれば良いのに、と思った。

(追記)CloudFront を使ってサーバレスに出来た

その後、Caddy の代わりに CloudFront を使えば、TLS証明書も無料で、好きなドメインで出来ることが分かった。

CloudFront の画面から、Create Distribution -> Web の Get Started で、作成画面へ入る。

Origin Domain Name は、API Gateway の Invoke URL を指定する。https:// を付けても勝手に外してくれるので、URLコピペで良い。stage名は消す。つまりドメイン部分だけを記述する。
Origin Path は、stage名を入れる。 prod なら /prod とする。

あとは項目を読みながら設定すれば良い。CloudFront は独自ドメインのTLS証明書が無料なので、とてもありがたい。注意点としては、 CloudFront にキャッシュされると前に実行した人のIPアドレスが返ってしまうので、キャッシュさせないようにしなければいけない。
Behaviors のタブで、初期から存在しているものを編集して、Object Caching : Customizeとして、Minimum TTL : 0, Maximum TTL : 0, Default TTL : 0 とすれば良い。

これで、自己管理するインフラ無しで、当初のやりたかったことが出来た。

API Gateway から Export した Swagger 用の YAML

https://gist.github.com/ecpplus/1373c676f602326955f8

AWS API Gateway を使ってサーバレスな ifconfig API を作ってみる

HamlでTextarea(Rails)

Rails + Haml で textarea を出力しようとすると、textarea 内に表示しようとしたテキストまでインデントされてしまう。

find_and_preserve というヘルパーメソッドを使えば、中身のインデントは保持されるようだ。

= find_and_preserve(f.text_area)

Simple Form を使ってる場合も、同様にいける。

= find_and_preserve(f.input :comment, as: :text)

参考URL: How do I stop Haml from indenting the contents of my pre and textarea tags?

HamlでTextarea(Rails)

MacでIEの確認をリモートで行う

IEの確認で、Azure の RemoteIE というのが知らないうちに出てた。これだと、手元に virtual machines のイメージを置いておかなくて良いので、ディスク容量の節約になる。

登録すれば無料で使えるようだが、inactive な時間が10分続くか、 active な状態でも60分経過すると切断される制限がある。短時間の確認なら問題無さそうだが、長時間の確認は繋ぎ直す必要がありそう。

まずは、RemoteIE で Register for access して、自分の Region を選ぶ。日本なら East Asia が良い。

Mac で使う場合は、Microsoft Remote Desktop アプリを App Store からダウンロードして、Azure RemoteApp というところをクリックすると、ログイン画面になるので、ログインする。
すると、InternetExplorer(email: iewebeco@microsoft.com) という Invitations があるので、チェックを入れると自動的に起動する。いきなり IE の画面が開いているので検証できる。

MacでIEの確認をリモートで行う

kswapd0 プロセスが CPU を食ってた

Load average が高いので、top でプロセスを見たら kswapd0 というプロセスが CPU を使いまくってた。

メモリがスワップアウトしたときに、ディスクに書き込んだりするプロセスらしいけれど、貧弱なサーバだとその処理が重くて Load average が上がってサーバが遅くなって…のループでつらそうだった。

vm.drop_caches の値を変えることで、メモリ内にあるキャッシュを削除して、このプロセスがやるべきことをなくしてあげれば CPU は解放されるようだ。

例えば、ページキャッシュだけ解放するときは、下記のようにする。sync をして、強制的にキャッシュの内容をディスクに書き込んだ後に実行します。

sudo sync
sudo /sbin/sysctl -w vm.drop_caches=1

数字は、下記の中から適切なものを選ぶ必要がある。

  • [0] 初期値
  • [1] ページキャッシュ解放
  • [2] dentry、inode 解放
  • [3] ページキャッシュ、dentry、inode 解放

Linux におけるメモリの解放

なお、0 を指定するとエラーになって設定が出来ないのだが、よく分かっていない。

$ sudo /sbin/sysctl -w vm.drop_caches=0
sysctl: setting key "vm.drop_caches": Invalid argument
vm.drop_caches = 0
kswapd0 プロセスが CPU を食ってた

AJAXでファイルダウンロード

バイナリでもテキストでも、AJAX でリクエストしたら JavaScript で受けることになるので、ブラウザにファイルをダウンロードさせるように処理を作る必要がある。

Blob オブジェクトはファイルに似たオブジェクトで、immutable な生データです。データを表す blob は必ずしも JavaScript ネイティブなフォーマットではありません。File インターフェースは Blob を基礎にしており、その機能を継承する一方で、ユーザのシステム上のファイルをサポートするための機能を拡張しています。

https://developer.mozilla.org/ja/docs/Web/API/Blob

とのことです。レスポンスから Blob オブジェクトを作って、createObjectURL すれば、ダウンロード可能なURLを生成する事ができます。

よくありがちな、CSVデータを管理画面からダウンロードさせたい、みたいなパターンを jQuery + ES6 で書いた場合の例。

IEの場合、IE10以上であれば msSaveBlob という、Blob をダウンロードするという機能を使うことが出来る。それ以外のブラウザは、createObjectURL を使って URL を生成したら、a タグの href にセットしてそれにクリックイベントを発火させるという方法で実現できます。

$.ajax({
  url: 'https://example.com/api/admin/users.csv',
  type: 'GET',
}).done((data, status, jqXHR) => {
  let downloadData = new Blob([data], {type: 'text/csv'});
  let filename = 'users.csv'

  if (window.navigator.msSaveBlob) {
    window.navigator.msSaveBlob(downloadData, filename); // IE用
  } else {
    let downloadUrl  = (window.URL || window.webkitURL).createObjectURL(downloadData);
    let link = document.createElement('a');
    link.href = downloadUrl;
    link.download = filename;
    link.click();
    (window.URL || window.webkitURL).revokeObjectURL(downloadUrl);
  }
}).fail((data, status, jqXHR) => {
  alert('OMG!');
});      
AJAXでファイルダウンロード

WKWebViewでnetworkActivityIndicatorVisibleを表示

networkActivityIndicatorVisible (iPhoneの上部のステータスバーで読込中にぐるぐるさせるやつ) を表示する方法。UIWebView は、html,js,css,画像などの区別がつかなくて結構大変だったけど、そういうの気にせずだいぶ楽に実装できる。

Continue reading “WKWebViewでnetworkActivityIndicatorVisibleを表示”

WKWebViewでnetworkActivityIndicatorVisibleを表示