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

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

2017/06

Node.js でウェブアプリを作って、
$ node app.js

みたいな感じで実行する際に、
listen EACCES 0.0.0.0:443
  :
  :

というエラーが出て実行できないことがあります。この原因と回避方法について紹介します。


この "listen EACCES 0.0.0.0:XXX"(XXX 部分は数字)というエラーは node サーバー起動時の指定ポート番号に1024以下の小さい数字が指定されている場合に発生します。一般的には管理者権限を持っていないユーザー権限で 1024 番以下のポートを listen することはできません。

上記エラーの場合は HTTPS(HTTP+SSL) を使いたくて 443 番ポートを指定して実行したケースでした。このポート番号が小さすぎることに加え、管理者権限を持たないユーザーが実行したことで発生していました。

このエラーを回避するには、管理者権限で node サーバーを起動すればよいので、
$ sudo node app.js

といった感じで、sudo を付けて実行することで回避できます。


(参考)
Node.js + Express で SSL を使う


 

クラウドファンディング Indiegogo で申し込んで以来、ずっと楽しみにしていた GPD Pocket が届きました!まだ2日程度しか使っていませんが、さっそくレビューを書いてみます。ちなみにこのレビューエントリは GPD Pocket (と IKEA の無線LAN)で書いています。

GPD Pocket はわずか 480g の超小型 PC ですが、Intel Atom z8759 CPU を駆り、8GB メモリと 128GB eMMC(Surface Pro 3 は4GB+128GB SSD で 622g)を搭載しており、エントリーモデルとはいえないスペックを誇っています。クラウドファンディングではこれで $399 でした(量産後も $599 予定らしいので超お買い得だと思ってます):
2017062501


大きさは横18cm、縦10.6cm、厚さ1.85cm。iPhone6 と比較するとこんな感じです:
IMG_0937


いくつか注文していたのですが、届いたのは最初に注文した Windows 10 Home 搭載版です。Ubuntu を選択することもできます(これも注文しているので、届いたらいずれ)。とりあえず初期セットアップして、Windows Update して、Defender を有効にして、いくつかの定番ソフト(Chrome, 7Zip, Teraterm, WinSCP, Paint.Net, サクラエディタあたり)を導入して使っています。(ディスククリーンアップとかはしていない)この時点で 99.2 GB の空きがあります:
2017062501


キーボードは結構しっかり作られています(英語キーボード)。打鍵感もあって、サイズの割に両手で打ちやすい印象。ただ、かなり特殊な配列になっているので、アルファベットや数字以外の特殊キー(カッコとかーとか)はまだかなりの頻度で打ち間違えます。今はまだウェブやSNS、ブログ編集がメインで本格的なプログラミング目的では使ってないのですが、そうなってくるともう少し慣れが必要かも。ちなみに英語<->日本語の切り替えは Alt + `(ESC の右横)で可能です:
IMG_0933


タッチパッドはトラックポイント(!)です。しかも僕がメイン機として公私にわたって愛用している ThinkPad と互換の感圧式トラックポイントで、スティックそのものにも互換性がありました(写真は僕が持っていた ThinkPad 用の赤いスティックを GPD Pocket に差して使っている様子です):
IMG_0934



外部インターフェースはこんな感じ。右からUSB 3.0、オーディオ、micro HDMI、USB C(電源)。これらに加えて無線LANとBluetoothを搭載しています。あー、micro HDMI の変換アダプタ買っとこ:



2日ほどしか使ってないのですが、「期待をはるかに超える出来」だと思います。実はこれまでセカンド機には Portabook を使っていました(ネタで買ったら意外と使いやすかったパターン)。今でもキーボードの入力のしやすさはメイン機であるThinkPad と比較しても劣らないと思っているのですが、いかんせん基本スペックの低さがネックになり、使いにくさを感じることが多くあるのも事実です。また GPD が出している別のミニ PC : GPD Win も所有しているのですが、こちらはどちらかというと「Windows の動くゲーム機」で、Windows としての使い勝手は必ずしもいいものではありませんでした。その点 GPD Pocket は編集機能を多く使う人にとっても使いやすいし、充分すぎるスペックが搭載された魅力的な PC だと思っています。


(2017/Jul/01 追記 続編を書きました)
GPD Pocket に ThinkPad のトラックポイントスティックを試す

facebook を使っていると、「今日は○○○さんの誕生日です!」というお知らせが届きますよね:
2017062101


ある程度以上の繋がりがあると、体感的にはほぼ毎日のようにこの機能を目にすることになります。もちろんちゃんと心の籠もったメッセージを作って、思い出の画像も用意して、・・・という人もいますが、中には「メッセージ考えるのも面倒くさい、そんなにお世話になってるわけでもないし、ってかこいつ誰だっけ?どんなメッセージを送るのがいいのかなあ」と頭を悩ませることもあります。そんなあなたの大切な時間を節約する Chrome プラグインを作ってみました。

このプラグインを Chrome にインストールして有効にすると、facebook の誕生日ページに移動して、そこに今日が誕生日でまだバースデーメッセージを送っていない人がいると、自動的に(あらかじめ用意した)メッセージを入力してくれます(自動投稿まではしません)。そのままで良ければ後は投稿するだけ、「この人にはちゃんとメッセージを書きたいなあ」という場合はそこから編集することもできます:
2017062100

(↑こんな感じ。自動的にバースデーメッセージが入力された状態になります)


プラグイン自体は github 上に公開しておきました:
https://github.com/dotnsf/fb_birthday_extension


上記ページから git clone するかダウンロード&展開して、ローカルシステム上に以下の3つのファイルが存在しているディレクトリを用意します:
2017062102


この中の script.js というファイルが拡張機能の本体となる部分です。中身は結構シンプルにこんな感じ:
var birthday_msg = 'ハッピーハッピーバースデー♪';

$(function(){
 $("#events_birthday_view div:eq(0) ul li ._42ef ._6a ._6b:eq(1) div div:eq(1) textarea").each(function(){
  var textarea = $(this);
  textarea.val( birthday_msg );
 });
});

1行目の birthday_msg 変数がバースデーメッセージです。必要に応じてこの内容を書き換えて使ってください。


では実際にインストールしてみます。Chrome を起動して拡張機能の画面(右上のメニューから「その他のツール」-「拡張機能」)に移動します:
2017062106


現在インストール済みの Chrome 拡張の一覧が表示されます。ここで「デベロッパーモード」が有効になっていることを確認し、「パッケージ化されていない拡張機能を読み込む」をクリックします:
2017062103


先程ダウンロードした3つの拡張用ファイルがあるディレクトリを指定します:
2017062104


すると "FB Birthdah" という名前の、この拡張機能が導入されます。「有効」にチェックを入れると、実際に facebook の誕生日ページで動くようになります。なお、導入後に script.js の中身を書き換えた場合は、この画面内の「リロード」をクリックすると、変更が反映されるようになります:
2017062105


これで上記で紹介したような心の籠もっていないバースデーメッセージを自動作成することができるようになっている、はず:
2017062101


※このプラグインはあくまで誕生日ページを参照した時に動作するもので、↓こんな感じのダイアログで出た場合は動いてくれません、あしからず:
20170621


自分が苦手なので、備忘録的に残しておきます。

まずやりたいことはこれです。RDB (今回は MySQL)のこんなテーブル(todos)があったとします。email の人の ToDo を管理するようなテーブルだと考えてください:
列名列型目的
idintプライマリキー、自動インクリメント
emailvarchar(50)メールアドレス
bodyvarchar(255)本文
createdlongレコードを作成した日時のタイムスタンプ


これだけであれば、以下の SQL でテーブルを定義するようなものだと思います:
create table todos(
  id int primary key auto_increment,
  email varchar(50),
  body varchar(255),
  created long
);

ここに運用上の条件を1つ加えます。1つの email に対しては最新の body のみを有効としたいのです。要するに同じ email に異なる body があっても(なくても)いいのですが、有効なものは最新の(created が最も大きな)body のみとして扱えるようにしたい、というものです。

例えばテーブルの中身がこのようになっているものとします(タイムスタンプの値がいいかげんですが、大小関係だけ分かればいいものとします):
idemailbodycreated
1aaa@xxx.jpToDo その110
2bbb@xxx.jpToDo その220
3aaa@xxx.jpToDo その330


全部で3件のレコードがありますが、email = aaa@xxx.jp のレコードが2件あります。この場合は created が最大である id=3 のレコードのみが有効と考えます(id=1 のレコードは削除されていても構いません)。

このような条件を付けた上で、「有効なレコードだけの一覧を取り出したい」のですが、どのようにデータベースを定義して運用すればいいでしょう?

まず email 列は実質的にユニークになるとも考えられます。が、テーブル定義時に UNIQUE 属性を付けてしまうと、INSERT 時にエラーとなってしまうので「まず同じ email のデータが存在しているかどうかを確認し、存在していなければ INSERT 、していれば UPDATE するような SQL を実行する」ことになります(こうすると実は UNIQUE 属性はあってもなくても同じと言えます)。INSERT 時の処理がちと面倒ですが、読み込み時にはシンプルに "select * from todos" だけで実現可能な方法といえます。

次に email はユニークとは考えずに、とにかくレコードは全て INSERT して、有効なレコードを取り出す時に工夫する、という方法もあります。これは SQL としては以下を実行すればよいことになります:
> select * from todos where ( email, created ) in (select email,max(created) as max_created from todos group by email);

これはこれで実現可能な方法の1つです。ただ上記の例とは逆に、INSERT は無条件に行いますが、読み込み時に比較的複雑な SQL が実行されることになります。また有効でない(使われることのない)レコードがテーブル内にずっと残ってしまうことになります。


ではこれらのいいとこ取りを考えてみます。つまり、以下のようなロジックです:
(1) INSERT は無条件に行い、
(2) INSERT 直後に有効でないレコードがあったら削除し、
(3) 検索はシンプルに "select * from todos" だけで行えるようにする


(1) の直後に (2) が出来れば、(1) も (3) もシンプルな処理になるので理想的とも言えます。で、前書きが長くなりましたが、今回はこの (2) を実現するのに苦労した話です(苦笑)。

まず、このケースでの「有効なレコード」を取り出す場合の SQL クエリーは上記のようにこのようになります:
> select * from todos where ( email, created ) in (select email,max(created) as max_created from todos group by email);

(結果)
idemailbodycreated
2bbb@xxx.jpToDo その220
3aaa@xxx.jpToDo その330

ということは、「有効でないレコード」(削除対象のレコード)はこの SQL で取り出せます:
> select * from todos where ( email, created ) not in (select email,max(created) as max_created from todos group by email);

(結果)
idemailbodycreated
1aaa@xxx.jpToDo その110

ここまでできれば後は単純に削除対象のレコードを削除すればよいので、サブクエリーを使ってこんな SQL を実行するだけ・・・
> delete from todos where id in ( select id from todos where ( email, created ) not in (select email,max(created) as max_created from todos group by email) );

と思ったのですが、これを実行すると以下のようなエラーが発生します:
ERROR 1093 (HY000): You can't specify target table 'todos' for update in FROM clause

これがサブクエリーのややこしい(というか、自分がよく分かっていなかった)所です。サブクエリーを使った時に意識する構文がいくつか存在しています:
https://dev.mysql.com/doc/refman/5.6/ja/subquery-errors.html

↑これの「サブクエリー内の誤って使用されているテーブル」に書かれた項目が今回のエラーの原因でした。サブクエリーの FROM で指定されるテーブルと、更新系のテーブルに同じものを使用することができない、というルールです。

これを解決するにはサブクエリー内の FROM 部分("todos" と書かれた部分)を更にサブクエリーにしてテンポラリテーブルにします。上記例だとこんな感じ:
> delete from todos where id in ( select id from (select * from todos) where ( email, created ) not in ( select email,max(created)
 as max_created from (select * from todos) group by email ) );

ただこれだと該当部分が2箇所あり、今度は複数のテンポラリテーブルは異なるエイリアスを持たなければならない、という制約に引っかかってエラーになってしまいます:
ERROR 1248 (42000): Every derived table must have its own alias

というわけで、最終的な答はこちら。テンポラリテーブルに異なるエイリアスを指定して完成です:
> delete from todos where id in ( select id from (select * from todos) as temp1 where ( email, created ) not in ( select email,max(created)
 as max_created from (select * from todos) as temp2 group by email ) );

こんな SQL 、何も見ずに一発でさらっと書ける人いるんかな・・・


このページのトップヘ