Corredor

ウェブ、プログラミングの勉強メモ。

クエリ文字列を連想配列に変換する簡単なやり方 (URLSearchParams)

location.search で取得した ?hoge=fuga&foo=bar みたいなクエリ文字列をパースする時に、コレまでは npm の query-string パッケージを使ったりしていた。

もしくは、次のように自分でパースしてみたこともあった。

// slice(1) で先頭の '?' を除去している
const params = window.location.search.slice(1).split('&').reduce((acc, current) => {
  const pair  = current.split('=');
  const key   = pair[0];
  const value = pair[1];  // 場合によっては decodeURIComponent() をかませる
  acc[key] = value;
  return acc;
}, {});

今でもこの手法は有効ではあるが、最近は URLSearchParams というインターフェースがあるようだ。

const urlSearchParams = new URLSearchParams(location.search);

でインスタンス化すると色々なことが出来るようだ。先頭の ? の有無とかも気にしなくて良い。

// location.search 風な文字列を自分で作って渡してやってもちゃんと処理する
const myLocationSearch = '?hoge=fuga&foo=bar';

const pairs = [...new URLSearchParams(myLocationSearch).entries()];
// → [ [ 'hoge', 'fuga' ], [ 'foo', 'bar' ] ]

こんな風に配列にセットしてやると、Key Value のペアの配列になる。この .entries() は省略しても等価なので、クエリ文字列を連想配列にするワンライナーが以下のように作れる。

const params = [...new URLSearchParams(location.search)].reduce((acc, pair) => ({...acc, [pair[0]]: pair[1]}), {});
// → { hoge: 'fuga', foo: 'bar' }

トリッキーに見えるが、URLSearchParams に渡す引数は undefinednull・空文字・オブジェクトなど、結構メチャクチャなデータを投げつけてもうまく処理してくれる。ざっと触ってみた感じ例外が発生することはなさそうだ。

ワンライナーが厳しければ、

const pairs = [...new URLSearchParams(myLocationSearch)];

pairs.forEach((pair) => {
  const key   = pair[0];
  const value = pair[1];
});

この Key・Value ペアを作るイディオムだけでも覚えておくと、find したりなんだりがやりやすくなると思う。

ルータ付属の無線 LAN に3つある SSID の違い

OCN から提供されている RS-500KI というルータ (「ホームゲートウェイ」と呼ばれる) を使っている。

このルータには無線 LAN (Wi-Fi) が内蔵されているのだが、接続先として選べるネットワーク名 (SSID) が3つ存在する。これらの違いは何だろう。

帯域の違い

まずはじめに、Wi-Fi の通信帯域が異なる。

  • rs500k-XXXXXX-1 : IEEE802.11b/g/n 2.4GHz 帯
  • rs500k-XXXXXX-2 : IEEE802.11b/g/n 2.4GHz 帯
  • rs500k-XXXXXX-3 : IEEE802.11a/n/ac 5GHz 帯

b とか g とか n とかは、最大通信速度の違い。2.4GHz 帯域よりも 5GHz 帯域の方が通信速度が速いのだが、電波干渉に弱く、繋がりにくいことがある。

暗号化方式などが違う

さて、そうすると 12 は同じ帯域で、何で2つあるんだろう?と気になる。

これらは無線ネットワークごとに異なるセキュリティ設定が出来るようにするために存在している。

  • rs500k-XXXXXX-1 : デフォルトの暗号化方式は「なし」、ネットワークが隠蔽されていないことが多い
  • rs500k-XXXXXX-2 : デフォルトで「WEP 接続」になり、ネットワークが隠蔽されていることが多い

といった感じ。スマホで Wi-Fi の接続先として選択しようとした時に、13 は表示されるが 2 は表示されない、とかいうのは、この隠蔽設定の違いによるものらしい。

SSID 1 の方を、来客に教えてあげる公開ネットワークにして、SSID 2 の方は MAC アドレスで接続元を制限してプライベートなネットワークにしたりできるワケだ。

とはいえ、普通の人はそこまで凝った使い方はしないだろうから、

  • スマホやノート PC など、部屋の中で動かして使うモノは SSID 1 (2.4GHz 帯) で接続する
  • 据え置きゲーム機や家電など、あまり動かさず電波干渉が少なそうなモノは SSID 3 (5GHz 帯) で接続する
  • SSID 2 は使わない

という運用でよさそうだ。

同時に接続できる上限数は?

ちなみに RS-500KI の場合は、有線 LAN ポートが4つあり、無線 LAN 接続は最大32台まで行けるようだ。

ウチは PC と Fire TV Stick を有線接続していて4ポートはまず潰れている。さらにスマホ、タブレット、スマートディスプレイ、ホットクック、ゲーム機の類で15台くらいは繋いでいたりする。

さすがに全てを同時に使うことはないが、意識せずとも7・8台のデバイスは同時にネットに繋いでいるような状況。よくコレをやりくりしてくれているなーと、ルータくんに改めて感謝の意。w

Fire 7 タブレットを Echo Show っぽくするためにフルスクリーン表示する時計アプリを作った

2019年モデルの Fire 7 は、Alexa が使えるが、Echo Show モードが使えない。開発者オプションにて電源接続時にスリープにしない設定は出来るので、フルスクリーンで常時表示させられる何らかの画面が作れたら面白いかな?と思った。

Android OS および Fire OS のブラウザでは、HTML5 の Fullscreen API が利用できる。すなわち、ウェブページをフルスクリーン表示できるワケだ。

それならばと、フルスクリーンで時計を表示する Web ページを作ってみた。

成果物とデモ

作ったモノは以下。

codepen.io

CodePen 上で実装しているのだが、ページ左上付近をタップするとフルスクリーン表示になる。見えないボタンを配置している。同様に、

  • ページ右上をタップすると当月のカレンダーを表示
  • ページ左下をタップすると全要素を非表示にする (再度タップすれば時計 or カレンダーを表示する)
  • ページ右下をタップすると、ライトモードとダークモードを切り替える

といった機能を実装した。

f:id:neos21:20200817171106j:plain

実際に Fire 7 タブレットで表示した様子はこんな感じ (中央)。隣の Google Nest Hub と遜色ない……??w

ソースコードを見てもらえば分かると思うが、容量削減のためにフルスクラッチで実装している。

Fullscreen API

Fullscreen API は、主要な PC ブラウザや Android の主要ブラウザで利用できる。iOS では使えないので、代わりに Standalone モードを利用することになるだろうか。

今回は Fire OS に搭載されている Silk ブラウザ、および Chrome ブラウザで検証したが、正常にフルスクリーン表示できた。

以下に Fullscreen API のデモを用意した。

See the Pen Fullscreen API by Neo (@Neos21) on CodePen.

document.fullscreenEnabled プロパティを見れば、Fullscreen API が使えるかどうかが事前に分かる。

また、document.fullscreenElementnull かどうかで、フルスクリーン表示中か否かが確認できる。

フルスクリーンに移行するには Element.requestFullscreen() を使う。ページ全体をフルスクリーン表示するなら document.documentElement.requestFullscreen() とする。

逆にフルスクリーン表示を終了するには、document.exitFullscreen() を使う。コチラは document だけなので間違えないように。

それぞれ Promise になっているので成否が分かる他、fullscreenchangefullscreenerror イベントが存在するので、これをリスンしておくことで成否を確認できる。

時計の実装

時計の実装は愚直に行った。setInterval で1秒おきに時刻を表示しているだけ。等幅フォントを使っていないので、1文字単位で span で囲み、幅を揃えるようにした。

ページ上部に高さ 50px ほどの帯を置いている。コレは Fire 7 タブレットで表示した時に、画面下部にナビゲーションバーが表示されるのだが、それにより上下の中央位置がズレるために設けたモノ。テキトーに。w

ページ四隅には透明な button 要素を置いている。

カレンダーの実装は、以前実装したコードの流用。

ホントに Fire 7 タブレットでの見え方しか意識していないので、雑な作りだが…。w

とりあえず Fullscreen API で遊べたのでおけおけ。w

PL/pgSQL : PostgreSQL でプロシージャ・トリガーを実装する

Oracle DB を触っていた時に PL/SQL というプログラミング言語を触っていた。同様の手続き型言語が PostgreSQL にも用意されていて、PL/pgSQL という。

今回は対象テーブルにインサートが発生した時にその内容を標準出力するというプロシージャを作成し、PostgreSQL に適用してみる。Docker コンテナとかで動いている PostgreSQL を想定して、標準出力をロギングに利用しているので、そこに必要なログを追加で流すためにプロシージャを使うというようなシナリオだ。

プロシージャを作成する

まずはプロシージャを作成する。プロシージャは「関数」とも言い換えられ、このあと定義する「トリガー」によって実行される処理を実装する。

CREATE OR REPLACE FUNCTION logging_my_table()
RETURNS TRIGGER AS $$
DECLARE
  -- 単一行コメントはハイフン2つ。C や Java と同じブロックコメントも書ける
  -- DECLARE は変数宣言のブロック。以下は「my_table」というテーブルの型を指定した「new_record」変数を定義している
  new_record my_table%ROWTYPE;
BEGIN
  -- インサートされた行は、組み込み変数「NEW」に設定されている
  new_record := NEW;
  -- 標準出力にログを出力する。「%」を使用して my_table の id カラムの値を表示させている
  RAISE LOG 'Inserted : %', new_record.id;
  -- インサート時のプロシージャでは戻り値を使わないので以下のように NULL を返す。RETURN を書かないと警告が出る
  RETURN NULL;
END;
$$ LANGUAGE plpgsql;

こんな感じ。ほとんど PL/SQL と同様の構文。公式のリファレンスを参考に見様見真似で実装してみた。

RAISE がログ出力するための処理。RAISE LOGLOG レベルのログを出力できる。ログレベルは以下のモノがあり、それぞれで標準出力されるかどうかなどの挙動が違った。

# 以下の3つのレベルは標準出力にログが出てこなかった
RAISE DEBUG 'Debug Level Log!';
RAISE INFO 'Info Level Log!';
RAISE NOTICE 'Notice Level Log!';

# 以下の2つのレベルは標準出力にログが出た
RAISE LOG 'Log Level Log!';
RAISE WARNING 'Warning Level Log!';

# 以下の2つはログ出力した行で処理が中止され、後続行が動作しない
# RAISE のログレベル未指定は EXCEPTION と同義になる
RAISE EXCEPTION 'Exception Level Log!';
RAISE 'Raise Log!';

Info ログなつもりで考えているので、今回は RAISE LOG を使っている。

トリガーを作成する

次に、「テーブルにインサートが発生した時に上述のプロシージャを実行する」という、トリガー定義を実装する。

PostgreSQL の場合、CREATE OR REPLACE TRIGGER という構文はないので、先に DROP TRIGGER IF EXISTS を使って、CREATE OR REPLACE 相当の処理を実現する。

DROP TRIGGER IF EXISTS trigger_my_table ON my_table;

CREATE TRIGGER trigger_my_table
  AFTER INSERT ON my_table
  FOR EACH ROW
  EXECUTE PROCEDURE logging_my_table();

ON 【テーブル名】 で対象のテーブルを指定し、AFTER INSERT で実行タイミングを指定している。

どんなログが出力されるのか

プロシージャとトリガーを登録したら、実際にどんなログが出力されるのか見てみる。

# 以下の要領で標準出力を確認する想定
$ docker logs -f my-postgres

別のターミナルで my_table にインサートをかけてみる。

SQL> INSERT INTO my_tables (id, name) VALUES (100, 'Example');

標準出力には次のようなログが出力された。

2020-08-01 00:10:25.500 UTC [7690] LOG:  Inserted : 100
2020-08-01 00:10:25.500 UTC [7690] CONTEXT:  PL/pgSQL function logging_my_table() line 4 at RAISE
2020-08-01 00:10:25.500 UTC [7690] STATEMENT:  INSERT INTO my_table VALUES (100, 'Example');

CONTEXTSTATEMENT の行は勝手に出る。自分が意図して出したのは LOG: の行のみ。整形は後でやるからとりあえずコレでいいや〜。

わたしとぼくのPL/pgSQL (技術の泉シリーズ(NextPublishing))

わたしとぼくのPL/pgSQL (技術の泉シリーズ(NextPublishing))

  • 作者:目黒 聖
  • 発売日: 2019/04/12
  • メディア: オンデマンド (ペーパーバック)