まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

2021/03

ラズベリーパイに Raspberry Pi OS をインストールする場合、多くのケースで Lite 版以外のデスクトップ版やサードパーティアプリまで含まれた拡張デスクトップ版を使うと思っています。この2つであれば初期セットアップの段階からデスクトップ GUI による操作が可能となり、無線LANの設定含めて便利に環境構築ができます。逆に Lite 版を選んだ場合は初期段階では CUI による操作が必要になり、ある程度まではキーボードによるコマンド実行や環境構築が必要になります(正しいキーボード設定をするまではキーボードレイアウトも合っていない可能性もあります)。慣れないと不便ですが、一方で余計なものが含まれていない状態であるとも言え、非常に軽量で動作する環境でもあります。特にラズベリーパイゼロ(以降「ラズパイゼロ」)のようにリソースの限られたハードウェアを使おうとすると、デスクトップ版だと起動の時点でリソースの多くが使われてしまい、初期セットアップ作業もかなり時間をかけて行う必要がでてきてしまいます。そんなラズパイゼロでは作業効率を考えると Lite 版で初期セットアップを行い、必要に応じて後からデスクトップ環境も導入する、という使い方も考慮する必要があります。

さて、この面倒な状況をなんとかならんか・・・と考え、短絡的に「デスクトップ環境をもっと軽量にできないか?」と試みることにしました。自分は 30 年近く UNIX を使っていて、自分が使い始めた当初は "X Window" なるウィンドウシステムが(正確には X11 が)使われ始めていた、という時期でもありました。当時の自分は今ほど CUI に理解はなく(苦笑)、GUI オペレーションが可能な X Windows を使いたくて個人の環境でも試行錯誤していた時期でした。

#より正確には当時の Linux(Slackware) でもインストール時に X Window を含める選択をすることは可能でした。が、標準機能で導入される X Window は twm(Tab Window manager) と呼ばれる、見た目も操作感もイマイチ・・・な GUI で、「これじゃなくて大学の研究室で使ってるやつを使いたい」と試行錯誤していたのでした。その「大学の研究室で使ってるやつ」は現在ではオープン化された mwm(Motif Window Manager) で、当時は無料ではなかったと記憶しています。事実、僕はこれを買ってまで使っていました(メディアもまだ持ってます)。思い入れのあるウィンドウマネージャーです:
IMG_3176


話を戻すと、要は「ラズパイ(ゼロ)でも X Window だけを入れれば軽量 GUI が実現できるのではないか?」&「今なら mwm も無料で使えるので(折角なので) mwm で使いたい」と考えて、「ラズパイで X Window + mwm を使う方法」を調べたのでこのブログで共有します。なお以下の内容はラズパイゼロでも使えることを確認していますが、普通のラズパイでも動く内容です(普通のラズパイなら普通のデスクトップでもそんなに苦労はしないと思うけど)。


【ラズパイ(ゼロ)に X Window と mwm をインストールする】
まずラズパイ(ゼロ)を Raspberry Pi OSLite 版でセットアップします。ある意味、ここが一番大変だと思ってます。ここに書かれた内容を参照するなどして初期セットアップまでを終えておいてください(以下の手順では SSH 接続やシリアルコンソール接続は必須ではありません):
Raspbian Liteの初期設定 令和2年(2020年)3月版

改めて X Window と mwm を利用する上で必要なツール類を追加インストールします。必須ではありませんが、せっかく GUI を使うならウェブブラウザくらいは・・と思って最後に Chromium(chromium-browser) も含めていますが、不要であれば指定しなくても構いません:
$ sudo apt-get install libxm4 mwm xserver-xorg xinit x11-xserver-utils xterm x11-apps chromium-browser

ツール類のインストールが完了したらホームディレクトリに .xinitrc という名前のファイルを作成します。X Window 起動時の各種設定をするファイルで自分はとりあえず以下の内容にしました(上記のツール類をすべて導入していれば起動エラーは起こらないと思います):
#!/bin/sh

xset s off
xset -dpms
xset s noblank

xsetroot -solid darkslateblue

xeyes -geometry 70x70+5+5 &
xclock -geometry 70x70+105+5 &
xterm -geometry 80x20+100+100 &
exec mwm

これで準備は完了。最後に以下のコマンドを実行して X Window を起動します:
$ startx

成功するとこのような画面が表示されます(この画面で「懐かしい!」と感じる人はアラフィフ以上のオッサンだと思います(笑))。普段見慣れた GUI と比べるとかなりシンプルに感じますが、xterm のタイトル部分でラズパイ環境であることもわかります:
1

※x11-apps パッケージを導入しているので、xeyes や xclocks 以外に以下のページで紹介されているアプリも使えます:
https://packages.debian.org/stretch/x11-apps


せっかくなので導入した Chromium も起動してみます。xterm の画面から起動コマンドを最後に & を付けるのを忘れずに実行すると、指定したアプリが  X Window 内で起動し、xterm も(実行したままにならず)プロンプトが戻って続けてコマンドを実行できる状態になります:
$ chromium-browser &

2


自分の感想ですが、ラズパイゼロだと Chromium を起動すると「さすがにちと厳しいかも・・」という印象の操作感になります(現実的には日本語フォントなども追加でインストールすべきだし・・)。ただ通常のデスクトップ GUI で起動した時と比べると全体的にまだ全然軽いですね。


ちなみに X Window を終了するには背景部分を右クリックして、ポップアップメニューから "Quit" を選択、です。
3


4


昔はこの mwm って有料だったんだよなあ。その頃を知っているので、オープン化されて無料で使えるようになった mwm が簡単に使えるようになったのは改めて感慨深いものがあります。

また、.xinitrc ファイルの最後の "exec mwm" 部分で、(足りない場合は apt-get で導入してから)mwm 以外の別のウィンドウマネージャーを指定することもできます。有名どころはデフォルトだった twm や fvwm2 あたりでしょうかね。まあ使い慣れたものがあったら、それを使うのがいいと思っていますが、僕は「買ってでも欲しかった」mwm 一択です。



いま自分の空き時間を使って、過去に Node.js + Cloudant を使って(普通のウェブアプリケーションとして)作っていたウェブサービスの Node-RED 環境への移植に挑戦しています。要はサーバーサイド JavaScript 実行環境である Node.js を使って過去に開発したウェブアプリケーション(画面や REST API)を、Node-RED の HTTP リクエスト/レスポンスノードや、HTML テンプレートノードを使っても動くように移植することに挑戦している、ということです。それなりに実績のある Node.js アプリケーションを Node-RED 上でも動かすことができれば、プラットフォームとしての Node-RED のポテンシャルを証明することができるのではないか、と考えています。

これを具体的に進めようとすると、まずウェブ画面は(i18n とかを考慮しなければ)HTML テンプレートノードを使えば一通りのことはできると思っています。要するに HTML テンプレートの中に HTML や CSS, フロントエンド JavaScript を含めてしまえば、見た目や挙動含めて一通りの画面を作ることはできると思っています。

問題は REST API 部分です。例えばデータベース(今回は Cloudant)のデータを読み書きするインターフェースを REST API で用意しておき、フロントエンドの画面から REST API を呼び出すことでデータの読み書き更新削除を行うことができるようになります(理論上は)。この REST API を Node-RED の HTTP リクエストノードと、HTTP レスポンスノードと、function などのノードを駆使して必要な機能を実装することができるかどうかが移植の可否になりそうだと思っています。まあ普通にデータを読み書き更新削除検索・・・する程度であれば標準の Cloudant ノードの機能範囲内でできそうな感触を持っています。


さて、今とある Node.js + Cloudant 製ウェブアプリケーションの Node-RED プラットフォームへの移植を設計している中で1つの壁に当たってしまいました。上述のように「普通の」データの読み書きの REST API 化はさほど問題にならないのですが、Cloudant が持つ特徴を使った部分が普通のデータのように扱うことができず、一筋縄ではいかない内容でした。結論としてはなんとなく解決の目処はたったと思っているのですが、その内容と解決までの経緯を含めて以下にまとめてみたので、興味ある方はご覧いただきたいです。


さて、問題となった Cloudant が持つ特徴を使った部分です。Cloudant は NoSQL 型(JSON 型)データベースですが、特殊な JSON フォーマットで格納することでバイナリデータを格納することができます。またその格納されたバイナリデータを(Content-Type 含めて)出力することもできます。この機能を使うことで、例えば画像データを Cloudant に格納して、画像データとして出力する、といったことも可能です。この機能は Cloudant の各種 SDK からも便利に使えるよう関数化されていたりします。

実は Cloudant のこのバイナリデータ格納機能を使っている場合が Node-RED 移植をする上でのネックとなります。Node.js などのプログラミング言語で Cloudant を利用する場合(特に上述の機能を使ってバイナリデータを Cloudant に格納する要件が含まれる場合)、Cloudant の REST API や各種 SDK を使ってデータの読み書きを実装することになります。上述のバイナリデータの読み書きも同様です。バイナリデータを書き込んだり、バイナリデータを Content-Type 含めて(つまり画像データであれば画像データとして)取り出して出力したりできます。実際にアプリケーション開発の中でこの機能を使って実装していることは(個人的にはバイナリデータの格納先に Cloudant を使うことが多いので)珍しくありません。

しかし、この部分を Node-RED に移植できるか? となると話は変わってきます。まず Node-RED からは Node.js 向けの Cloudant SDK を利用することはできません。function ノードの中でがんばって  Cloudant の REST API を呼び出すような JavaScript を書けば Node-RED でできるかもしれません(認証情報をどのように管理するかの問題は残ります。またどうせ JavaScript でゴリゴリ書くというのであれば、そもそも Node-RED をプラットフォームに選択しない方が正しいような気もします)。 この問題を標準の Cloudant in/out ノードだけでバイナリデータの読み書きを扱うことはできないか? と読み替えて考えることにしました。


【方法1 正攻法】
そもそも何が正攻法なのか、という問題もありますが、実は標準の Cloudant in ノード(Cloudant にデータを格納するノード)はバイナリデータを格納することもできます。上述の Cloudant のバイナリデータ格納機能は単に JSON データフォーマットをうまい具合に指定することで実現しているので、データを格納する点までは少しの工夫で実現できるのでした。

ただし、この方法の問題点は格納時ではなく取り出し時にあります。標準の Cloudant out ノード(Cloudant からデータを取り出すノード)は _id 値を指定してデータを取り出すことはできるのですが、肝心のこの部分がバイナリデータ格納を意識することなく、普通に JSON データとして取り出してしまうことしかできないのでした。特殊なフォーマットで格納することでバイナリデータ格納を実現しているのですが、この特殊なフォーマットに合わせた取り出しができないため、書き込むことはできても読み出せない、という問題が残ってしまうのでした。。


【方法2 BASE64 エンコードを利用して独自実装】
なんとなく解決の目処が立っているのがこちらの方法です。データの読み書きそのものは Cloudant の標準ノードを使うのですが、扱うバイナリデータは格納前に BASE64 でエンコードして(標準 Cloudant in ノードで)格納します。そして取り出す際も普通に標準 Cloudant out ノードで取り出した後に該当部分を BASE64 でデコードします。最後に HTTP レスポンスノードの属性で Content-Type ヘッダを指定して、デコード結果(画像バイナリ)を返信する、という方法です。プログラミングによるカスタマイズを駆使した、いかにもプログラマーらしい方法ですが、こちらの方法であれば格納時だけでなく取り出し時にも問題なく実現できそうです。

試しにフローを作ってみました。Github でも公開したので良かったらこちらからフローをダウンロードするなどして後述の手順で試してみてください:
https://github.com/dotnsf/nodered_cloudant_binarydata_io


【方法2 サンプルフローの使い方】
このサンプルを使って、実際にバイナリデータ(画像データ)を Node-RED で読み書きできることを確認してみます。

まずは Node-RED 環境を用意します。個別に用意していただいても構いませんが、最終的に Cloudant データベースを用意する必要もあるので、IBM Cloud を使って用意する方法がおすすめです。なお IBM Cloud を使ってここに書かれた方法で Node-RED 環境を構築した場合は、始めから Cloudant-in / Cloudant-out ノードがインストールされた状態になっているので、後述のこれら Cloudant 関連ノードのインストールは不要です。無料のライトアカウントを使って構築することもできる内容なので、Node-RED 環境がない人が試す上でおすすめの方法ではあります。

上記以外の方法で(普通にインストールするなどして)Node-RED 環境を用意した場合は node-red-node-cf-cloudant ノードを別途インストールする必要があります。右上のメニューから「パレットの管理」を選択し、「ノードを追加」から "node-red-node-cf-cloudant" を検索して追加してください:
2021032101

2021032102


併せて IBM Cloud にログインして Cloudant サービスを追加して利用できるようにしておいてください。繰り返しますが、このあたりあまり詳しい自信がない場合は上述の方法で IBM Cloud 環境内に Node-RED 環境を Cloudant データベースや Cloudant ノードなどとまとめて用意するのがおすすめです。


Node-RED が準備できたら、上述の Github リポジトリを使ってサンプルのフローを構築します。この flow.json ファイルがサンプルのフローそのものです。リンク先のテキスト内容をまとめてコピーし、Node-RED の右上メニューから「読み込み」を選択します:
2021032103


読み込みのダイアログで「クリップボード」を選択し、コピーしていた内容をペーストします。そして「新規のタブ」を選択し、最後に「読み込み」ボタンをクリックします:
2021032104


するとこのようなフロー画面が再現されるはずです:
2021032105


このままだとまだ2つの Cloudant ノード(画面上では "mydb" と表示されている2つの水色ノード)が未接続で使えません。どちらかをダブルクリックして設定ダイアログを表示します。すると Service 欄が一瞬だけ空のまま表示されますが、IBM Cloud の Node-RED 環境であれば接続済みの Cloudant サービスを見つけて接続してくれます。Service 欄に Cloudant サービス名が表示されたら「完了」ボタンをクリックします(もう1つの Cloudant ノードも同様にして Service 欄が埋まった状態にします):
2021032106


このように2つの Cloudant ノードの右上に表示されていた赤い印が2つとも消えればサンプルを動かすための準備は完了です。画面右上の「デプロイ」ボタンでデプロイして動作前の準備は完了です:
2021032101


改めてこのタブを見ると、3つの HTTP リクエストを処理するフローが定義されています:
#HTTP リクエスト処理内容
1GET /home画像ファイルアップロード画面
2POST /file画像ファイルアップロード処理
3GET /file(?_id=XXXX)アップロードした画像ファイルを画像として取り出す処理


1番目の GET /home は後述の 2 と 3 の動作を確認するための UI として、ファイルを指定してアップロードできる画面を表示するものです。実際に /home へアクセスすると、以下のような画面が表示されます:
2021032201


非常にシンプルなファイルアップロード機能を持ったページです。「ファイルを選択」ボタンを選んでローカル PC からファイル(今回は画像ファイル)を選択して「送信」ボタンをクリックです。「送信」すると、2番目 POST /file が実行されて、選択したファイルが Cloudant に格納される、というものです。

ここで試しに以下の画像ファイルを指定してアップロードしてみます(お好きな画像で試してください):
dotnsf_logo_200x200


画像ファイルを指定して「送信」します:
2021032202


こんな感じの HTTP POST の結果が表示されます(実際のアプリでは AJAX を使うなどしてこの結果をそのまま表示しないようにします):
2021032203


この後に Cloudant のダッシュボードなどから mydb データベースの中を確認するとデータが1件追加されているはずです:
2021032204


表示を JSON 形式などに切り替えると、格納されたデータファーマットも確認できます(type に画像フォーマット、data に base64 でエンコードした画像バイナリデータが格納されています):
2021032205


このデータの id 値を確認します(上図だと c7c3eb8e3b9ac0fffcd45c1beea6c62a )。この値と3番目の GET /file を使って格納された画像を表示してします。ウェブブラウザで /file?_id=(id の値) にアクセスして、アップロードした画像が表示されることを確認します:
2021032206
(↑アップロードした画像が復元できた!)


Node-RED を使ってバイナリ(画像)ファイルを Cloudant に格納し、また Node-RED から画像を復元することも実現できることがわかりました。


【方法2 解説】
Node-RED の HTTP リクエストでバイナリデータを格納したり、Node-RED の HTTP リクエストで格納したバイナリデータを取り出すことができる、ということがわかりました。以下はこれを実現している上記フローの解説です。

まず画面 UI である GET /home ですが、これはごく普通に enctype="multipart/form-data" を指定したフォームを定義しているだけです。テンプレートノードの中身は以下の内容の HTML です:
<html>
<body>
<form method="POST" action="/file" enctype="multipart/form-data">
<input type="file" accept="image/*" capture="camera" name="image" id="image"/>
<input type="submit" value="送信"/>
</form>
</body>
</html>

次に POST /file の各ノードを説明します。まず HTTP in ノード(POST /file と書かれたノード)はファイルアップロードに対応する処理を行うため「ファイルのアップロード」にチェックを入れている点に注意してください:
2021032207


また直後の function ノードの内容は以下のようになっています。アップロードされたファイルは msg.req.files に配列で格納されます(今回はファイル1つだけですが、配列の0番目に格納されます)。その mimetype と buffer を取り出し、buffer を base64 エンコードして msg.payload に格納し直して、最後にタイムスタンプを追加する、という処理を行っています(Cloudant データベースにはこのフォーマットで格納されていたはずです):
2021032208
//. アップロードしたファイルを base64 エンコーディング
var type = msg.req.files[0].mimetype;
var img64 = new Buffer( msg.req.files[0].buffer ).toString( 'base64' );

//. 独自フォーマット化
msg.payload.type = type;
msg.payload.data = img64;

//. タイムスタンプを追加
msg.payload.timestamp = ( new Date() ).getTime();

return msg;


この function ノードで処理された msg.payload の内容を Cloudant out ノードが受け取って格納します。このノードでは「Only store msg.payload object?」にチェックを入れて、ヘッダ情報などを格納しないようにしています。これで指定したバイナリファイルを(base64 エンコードして)Cloudant に格納する(同時に _id が割り振られます)、までの処理を実現しています:
2021032209


最後に GET /file(?_id=XXXX) のノードを紹介します。まず Cloudant in ノードでは特別な処理は行っておらず、パラメータとして与えられた _id を使って Cloudant の mydb 内を検索して結果を返す内容にしています:
2021032201


直後の function ノードでは mydb から取り出した結果を画像に戻す処理をしています。上述の function ノードの逆を行う形で、msg.payload.data の値を base64 デコードして画像バイナリに戻して msg.payload に代入し直しています:
2021032202
//. base64 エンコードされているバイナリデータをデコード
if( msg.payload && msg.payload.data ){
  msg.payload = new Buffer( msg.payload.data, 'base64' );
}

return msg;


その結果を HTTP レスポンスノードに渡して処理は終了です。が、このノードでは HTTP ヘッダをカスタマイズし、"Content-Type: image/png" を付けています。つまり直前の function ノードで取り出した画像のバイナリを画像(image/png)として送信するための処理を最後に加えています:
2021032203


これらのノードや処理を組み合わせることで Node-RED の HTTP リクエストからバイナリデータを Cloudant に格納したり、格納したデータからバイナリデータを取り出して Content-Type ヘッダを付けて返す、といった一連の処理を実現していました。


この例は Cloudant にバイナリデータを格納する場合のサンプルでしたが、おそらくほぼ同様の方法で他のデータストアにも応用できると思っています。



まず、今回紹介するのは Node.js + Express で作った API を CORS 対応にする、という、これ自体はシンプルな内容なのですが、この話を考えるに至った経緯を最初にまとめておきます。


【背景】
普段からウェブアプリケーション・サービスを開発しています。詳しいアーキテクチャはともかく、ユーザーのアクセス先となるフロントエンドのアプリケーション・サーバーとバックエンド(データベースなど)があって、クラウドっぽくバックエンドには API サーバー経由でアクセスする、という形態を多く採用しています。

この形態を採用している時に限らない話ですが、「サービスの安定運用」を考えると「いかにフロントエンドを安定させるか」を考慮する必要があります。ネットワークやバックエンド含めたサービス全体のどこかに不具合が生じた場合であっても、ユーザーが最初にアクセスするフロントエンドが動いていれば「画面に障害発生メッセージを出す」ことができるようになります。逆にフロントエンドにアクセス過多を含めた不具合が発生してしまうと、「メッセージで利用者に不具合が発生していることを知らせる」ことすらもできなくなってしまいます。

このフロントエンドサーバーを安定稼働させるための技術として、最近は(docker などの)コンテナ技術であったり、(k8s や OpenShift などの)コンテナのオーケストレーション技術が流行っています。ここまでは「いわゆる一般論」的な話です。

一般論を理解した所で、技術者としての「一般論ではない話」も考えます。自分は業務でもプログラミングや作ったりサービスの運用を行ったりしていますが、業務外でも(つまり個人でも)プログラミングしたり、作ったサービスを公開して運用したりします。2つの異なる立場を持っているわけです(決して珍しくないと思ってます)。前者ではある程度の予算の中でクラウドのサービスを契約したりして、必要であればベンダーが提供するコンテナやコンテナ・オーケストレーションも使って構築することになります。 一方後者では、これらのインフラ構築部分も自腹になるわけです。まあ「これも授業代」と太っ腹に考える人は立派だと思いますが、コンテナ・オーケストレーションまで使おうとするとそれなりに懐も痛む価格だったりします。


要するに「個人開発者としての自分はケチ」なわけで(繰り返しますが、決して珍しくないと思ってますw)、「ケチはケチなりに知恵と作業でなんとかしたい」と考えるわけです。コンテナ技術やコンテナ・オーケストレーション技術を否定するつもりは全くありませんが、「より安価」に「フロントエンドを安定稼働」させる方法はないものか、と:
2021032001


#「コンテナ・オーケストレーションまで自分で構築すればいい」と考える人もいると思うので一応コメントを。技術的にはそのとおりなんですが、目的はあくまで「安価なフロントエンドの安定稼働」です。そう考えると例えば1ノードで構築した場合に目的を達成しているといえるか・・・ では複数ノードを構築する場合、今度は安価といえるのか・・・ となってしまうと考えました。


そんな自分にひらめいた1つの案が「フロントエンドを GitHub Pages にする」方法です。GitHub Pages は GitHub の無料アカウントがあれば使うことのできる「静的ウェブコンテンツの公開サービス」です。GitHub の一部として考えると、容量は(ほぼ)無制限で、アップロード直後にバックアップされ、世界中のウェブサービスの中でも指折りの安定稼働を誇っています。「フロントエンドを安定させたい」という目的だけを考えると、「かなり使える案」だと思っています:
2021032002
(↑フロントエンドを GitHub Pages にして、バックエンドを IBM Cloud にする場合)


もちろんこの方法は万能ではありません。まず GitHub Pages で公開できるコンテンツはウェブアプリケーションではなく「静的ページ」、つまり HTML ページに限られます(この時点で i18n などを考慮するアプリケーションページの公開は難しくなります)。表示データは REST API で取得すれば良いのですが、フロントエンドが静的ページである以上、通信は AJAX に限られてしまいます。その結果、API 側は(クロスオリジン通信をすることになるため)CORS を考慮した設計が必須となります:
2021032003


フロントエンドはウェブアプリケーションではないので、ウェブページをテンプレートから作る、といった便利な手法が使えず、AJAX を駆使したレンダリングを実装しないといけないことも不利な点となりますが、バックエンド側も考慮点の影響が大きな方法ではあると思っています。ただ「安価にフロントエンドを安定稼働」させる面ではイケそうな方法にも感じています。


・・・といった背景がありました。この前提だと REST API 部分も便利なサービスを有償契約して使うのではなく、安価なアプリケーション・サーバーを使って、自前で API サーバーを構築する必要があります(最悪、ここにトラブルがあってもフロントエンド側ではその旨を伝えることができる構成)。というわけで、無料のライトプランが使える IBM Cloud で「Node.js + Express で作った API を CORS 対応にする」ための方法を理解しておく必要がありました。


【Node.js + Express の REST API を CORS 対応する】
こちらはサンプルを用意しておきました:
https://github.com/dotnsf/express-cors


まず settings.js の exports.cors 配列変数内にクロスオリジン通信を許可するオリジン(ドメイン)を登録しておきます。デフォルトでは以下のようになっていて、'http://localhost:8080' と 'https://dotnsf.github.io' からの AJAX 通信を許可するよう設定しています。前者はローカルでの動作確認用ですが、実際に Github Pages で運用を始めた後は削除しておくべきです。後者は僕の Github Pages のドメインです。実際にみなさんが Github Pages でこの API を使う場合は自分自身の Github Pages ドメインに変更してください:
//. settings for CORS
exports.cors = [ 'http://localhost:8080', 'https://dotnsf.github.io' ];

本体とも言える app.js の内部は以下です:
//. app.js
var express = require( 'express' ),
    app = express();

var settings = require( './settings' );

app.use( express.static( __dirname + '/public' ) );

//. CORS
if( settings && settings.cors && settings.cors.length && settings.cors[0] ){
  var cors = require( 'cors' );
  var option = {
    origin: settings.cors,
    optionSuccessStatus: 200
  };
  app.use( cors( option ) );
}

app.get( '/ping', function( req, res ){
  res.contentType( 'application/json; charset=utf-8' );
  res.write( JSON.stringify( { status: 'OK' } ) );
  res.end();
});


var port = process.env.PORT || 8080;
app.listen( port );
console.log( "server starting on " + port + " ..." );

赤字以外の部分はごく普通の Node.js + Express を使った REST API アプリケーションです。この例では GET /ping という動作確認用のシンプルな API を1つだけ定義しています(サーバー側に障害等が起きていなければ、返り値は常に { status: 'OK' } という JSON です)。

肝心の CORS の対応をしているのが赤字部分です。settings.js の内容を確認し、exports.cors 配列に有効な値が1つでも含まれていると判断した場合は、cors ライブラリを使って設定されているオリジンを対象に CORS を有効にします。ちなみに settings.js の exports.cors が例えば null とか [](空配列)とかに設定されている場合は CORS の処理が行われないので、CORS の処理としてはデフォルトの「全てのクロスオリジンからの AJAX アクセスを許可しない(同一オリジンからの AJAX アクセスのみ許可する)」ことになります。

あとは必要に応じてベーシック認証を加えるとか、トークン認証を加えるとか(フロントエンドが静的コンテンツだとトークンの管理が面倒そうだけど)するなどして CORS 対応の REST API を構築することができます。ここは手順がわかってしまえば簡単そうですね。


【運用時】
こんな感じでデータベースへの読み書き検索などが REST API 化され、CORS 対応までできていれば Github Pages の HTML からも利用できるので、かなり安定したフロントエンドコンテンツを実現できそうです。この形で REST API が実現できていると、例えばウェブアプリを作るハンズオンでもあらかじめ用意した REST API を Github Pages から呼び出す形で実現できるので、フロントエンドの運用サーバーは無料で(しかもかなり安定したものを)用意できます。更にフロントエンド部分の開発時にはこんなフォルダ構成の Node.js + Express のアプリケーションにしておくと docs/ 以下の静的コンテンツを対象に作って、ローカルで動作確認もして、コードごと Github にあげて docs/ 以下を Github Pages で公開する、といった便利な開発・運用も可能になります:
//. app.js
var express = require( 'express' ),
    app = express();

app.use( express.static( __dirname + '/docs' ) );

var port = process.env.PORT || 8080;
app.listen( port );
console.log( "server starting on " + port + " ..." );
(↑メインファイル。静的コンテンツのフォルダを /docs に指定している以外はごく普通のシンプルな Express アプリ)


2021032004
(↑このプロジェクトを Github に上げる)


2021032005
(↑Github Pages の設定で /docs フォルダ以下を公開するよう指定する)


フロントエンドコンテンツは全て AJAX 対応させる必要があり、昨今の流行りとは違う形での実装は必要になります。それはそれで大変だし、普通のウェブアプリケーションで使える便利な技術も使えなくて不便な面も出てくるのですが、"Poorman's stable web contents" みたいに割り切って使うネタを準備しています。まだアイデア先行な面もあって実証実験が必要な段階だと思っていますが、運用していて気づくことがあったらまたこのページに追記します。


Github の認証をスマホアプリを使った2段階認証にするための手順を紹介します。対応したスマホアプリはいくつか存在しますが、このブログエントリでは IBM Security Verify アプリを使った設定手順を紹介します。

なお Github の2段階認証を有効にするには、ここで紹介する手順を実行する前に、Github の認証方法をベーシック認証ではなくパーソナルアクセストークン認証に切り替えておく必要があります。そちらの手順については以前のブログエントリを参照してください:
Github のベーシック認証が利用できなくなる前にパーソナルアクセストークンに対応する


【Github の2段階認証対応】
今回は IBM Security Verify アプリを使った Github の2段階認証対応手順を紹介します。まず iPhone や Android スマホでストアアプリを起動し、"IBM Security Verify" を検索して該当アプリをインストールします(無料です):
2021031307


そして起動します。下図は初回起動直後の画面です。この時点ではまだどのサービスとも連携できていないので、「アカウントの接続」ボタンが表示されます。このボタンをタップします:
IMG_3073


「コンピュータの QR コードをスキャンしてください」というメッセージが表示されます。この後 Github と連携するための QR コード読み取りを行います。スマホアプリ側はいったんこの状態にしたまま続けて Github 側の作業を行います:
IMG_3074


別の PC などで Github にログインし、"Settings" メニューから "Account security" を選択し、2段階認証を有効にするため、画面内の "Enable two-factor authentication" ボタンをタップします:
2021031301


次の画面で "Set up using an app" ボタンを選択します:
2021031302


すると以下のようなリカバリーコードが表示されます。リカバリーコードはアカウントにアクセスできなくなった場合に、再びアクセスできるようにするためのコードです。"Download" ボタンを押して内容を保存してから、"Next" ボタンをクリックして先に進みます:
2021031303


すると以下のように QR コードが表示されます。改めて先程起動したままだったスマホの IBM Security Verify アプリの画面から「QR コードのスキャン」を選択し、起動したカメラでこの QR コードを撮影します:
2021031304

するとアプリ側は「完了しました」と表示されます。"OK" をクリックします:
IMG_3075


Github との接続が完了した様子がわかります。この "Github" 部分をタップします:
IMG_3076


すると以下のような画面が表示されます。モザイクになっている箇所にはアクセスコードと呼ばれる6桁の数字が表示されており、約30秒ごとに異なる値に変更されます:
IMG_3077


この数字が変更になる前に Github 画面内の QR コード下部に6桁の数字を入力して、"Enable" ボタンをクリックします(作業途中で数字が切り替わってしまった場合、その6桁の数字は無効です。改めて有効な数字を入力して、切り替わる前に "Enable" ボタンを押してください):
2021031305


これで IBM Security Verify アプリを使った Github の2段階認証が有効になりました:
2021031306


うまく設定できているか、動作確認をしてみます。この状態で PC の Github からサインアウトし、再びサインインします:
2021031401


するとパスワード入力後に2段階認証のコード入力画面に切り替わります:
2021031402


スマホにインストールした IBM Security Verify アプリを起動して、先程と同様に Github アカウントを選択すると6桁の認証コードが表示されています:
2021031404


入力期限が切れる前にこの6桁の数字を正しく入力して "Verify" ボタンをクリックするとログインできます:
2021031403






 

Github の認証ルールが変わったらしく、最終的には 2021 年 8 月 13 日からベーシック認証を用いた利用ができなくなるようです:
Token authentication requirements for Git operations


普段から Github は頻繁に使っていて、しかも現在はベーシック認証中心に使っていました。突然利用できなくなると困るので、早めの対策を取っておきました。その手順を紹介します。 また合わせて Github の2段階認証を利用すべく、IBM Security Verify アプリを使った2段階認証対応手順を紹介します。

【Github のパーソナルアクセストークン認証】
Github のベーシック認証の利用ができなくなる前にパーソナルアクセストークンと呼ばれるトークンを使った認証に切り替える必要があります。まずはその手順を紹介します。

Github ページにログインし、画面右上のアイコンをクリックして "Settings" を選択します:
2021031301


プロフィールページが表示されるので、画面左のサイドメニューから "Developer settings" を選択します:
2021031302


開発者向け設定ページが表示されます。ここでも画面左のサイドメニューから "Personal access tokens" を選択します:
2021031303


設定済みのパーソナルアクセストークン一覧画面が表示されます。新しいパーソナルアクセストークンを作成するために "Generate new token" を選択します:
2021031304


新たに作成するパーソナルアクセストークンの説明(名前)を Note に入力し、スコープは全てにチェックを入れます:
2021031305


最後に "Generate token" ボタンをクリックしてパーソナルアクセストークンを作成します:
2021031306


新しいパーソナルアクセストークンが生成されました。このトークン文字列はこの作成直後のタイミングのみ表示されます(別のページに推移したら2度と表示できません)。このタイミングでコピーするなどして値を保存しておくことを推奨します:
2021031307


これでパーソナルアクセストークンを生成することができました。これ以降で git push するなど認証が必要になった場合は、ID としてユーザー名、パスワードとして(これまでのログインパスワードではなく)パーソナルアクセストークン文字列を指定して認証することができるようになります。また 2021 年 8 月 13 日以降は(ベーシック認証が利用できなくなるため)このパーソナルアクセストークン文字列を使った認証のみがサポートされるようになります:
2021031308



このページのトップヘ