Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8833

Netlify Functions で古のアクセスカウンター(アクセサリー)をつくる

$
0
0

はじめに

既にホスティングサービスが終了している懐かしの「ジオシティーズ」ですが、スケジュール上では 2020/3/31 に全ファイルの削除が行われるようです。

自分にはジオシティーズ上で 2005年くらいまで更新していたサイトがありましたので、FTP でファイルを救出し Netlifyの無料枠にて記念に再ホストすることにしました。

…できたものの何かが足りない。

2020-03-30_01-19.png

アクセスカウンターだ!

ということで Netlify に備わる Lambda なサービス Netlify Functions でアクセスカウンターをふとつくってみることにしました。

お気づきのように、Lambda のインスタンスが寝てしまうとカウンターが「飛ぶ」アクセスカウンター(アクセサリー)となっていますが、現代風に JSON や SVG などを使って処理するようになっています。

Netlify Functions について

Netlify Functionsは静的ファイルホスティングサービス Netlify が提供する、くだけて言えば Amazon AWS Lambda に対するプログラムの自動デプロイの仕組みで、Netlify のアカウントにて(クレジットカード登録が必要な AWS アカウントなしに)Lambda を利用することができます。

この記事で分かること

アクセスカウンターの動きはどうだろうという感じですが、いくつかの処理が参考になるかもしれないと Qiita に投稿することにしました。何か使える部分があったら幸いです。

  • Lambda でファイルシステムを操作する
  • netlify-lambda で追加の webpack.config を構成する
  • Lambda で jsdom を使って DOM を操作する

ソースコードと動作デモ

ここで使われているソースコードを github にコミットしてあります。ソース全容を確認しながら読んでいただくと分かりやすいかも知れません。

ソースコード一式

Netlify Functions Template. Includes joke access counter sample program.

https://github.com/h1romas4/netlify-functions-template

動作デモ(しばらくアクセスがないとカウントが 1に戻ります)

https://maple4estry.netlify.com/

Lambda でファイルシステムを操作する

Netlify Functions(Lambda) でも nodejsの fsオブジェクトを使ってファイルを操作することが出来ます。

fsで作成を行ったファイルは、この「アクセスカウンター」の動きどおり、インスタンスのリビルドなどのタイミングで消されますし、スケールした場合は値が分裂しますので、アルゴリズムとしてステートの性質をあてにしてはいけません。

API へのアクセス数をカウントする JSON ファイルを /tmpに書き込む処理抜粋:

importfsfrom'fs';import*ascommonfrom'../common'// async await 版の fs オブジェクトconstfsp=fs.promises;// カウンターファイル JSON 出力パスconstcounterJsonFile=common.tmp+"/counter.json";/**
 * Lambda エンドポイント
 */exports.handler=async(event)=>{constnow=newDate();letcounterJson={"createDate":now,"updateDate":now,"count":1};try{// カウンターファイル存在確認constdata=awaitfsp.readFile(counterJsonFile);// カウンターファイルが存在すれば JSON 解析してカウンターを更新counterJson=JSON.parse(data.toString('utf-8'));counterJson.updateDate=now;counterJson.count++;}catch(e){// カウンターファイルがなければ例外を潰して新規作成}// カウンターファイル書き込みawaitfsp.writeFile(counterJsonFile,JSON.stringify(counterJson));// ...}

Lambda のハンドラーを exports.handler = async (event)asyncとして fsの操作は fs.promisesからもらったオブジェクトで awaitしてあげると簡単でした。

ちなみにこの処理は readFileから writeFileまでに間がありますのでアトミックなカウントアップはできません。また nodejs の fs の実装を確認していませんが、ジオシティーズ世代の CGI カウンターよろしく壊れる可能性もあるでしょうか?(懐かしい)。

さて、/tmpは Lambda で使えるテンポラリー領域となっていますが、開発を行うローカル環境でそのまま /tmpにかかれると確認などが面倒なためちょっと小細工をしています。

AWS で設定される環境変数の有無で /tmpの位置を切り替え:

// カウンターファイルを作成するテンポラリーディレクトリexportlettmp='/tmp'// AWS Lambda で動いていない場合はテンポラリーを dist/tmp に設定if(!("AWS_REGION"inprocess.env)){tmp="./dist/tmp"try{fs.mkdirSync(tmp);}catch(e){}}

Netlify Functions から提供されている netlify-lambda SDK が ./dist/api以下にビルドを出力するため、合わせてテンポラリー領域を ./dist/tmpに設定しています。

netlify-lambda で追加の webpack.config を構成する

netlify-lambda SDK は内部的に webpack を使ってビルドしていますが、標準の webpack 設定をマージできる --configオプションが準備されています。

netlify-lambda --config ./webpack.functions.js build src/functions/endpoint

本アクセスカウンターでは、古では GIF 画像などで処理していたカウント画像に代わり、SVG/CSS/HTML を Lambda 上で処理し数字を描いてクライアントに返却したく、処理前の .htmlを文字列として importするため raw-loaderを webpack に追加設定しています。

// webpack.functions.jsmodule.exports={optimization:{minimize:false},module:{rules:[{test:/\.html$/i,exclude:/node_modules/,use:'raw-loader'}],}};

この設定によりプログラムから html文字列変数として importができます。

// counter.jsimporthtmlfrom'../resources/fujilcd.html'

このような追加の webpack 設定がある場合は --configオプションを活用すると良さそうです。

なお、Lambda 上にデプロイするソースをミニマイズしてもあまり意味がないため、どのプロジェクトでもこのコンフィグで optimization: { minimize: false },を入れておくとビルド時間短縮に役立つかもしれません。

Lambda で jsdom を使って DOM を操作する

nodejs 上で html などの DOM 操作を行う jsdomパッケージを Netlify Functions(Lambda?) 上で使う場合は少々コツが必要なようです。

通常通り packege.jsonに依存関係をいれると、

Error while initializing entrypoint: { Error: Cannot find module 'canvas'

というエラーで canvasパッケージを依存に入れても動作しませんでした。 issue を探ったところワークアラウンドがありましたので、この方法で回避しています。

https://github.com/jsdom/jsdom/issues/1708#issuecomment-462990288

I was able to work around this by adding "canvas": "file:./canvas" to the dependencies section of the app's package.json and creating canvas/index.js containing simply module.exports = {}.

プロジェクト上に空の canvasモジュールをつくって依存に追加:

// package.json{"dependencies":{"jsdom":"^16.2.1","canvas":"file:./src/functions/canvas","utf-8-validate":"^5.0.2","bufferutil":"^4.0.1"},}

jsdomが導入できれば、先程 import したような文字列 HTML を new JSDOM(html)で DOM 化しウェブブラウザーと同様に document.querySelectorなどで操作することができます。

importしたカウンター表示 HTMLを DOM 操作して CSS クラスを付けてカウント数を表示:

// counter.jsfunctionupdateLCD(html,number){constdom=newJSDOM(html);const{document}=dom.window;letcountString=number+""countString=("0".repeat(maxCount)+number);countString=countString.substring(countString.length-maxCount)for(leti=0;i<countString.length;i++){letnumber=countString.substring(i,i+1);constdigi=document.querySelector(`.digit-${i} svg`);digi.removeAttribute('class');digi.classList.add(`num-${number}`);}returndom.serialize();}

jsdomdom.serialize();にてできた DOM を文字列化できますので、これをアクセスしてきたウェブブラウザーに返却し、

// counter.jsexports.handler=async(event)=>{// ...// カウンター数 SVG を生成counterJson.html=updateLCD(html,counterJson.count);return{statusCode:200,headers:{"Content-Type":"application/json; charset=utf-8",},body:JSON.stringify(counterJson)}};

ウェブブラウザーは shadowDOM で画面にそのまま書き出しています。

/v1/counterとして Netlify Functions にデプロイした Lambda を呼び出すクライアントのソース:

<!DOCTYPE html><html><body><access-counter></access-counter><script>fetch('/v1/counter').then(function(response){returnresponse.json()}).then(function(json){customElements.define('access-counter',classextendsHTMLElement{constructor(){super();constshadowRoot=this.attachShadow({mode:'open'});shadowRoot.innerHTML=json.html;}})});</script></body></html>

終わりに

現代版ジオシティーズとも言える Netlify を今回初めて使ってみたのですが、Netlify Functions 含めいろいろできて便利でした。

試しに検索エンジン API もつくってみましたので、自分のブログになってしまっていますが、気になる方がもしいらっしゃいましたらご覧ください!

Netlify Functions で検索エンジン API をつくる

ふと思いつきまして Netlify で使える Lambda なサービス、Netlify Functions を用いて参照系検索 API を作成してみました。無料枠での挑戦です。

netlify-01.jpeg


Viewing all articles
Browse latest Browse all 8833

Trending Articles