Air Mac Extremeを買った

月花です。
Air Mac Extremeを買いました。

www.apple.com


かねてより、ほしいとは思っていたのだ

  • ルータの調子が悪かった
  • macのバックアップしないとそろそろヤバイ
  • ファイルぽんぽんおける場所が欲しかった
  • PS4とつながればハッピー
    • ( 結局これはだめだった)

というわけで、購入時期が早くなったのだ。

ひととおりイカとFF14をしてみたが、ものすごく快適だ。
当然ルータを変えただけで上流はそのままなので、上り下りが速くなるわけではない。
深夜3時に130Mbpsしか出ないようなベストエフォート回線がルータのカタログスペックに追いつくことはないからそこはどうでもよい。

速度ではなく安定性が段違いによいらしく、イカをやれば22時前というピークタイムにも関わらずひっかかりなくキレッキレの動作をし、FF14をやれば逃げ遅れなどの失敗は自覚できたもののみだった。

これはよい買い物をしたぞと思ったしもっと早く買うべきだった。
まあでも最後まで読めばわかるけど、本当にMacユーザにしか値段相応の恩恵がなさそうなので、全体的には割とピーキーな感じ。

ルータの調子が悪かった

これはもう散々で、イカをすれば自分も味方も不審死し、FF14をやればギミックは失敗してタンクは気づけばHPがゼロ。
FF14しかやってなかったら単にヘタクソすぎワロタってだけなのだが、イカも不審とくれば、この2つはゲームシステムが違うのでまず間違いない。
イカもヘタすぎワロタって感じだけど、そこは無視しよう。

思えば長い付き合いだった。
就職して大阪に越してきたのが3年前の4月で、そのときに、ついでにルータを買い替えた。
とはいっても、安いルータから安いルータに変えただけで、へたをすれば前より安いルータになっていたかもしれなかった。
そこへいきなり2万のルータが黒船来航、すでにゴミ袋の中である。

macのバックアップしないとそろそろヤバイ

このMac Book Airとも長い。
購入時期がEarly2014に間に合わず、Mid2013の製品なので、そろそろ4年が経とうとしている。
2回ほどクリーンインストールはしているが、ハード的にそろそろだろう。

いよいよTime Machineか、というところだが、私にTime Machineを使いこなせると思わない。
ノートPCであるMac Book Airに外付けHDDを定期的に差し込み、バックアップをさせるという動作、絶対にしないからだ。
このAir Mac Extremeに接続した外付けHDDをTime Machine用のディスクとして設定できて、LANでつながっている限り、常にTime Machineがバックアップを取ってくれる。
これくらいしてくれないと私はバックアップをしない人間なんだ。もっと甘やかしてほしい。

PS4とつながればハッピーだった

Air Mac Extremeの裏にはUSBポートがあり、外付けHDDをNASのように扱える。
てきとうに軽く調べたら、どうもPS4が認識しそうなことが書いてあったんだけど、そうはうまくいかなかった。
そもそも、NASに対応しているのは、私が共有したかったキャプチャーギャラリーではなく、メディアプレイヤーだったのだ。

当初の筋書きとしては、
PS4スクリーンショットUSBメモリを使ってPCへ運ぶのが不便だから解決のためにNASであったのだが、やはりそれはだめだった。

まあどうせ置き場所近いし、そこまで頻繁にスクショ移さないし、外付けHDDの繋ぎ先を都度変えればいいだろうってことでそうした。
PS4のUSB端子は、キーボードとコントローラでふさがっているので、USBメモリを挿しっぱなしなどできず、それよりは楽だ。

Air Mac ExtremeexFAT読めない問題。

PS4が認識するのはFAT32exFATなので、じゃあとexFATパーティションを切ってスクショをPS4からコピーしたら、Air Mac Extremeが「ディスクの修復が必要です」と言い出した。
よく調べてみると、何の事はない、Air Mac ExtremeexFATに対応していないだけで、別に修復は必要なかった。こいつは手のかかる二万円だ。

しかたがないので、FAT32は表向きは32GBまでが対応ということなので、ためしに128GBの領域をFAT32で切り出してみたが、そうするとPS4が対応していない。
まあそうだよね。

しょうがないから、32GBの領域をFAT32で切り出して、どちらからも読み書きできることを確認した。
結果的にはUSBメモリよりも容量が小さくなった。ちょっと涙をこらえた。

CSS : 要素数・ブラウザ幅が可変長なサイトでflexをキレイに使う

月花です。
今回はCSSの備忘録です。

内容は、幅が可変長のサイトにおける、flexboxで要素は左寄せ、全体は中央寄せとする方法です。

テキストだと説明しづらいので、画像をつかっていきます。

やりたいこと

このようなレイアウトを組みたいとする。
f:id:gekka9:20170728122308p:plain
コードでは下記のようになる。

See the Pen zdvwqQ by gekka (@gekka9) on CodePen.

しかし、同じCSSで横幅が少しでも異なると

See the Pen zdvwBQ by gekka (@gekka9) on CodePen.


このように、一見入りそうだけど、実はマージン込みでは入らず、下に落ちてしまう。
この右側のスキマが気になるので、直していく。

よくある失敗例

flexには、幅全体を使って等間隔に割り付ける、 justify-content:space-around という指定がある。
しかし、これを単純につかってしまうと・・・。

See the Pen YxyZMX by gekka (@gekka9) on CodePen.

このように、最下段がおかしなことになる。

解決方針

一段あたりの要素の個数が決まっているなら、よくあるハウツーでは剰余の数だけ空の要素を入れればいい。
しかし今回は、ブラウザの幅によって個数を変えて、常に入りうる最大の数を一段に押し込みたい。

つまり、下記のようになるのが理想である。
f:id:gekka9:20170728122322p:plain

このためには、下記の X から Y を計算し、 Y/2 を margin-left に設定することでできるはずだ。
f:id:gekka9:20170728122327p:plain
これも、数か幅のどちらかが固定ならば、シンプルに定まるものだが、その場合は計算せずとも space-around を使えばよいだけだ。

今回は可変長のため、一行あたりに何個入っているかをHTMLとCSSは知らないので、全体の幅から要素の横幅に個数をかけたものを引くなどというような、個数を用いる計算はできない。

そこで、個数に依存しない計算方法として剰余を用い、
・ Y = 100%(px) mod X(px)
と求めることができる。

もし、CSS の calc に剰余が実装されているならば、これでいけるはずだった。
しかし現実は非情である。実装されてないばかりか、そもそもcalcはモダンブラウザしか使えないのでIEが死ぬ。

ここはおとなしくJavaScriptを持ち出すことにする。

JavaScript を使う戦略

上記の、 Y = 100%(px) mod X(px) という計算式において、 100% と X をピクセルで用いるには、単純にJSで幅を取ってやればいいだけなので、ページロード時とリサイズ時に計算をして、CSSを書き換えてやる。
ただし、 X にマージンを含めなければならないため、 .outerWidth(true) を用いることが必要なことに留意する。

See the Pen oejwep by gekka (@gekka9) on CodePen.

これで、どのようなブラウザの幅でも、どのような要素でも柔軟に対応できる要素グリッドが出来上がった。

Postgres・MDB2のキャスト演算子とプレースホルダ

月花です。

PDOによるデータベース操作の話をします。

今回はこんな話です

ことの発端

MDB2を使ったフレームワークで、Postgresから疑問符プレースホルダを使ったINSERTを行った際に、不思議なエラーが発生した。

INSERT INTO test (id,name,value) VALUES (1,NULL::TEXT,?);

こういうクエリをprepareして、

array('test')

を食わせてexecuteするような、一見問題のないINSERT文を実行した。

すると、

〜〜〜
MDB2 Error: unknown error
〜〜〜
[Native message: ERROR:  入力の最後で 構文エラー
LINE 1: INSERT INTO test (id,name,value) VALUES (1,NULL$1

とのこと。

不思議なエラー文だ。

糸口を探る

[Native message: ERROR:  入力の最後で 構文エラー
LINE 1: INSERT INTO test (id,name,value) VALUES (1,NULL$1

ということで、$1がどうにも気になる。
これはたぶん、プレースホルダだ。

原文では、NULLがベタ書きされている箇所は一個しか無いので、そうすると

NULL::TEXT

が気になってきた。

もしかして

INSERT INTO test (id,name,value) VALUES (1,CAST( NULL AS TEXT ),?);

コロンでのキャスト演算子ではなく、こうなら・・・・

いけた。

ということは、

  • コロン2つによるキャストが影響している
  • どうも不当にプレースホルダとして解釈されている
  • コロンを使わなければ治る

となれば原因は

結局は、

INSERT INTO test (id,name,value) VALUES (1,NULL::TEXT,?);

この、 NULL::TEXT の、 :TEXT が名前付きプレースホルダとして解釈され、 NULL$1 となり、さらに元々の疑問符プレースホルダとの競合もあり、構文エラーとなったようである。

ところが、これそんなはずはなく、正しい文法のはずだ。
ためしにいくつかのSQLクライアントから、疑問符プレースホルダを適当な値に変換したものを実行したが、どれも成功した。

ということは、フレームワーク・・・と思ったがこいつはMDB2との橋渡しをしているだけだ。

というわけでMDB2を探っていく。

環境の再現

適当な VirtualBoxMDB2をインストールして、下記のような環境にした。

# pear list
Installed packages, channel pear.php.net:
=========================================
Package           Version State
Archive_Tar       1.4.2   stable
Console_Getopt    1.3.1   stable
MDB2              2.4.1   stable
MDB2_Driver_pgsql 1.4.1   stable
PEAR              1.9.5   stable
Structures_Graph  1.0.4   stable
XML_RPC           1.5.4   stable
XML_Util          1.2.3   stable

これで、さくっと書いて実行する。

require_once 'MDB2.php';

$dsn = 'pgsql://***:***@***/***';
$options = array(
    'debug' => 2,
    'result_buffering' => false,
);

$mdb2 = MDB2::connect($dsn);
if (PEAR::isError($mdb2)) {
    die($mdb2->getMessage());
}else{
    $sql="INSERT INTO test (id,name,value) VALUES (1,NULL::TEXT,?);";
    $statement=$mdb2->prepare($sql);
    if (PEAR::isError($statement)){
        echo $statement->getDebugInfo();
    }else{
        $statement->execute(array('test'));
    }
}

$mdb2->disconnect();

とすると、やはり同じエラーが出た。
ということで、MDB2が悪いことが確定して一安心。

解決

そもそも、キャスト演算子がコロンでできている以上、検索のしにくさったら。
過去の例が全然見当たらない。

こういうときはとりあえずバージョンアップをしてみよう。

# pear install MDB2-2.5.0b5
# pear install MDB2_Driver_pgsql-1.5.0b4
#
# pear list
Installed packages, channel pear.php.net:
=========================================
Package           Version State
Archive_Tar       1.4.2   stable
Console_Getopt    1.3.1   stable
MDB2              2.5.0b5 beta
MDB2_Driver_pgsql 1.5.0b4 beta
PEAR              1.9.5   stable
Structures_Graph  1.0.4   stable
XML_RPC           1.5.4   stable
XML_Util          1.2.3   stable

とすると、あっさり成功したのでした。

なので、MDB2ChangeLogを読み漁る。
すると、MDB2_Driver_pgsql 1.5.0a1 において、

Changelog:

〜〜〜

- fixed bug #11652: failed prepared queries containing the "::type" style of casting

〜〜〜

なんと、2007-07-20 12:38 UTC に報告された不具合である。
10年前て・・・

ELB配下のEC2のうち、1つだけをIPアドレス制限する

月花です。

またAWSでハマってあれやこれややったので備忘録です。

やりたかったけどできなかったこと

ELB配下に複数のEC2インスタンスがあり、うち1つだけをIPアドレス制限する。
セキュリティグループでできると勘違いしていたのだが、実際はELBからEC2の通信を許可している以上、ELBにアクセスできればEC2にアクセスできるため、セキュリティグループの変更では不可能だった。

また、該当のELBにEIPを紐付け、Route53から飛ばすことで、違うURLとはなるが、1基だけIPアドレス制限をしようとしたが、AWSにおいて証明書はELBかCloudFrontにしか結びつかないため、HTTPS接続ができなかった。
図にするとこうなっているためだ。

クライアント <--HTTPS--> ELB(証明書あり) <--HTTP--> EC2(証明書なし)

このためAWSの機構では無理なので、Apacheの設定でどうにか設定した。

Apacheの設定を変更する

実際には.htaccessでやったが、httpd.confでやっても同じことと思われる。

X-Forwarded-For

筆者は全然知らなかったが、こういう名前のHTTPヘッダフィールドが存在する。
内容としては、クライアントのIPアドレスが入っている。
まさに、冒頭のような構成で、実際には負荷分散システムのIPアドレスでアクセスされてしまう際に使われるものだ。
一般的な負荷分散システムであれば、この値を引き渡してくれるらしい。

今回は、この値をホワイトリストとすることで解決した。

CIDR記法と正規表現

さきほどのX-Forwarded-Forの値をホワイトリストとして指定するにあたって、

xxx.xxx.xxx.xxx/26

といった、いわゆるCIDR記法は受け付けてくれない。
なので、このCIDR記法の範囲にあるIPアドレス全てを指定しなければならない。
ところがそれでは億劫なので、正規表現に変換してやる。

筆者は下記のサイトを使って変換した。
d.xenowire.net

このサイトで、許可したい正規表現を作って、それを指定する。

実際に記述する

先程の正規表現を使って、まずはホワイトリストの1件1件を定義する

SetEnvIf X-Forwarded-For "123\.45\.78\.(9[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])" allowed_ip1
SetEnvIf X-Forwarded-For "12\.3\.45\.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])" allowed_ip2

2件あるならばこんな感じ。
「allowed_ip〜」の部分はエイリアスなので、好きな名前をつけよう。

そして、まず全てのアクセスを拒否、次に先程定義したものを許可する。

Order Deny,Allow
Deny from All
Allow from env=allowed_ip1
Allow from env=allowed_ip2

一連の流れとしては、

SetEnvIf X-Forwarded-For "123\.45\.78\.(9[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])" allowed_ip1
SetEnvIf X-Forwarded-For "12\.3\.45\.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])" allowed_ip2
Order Deny,Allow
Deny from All
Allow from env=allowed_ip1
Allow from env=allowed_ip2

こうなる。

今回は.htaccessに書いたので、ディレクトリの指定はしていないが、httpd.confに書くときは、ディレクトリの指定が必要になる場合もある。

おわり。

jQuery : セレクタフィルタの様々な罠

月花です。

チェックボックスjQueryでわちゃわちゃしていたときにハマったのでメモします。
jQuery、卒業したいですね。

前提

こういうフォームがあります。

<!doctype html>
    <body>
        <form name="hoge">
            <label><input type="checkbox" name="fruits[]" value="4種盛り合わせ">4種盛り合わせ</label>
            <label><input type="checkbox" name="fruits[]" value="3種盛り合わせ">3種盛り合わせ</label>
            <label><input type="checkbox" name="fruits[]" value="レモン">レモン</label>
            <label><input type="checkbox" name="fruits[]" value="オレンジ">オレンジ</label>
            <label><input type="checkbox" name="fruits[]" value="ぶどう">ぶどう</label>
            <label><input type="checkbox" name="fruits[]" value="なし">なし</label>
        </form>
    </body>
</html>

果物を選択するチェックボックスが6つ並んでいます。

問題はこの「盛り合わせ」という2つの選択肢で、これに関しての仕様として、
1. チェックされたら、盛り合わせ以外の他の選択肢のチェックを外す
2. チェックされている間は、盛り合わせ以外の他の選択肢をチェックできない
3. ただし盛り合わせ同士は相互にチェックし合えるが、片方を押したら片方が非選択に戻る
ということになっています。

しかもなんとこのチェックボックス、上位フレームワークの影響でid属性を付けることはできません。
さらに、value値はなんと日本語です。勘弁してほしい。

セレクタだけで直接1要素を拾うことができず、value値が日本語で比較もあんまりしたくないので、フィルタを駆使して、上記を実装します。

:first は使えない

仕様の、3 が邪魔をして、:firstだけでなく、2番目を取る必要があります。
2番目をビシっと抜き出す、:second があれば良かったんですが、無いので、筆者は:nth-child()を使うことにしました。

わかってる人はアレっと思うでしょうが、後述ですし、そういう人にとって為になる情報はこの記事にはありません。

:nth-child() も使えない

とりあえずjQueryで1番目のチェックボックスが拾えれば、2番目も同様に拾えるし、なんとかなるやろってことで、やってみます。

$('input[name^=fruits]:nth-child(1)').change( function(){
    alert($(this).val());
});

しかしこれはうまく行きません。
1番目だけでなく、どこを押しても押した要素が拾われてしまいます。

これは、inputタグは全てそれぞれのlabelタグの中に入っているためです。
:nth-child() は親要素の長子を1とし、次子を2というように指定するのですが、それぞれのinputはそれぞれのlabelの長子なので、全てのinputが適合してしまい、全てのinputにchangeイベントがバインドされる結果となりました。

:nth-of-type()も、名前にchildが入っていないのですが、これは子要素のうち、このフィルタがくっついているセレクタで絞り込むため、長子しかいないこの場合は結局同じです。

:eq()を使う

筆者は、:eq()を知りませんでした。
正確には、知ってはいたんですけど、「eqがイコールの省略なのはわかるけど、何と何を比べてんねん」と思って詳しく知りませんでした。

:eq() は、そのセレクタの、親子関係を気にせず、ページの上から順番に振った通し番号を比較します。
要するに、DOM配列のインデックスです。なので、 :first と :eq(0) は等価になります。

ここで筆者は、しばらく nth-child() について考えていたため、インデックスの振り方でハマりました。
:nth-child() は、何番目の子供か、という通し番号です。0番目の子供というのは存在しないため、1から始まります。
:eq() はインデックスです。添字なので0から始まります。 めんどくさいですね。

これを使って、

$(function(){
    $('input[name^=fruits]:eq(0)').change( function(){
        if ($(this).is(':checked')) {
            //チェックされたとき
        } else {
            //チェックが外されたとき
        }
    });
    $('input[name^=fruits]:eq(1)').change( function(){
        if ($(this).is(':checked')) {
            //チェックされたとき
        } else {
            //チェックが外されたとき
        }
    });
});

これで、1番目の要素と2番目の要素だけにchangeイベントがバインドできました。

以外 をセレクタで表現する

1番目と2番目の要素だけにchangeイベントをバインドすることに成功しました。
その中で、問題の選択肢以外、というセレクタが必要になってきます。

これは、 :not() で解決します。

$('input[name^=fruits]:eq(0)').change( function(){
    if ($(this).is(':checked')) {
        //チェックされたとき
        $('input[name^=fruits]:not(:eq(0))').prop('checked', false);
        $('input[name^=fruits]:not(:eq(0))').attr('disabled', 'disabled');
    }
});

このように、さきほどの :eq() を :not() の引数の中に押し込めてやります。
これで、フィルタを反転させて、押した要素以外の全てのチェックを外し、選択不可にしました。
しかし、問題の選択肢2つの間では自由に選択することができなければいけません。

そこで、 :not() を複数のフィルタで指定します。

$('input[name^=fruits]:eq(0)').change( function(){
    if ($(this).is(':checked')) {
        //チェックされたとき
        $('input[name^=fruits]:not(:eq(0))').prop('checked', false);
        $('input[name^=fruits]:not(:eq(0),:eq(1))').attr('disabled', 'disabled');
    }
});

:not() の引数は、カンマ区切りでOR条件で複数のフィルタを指定できます。
この結果、1番目と2番目以外を選択不可にするということが実現できました。
ちなみに、半角スペースで区切ると、AND条件になります。

今回は、1番目でもなく かつ 2番目でもない、となればよいのでOR条件を使って ¬(1∨2) とし、実質 (¬1)∧(¬2) なので実現できています。
ド・モルガンの法則です。頻繁に使うんですが、普段は名前を意識しないので、名前を言われると懐かしさを感じますよね。

チェックが外されたときの挙動を追加して

$('input[name^=fruits]:eq(0)').change( function(){
    if ($(this).is(':checked')) {
        //チェックされたとき
        $('input[name^=fruits]:not(:eq(0))').prop('checked', false);
        $('input[name^=fruits]:not(:eq(0),:eq(1))').attr('disabled', 'disabled');
    }else{
        //チェックが外されたとき
        $('input[name^=fruits]').removeAttr('disabled');
    }
});

2番目の要素にも同様にバインド

$('input[name^=fruits]:eq(0)').change( function(){
    if ($(this).is(':checked')) {
        //チェックされたとき
        $('input[name^=fruits]:not(:eq(0))').prop('checked', false);
        $('input[name^=fruits]:not(:eq(0),:eq(1))').attr('disabled', 'disabled');
    }else{
        //チェックが外されたとき
        $('input[name^=fruits]').removeAttr('disabled');
    }
});
$('input[name^=fruits]:eq(1)').change( function(){
    if ($(this).is(':checked')) {
        //チェックされたとき
        $('input[name^=fruits]:not(:eq(1))').prop('checked', false);
        $('input[name^=fruits]:not(:eq(0),:eq(1))').attr('disabled', 'disabled');
    }else{
        //チェックが外されたとき
        $('input[name^=fruits]').removeAttr('disabled');
    }
});

共通化できそうなんですけど、共通化すると同じ要素に2度バインドしたりしてしまうのでうまく行きませんでした。
もう少しスマートにできる気はするんですけど。

最終的にはこう

<!doctype html>
    <head>
        <script
            src="https://code.jquery.com/jquery-3.1.1.slim.min.js"
            integrity="sha256-/SIrNqv8h6QGKDuNoLGA4iret+kyesCkHGzVUUV0shc="
            crossorigin="anonymous"></script>
        <script type="text/javascript">
            $(function(){
                $('input[name^=fruits]:eq(0)').change( function(){
                    if ($(this).is(':checked')) {
                        $('input[name^=fruits]:not(:eq(0))').prop('checked', false);
                        $('input[name^=fruits]:not(:eq(0),:eq(1))').attr('disabled', 'disabled');
                    } else {
                        $('input[name^=fruits]').removeAttr('disabled');
                    }
                });
                $('input[name^=fruits]:eq(1)').change( function(){
                    if ($(this).is(':checked')) {
                        $('input[name^=fruits]:not(:eq(1))').prop('checked', false);
                        $('input[name^=fruits]:not(:eq(0),:eq(1))').attr('disabled', 'disabled');
                    } else {
                        $('input[name^=fruits]').removeAttr('disabled');
                    }
                });
            });
        </script>
    </head>
    <body>
        <form name="hoge">
            <label><input type="checkbox" name="fruits[]" value="4種盛り合わせ">4種盛り合わせ</label>
            <label><input type="checkbox" name="fruits[]" value="3種盛り合わせ">3種盛り合わせ</label>
            <label><input type="checkbox" name="fruits[]" value="レモン">レモン</label>
            <label><input type="checkbox" name="fruits[]" value="オレンジ">オレンジ</label>
            <label><input type="checkbox" name="fruits[]" value="ぶどう">ぶどう</label>
            <label><input type="checkbox" name="fruits[]" value="なし">なし</label>
        </form>
    </body>
</html>

終わりです。

ついでに色々調べてたんですけど、セレクタって結構やるやつですね。
jQueryの是非はともかく、ちゃんと論理演算を考えながら実装すれば、行数とバインドの節約が捗りそうです。
とはいっても、仕様変更・追加で継ぎ足し継ぎ足しのjQuery、まとめて改善しようにもすでに稼働しているので余計なテストが・・・。厄介すぎる・・・。

参考文献

www.jquerystudy.info

bootstrap : twinviteでbootstrapを活用してエモい招待サイトを作る

月花です。

先日、関西で行われるクラブイベントのWebページと、twinviteを公開しました。
そのとき使った手法が極めて有効であったため、下記に残します。

この記事は、イベントのWebサイトを作ったりtwinviteで凝ったことがしたい方の手引書を目指して書きました。
・気合をいれたイベントであることを強調したい。
・次は周年記念回なので、いつもよりきれいに作りたい。
・他のイベントとの差別化を図りたい。
様々な動機はあると思いますが、Webページをこだわることは、どれにも有効な手段です。

簡素な記事のため、説明不足の箇所が多いですが、分からないワードは積極的にググってみましょう。よく使われるライブラリについて書いているので、日本語で記事を起こしている先駆者はたくさんいます。

これらは、ある程度のHTML+CSSの知識は必要ですが、仕様などの深い理解は必要ありません。

今回作ったもの

・Webサイト
IzanagiDistribution3.0
いわゆるランディングページです。誘導対象はtwinviteですが、商業的な色を出さないために動線は最小限にしてあります。

・twinvite
IzanagiDistribution Ver.3.0 - twinvite
イベント招待サイトです。
自由にHTMLを書いてデザインできる部分がある、twiplaとかtweetviteみたいなやつです。
今回はここを攻略していきます。

bootstrapについて

Webサイト、twinvite共、bootstrapというライブラリを使っています。
getbootstrap.com
このライブラリは、Webサイトのあらゆるパーツのデザインを定義したCSSの詰め合わせと、便利なJavaScript関数の集合体です。
つまり、このライブラリで定義しているクラス名やID名を指定するだけで、グリッドシステムやモーダルの起動が可能です。
twinviteなどのHTMLを自由記述できる部分を含むページでは、スタイルは基本的にHTMLタグのstyle属性を使って、

<div style="〜〜〜〜"></div>

とすべてのタグに記述する必要がありますが、bootstrapに定義されているデザインを使う分にはクラス名の指定があればよいので、style属性が必要なくなります。

ここで、なぜtwinviteでbootstrapが使えるか、についてですが、twinviteは元々bootstrapを使って作られていて、私達が記述するより前の時点でリンク済みであるためです。
また、font-awesomeもリンク済みであるため、こちらもクラス名の指定だけで、様々なアイコンフォントを活用することができます。
fontawesome.io

つまり、Webサイトを別途作成する場合、そちらもbootstrapを使って実装してやれば、理想的にはHTML部分のコピペだけで、ほぼ同じページが作れる、ということです。
今回私が作成した2つは、そのようになっているため、Webサイト完成後の極めて迅速なtwinvite対応が可能でした。

ちなみに、こちらは以前私が作成した、LoveDive! というイベントのtweetviteページのソースコードです。
エディタの横幅に収まらないほど、style属性にガリガリ記述していることがわかると思います。
f:id:gekka9:20170108180509p:plain
このコードは、下記のサイトの開催日時や金額が書かれたテーブル部分だけです。
http://gekka.sexy/love_dive.html

bootstrapが使えれば、もはやこのような苦労は必要ありません。

bootstrapの主な要素

レスポンシブ対応

近年、デバイスの増加により、様々な解像度の環境でWebサイトが見られることになります。
スマホ・PC・タブレットなど、その全てに対応するためにいくつもファイルを作り、デバイスを判断して読み込むHTMLやCSSを変えるといった解決策は非常に非効率です。
そこで、画面の幅だけを頼りに、CSSを出し分け、たとえばPCでは3カラムでスマホでは1カラムといったことが、1つのHTML、1つのCSSで実現可能になりました。そういった実装をレスポンシブデザインと呼びます。
冒頭で提示したサイトは両方ともレスポンシブです。PCでご覧になっているのであれば、ブラウザの幅をぐりぐり変えてみてください。手っ取り早くわかると思います。

bootstrapはレスポンシブに対応しており、デザインが切り替わる境界は全部で3つあります。
すなわち、4つの解像度レンジでデザインを出し分けることが可能です。当然、クラス名の指定だけによって。

グリッドシステム

グリッドは網目と言った意味がありますが、例えば正方形の画像を8つ並べるのであれば、PCでは横4つ縦2つ、スマホでは横2つ縦4つ、といった出し分けが適切でしょう。
このようなブラウザ解像度に合わせたグリッドレイアウトのカスタマイズが、bootstrapでは非常に強力にサポートされています。
CSS · Bootstrap

基本的には横幅を12分割し、そのうちどれくらいをHTML要素に割り当てるかによって指定します。
さらに、さきほどの、PCでは横4つ縦2つ、スマホでは横2つ縦4つといった実装は、グリッドの要素1つ1つに対して、下記のようなクラス名を当ててやります。

<div class="row"> <!--行の開始-->
  <div class="col-xs-6 col-sm-3 col-md-3"></div> <!--このdivが1つの要素-->
  <div class="col-xs-6 col-sm-3 col-md-3"></div>
  <div class="col-xs-6 col-sm-3 col-md-3"></div>
  <div class="col-xs-6 col-sm-3 col-md-3"></div>
  <div class="col-xs-6 col-sm-3 col-md-3"></div>
  <div class="col-xs-6 col-sm-3 col-md-3"></div>
  <div class="col-xs-6 col-sm-3 col-md-3"></div>
  <div class="col-xs-6 col-sm-3 col-md-3"></div>
</div> <!--行の終了-->

この例は、さきほどのページであれば、下記の部分に対応しています。
・PC表示
f:id:gekka9:20170108172230p:plain

スマホ表示
f:id:gekka9:20170108172237p:plain

それぞれのクラス名を説明します。

col-xs-6

このうち、xs の部分と、6の部分が重要です。
xsはブラウザの横幅が768pxより小さいときに適用するという指定で、
6は、先述の、横幅を12分割したうち、6個分の幅を使う、ということです。
同時に、

col-sm-3

を指定していますが、
mdはブラウザの横幅が768px以上のときに適用するという指定で、
3は、12分割中3個分の幅を使う、ということになります。
さらに、

col-md-3

によって、ブラウザの横幅が992px以上の場合も指定しています。

なので、
ブラウザの横幅が992pxより小さいときは、親要素の幅の半分の幅の要素が8個並び、
ブラウザの横幅が992px以上のときは、親要素の幅の1/4の幅の要素が8個並ぶ
という指定になります。

ここで、ひとつの div class="row" の中に合計12を超える指定をしていることになりますが、12を超えるごとに折返されるので、問題はありません。

その他の要素

見出しやアオリ文などのデザインを定義したTypography群、テーブルやボタン、フォームなど、ありとあらゆるものがすでに定義されており、単にHTMLを書いたものに適用するだけで非常に見栄えの良いサイトになります。

JavaScript

さきほどグリッドのところで提示した画像の1つ1つはボタンになっており、ボタンを押すとモーダルが起動するようになっています。
これは、bootstrapに元々組み込まれているJavaScriptによって、起動用のIDをボタンに設定し、対応するIDをモーダルの要素に設定してやれば、それだけで動作をします。
当然、このモーダルの中にもグリッドシステムなどの使用が可能です。
f:id:gekka9:20170108174642p:plain
画像はtwinviteでの動作例ですが、私はこのためのJavaScriptを1文字も書いていません。
この他にも様々な関数が用意されています。

twinviteへの適用

twinviteでは元々bootstrapが読み込まれているため、上記のような機能がすべて実装済みです。
twiplaやtweetviteでこったことをしようとして、HTMLタグにstyle属性をガリガリ書いている方であれば、これがどれほど強力なことかがわかるでしょう。
twinviteでは、linkタグやstyleタグが使えるため、そのようなことはもはや必要ありませんが、これらのタグはセキュリティ上の危険を孕むため、いつ使えなくなるかわかりません
その日が来る前に、是非bootstrapの学習をオススメ致します。

ただし、1つだけ大変なところがあります。
それは、自由記述部分の横幅が、ブラウザの横幅と一致しないことです。

先程のグリッド部分を、同じ幅のブラウザで表示してみました。
・Webサイト
f:id:gekka9:20170108174039p:plain

・twinvite
f:id:gekka9:20170108174049p:plain

Webサイト方はグリッドシステムがブラウザの横幅すべてを使っているため4つ入りますが、twinviteでは自由記述部分はかなり狭くなるため4つ入れると画像のサイズが極めて小さくなってしまいます。
そのため、グリッドシステムだけは、調整しています。
といっても、 col-sm-6 のようなクラス名を書き換えてやるだけです。

総括

さまざまなイベントが増え続ける今、同じハコや同じテーマでイベントをやることも多いでしょう。
その際、他と違うことを強調したい場合に、凝った招待サイトを作るといったことは非常に有効な手段だと考えています。もしくは、周年記念回のときだけ凝ってみるとか。

気合の入れた宣伝ページを作ることは、そのイベントの本気度や意気込みにそのまま直結していきます。
本気のイベントを気合を入れて打ち出し、最高のイベントへと導くことは、本当に楽しいです。

この記事の内容は私のような職業エンジニアでなくとも、十分に達成できる難易度のものだと思っています。
私自身もそういった告知ページを見ることは好きなので、こういった技術が浸透することを願っています。
是非、気合の入ったページを見せてください。

AWS : Fluentdを使ってEC2からApacheのログをS3へ

月花です。
久しぶりの技術記事です。

最近AWSを触っていて、わかりにくいなあと思ったS3との連携について。

AWSでは、障害が起こったりAutoScalingしてたりすると、EC2インスタンスが勝手にシャットダウンされてしまう。
このとき、ログが吹っ飛ぶのでS3へ逃しておく必要がある。

前提

  • WebサーバとしてのEC2が複数
  • OSはAmazon Linux
  • Apache
  • S3バケットはサーバ分作る

やりたいこと

  • Apacheの アクセス/エラー ログを両方S3へ逃す
  • 設定は軽いほうがいい
  • 自動でやってほしい
  • {server_name}/access_log/{YYYY}/{MM}/{DD}/access_log_{YYYY}-{MM}-{DD}_{HH}.log みたいにしてほしい

アプローチ

まずはどのみちS3へ転送が必要なため、EC2インスタンスそれぞれにS3フルアクセス権限を付与する。

これで、S3への操作権限を得た。このままcpコマンドでコピーすることもできるが、勝手にシャットダウンされて吹っ飛ぶので極めて短い間隔のcronかなにかでcpすることになる。
しかも、都度時間を見て、ディレクトリなければ作るなんてコード書くのめんどくさいし非効率。

そこで Fluentd を用いて、自動化してもらう。
こういうのを、Web Storage Archiveパターン と呼ぶらしい。
CDP:Web Storage Archiveパターン - AWS-CloudDesignPattern

S3へのアクセス権限を与える

まずはIAMロール・・・ではなく、既にEC2インスタンスは動いてしまっているので、IAMユーザを作成する。
サーバ分作ったら(1つでもいいのかしら)、アクセスキーとかをメモしておく。

下記の「AWS CLI を使用してロールを切り替えるには」を参考に、起動中EC2インスタンスに接続し、ユーザを割り当てる。
docs.aws.amazon.com

$ aws configure
AWS Access Key ID [None]: your_key_id
AWS Secret Access Key [None]: your_sec_key
Default region name [None]: ap-northeast-1
Default output format [None]: json

これでアクセス権限を付与できた。
Amazon LinuxにはデフォルトでAWS CLIという、AWSサービスをいじるためのクライアントが搭載されており、S3関連のコマンドは以下の記事に詳しい。
www.task-notes.com

なお、再起動は必要ないので、そのまま叩いてみて、成功なら成功である。失敗したら頑張れ。

NTPを設定する

Amazon LinuxのデフォルトはUTCなので、設定してないと、Fluentdくんが作るファイルを時刻を使って階層化するとき、めちゃくちゃになる。
docs.aws.amazon.com

インスタンスのセキュリティグループのルールでは、ポート 123 (NTP) でアウトバウンド UDP トラフィックを許可する必要があります。ネットワーク ACL のルールでは、ポート 123 でインバウンドとアウトバウンドの UDP トラフィックを両方許可する必要があります。

筆者はこれを忘れてハマった。

Fluentd のインストール

公式ページ
docs.fluentd.org
を参考に、とりあえず入れるだけ入れる。

$ curl -L https://toolbelt.treasuredata.com/sh/install-redhat-td-agent2.sh | sh

こういうインストール方法、めちゃめちゃ助かる。

素のままでも使いやすいっぽいのだが、やりたいことがあるので、Fluentdのプラグインをいれる。
github.com

$ /usr/sbin/td-agent-gem install fluent-plugin-forest

Fluentd の設定

どのファイルをどれくらいの間隔で、どんな名前でどこに転送するのかを定義する。
その定義ファイルは、以下にある。

/etc/td-agent/td-agent.conf

まずは、source節を編集して、どのファイルを対象とするかを定義する。
なお、基本的に末尾に追記で問題ない。

<source>
  @type tail
  format apache
  path /var/log/httpd/access_log
  pos_file /var/log/td-agent/httpd.access.log.pos
  tag s3.httpd.access
</source>

ここでは例として、Apacheアクセスログを対象とする。

type tailにするとtailコマンドで末尾をフォローしてくれる。このタイプによってオプションが変わる。ここから先は全部tailが対象のオプション
format 対象とするログのフォーマットを選択する*1
path 対象のログへのフルパス
pos_file 前回どこまで読んだかが書いてある栞ファイル。Fluentdが勝手に生成するので、わかりやすい名前にしておく
tag ソースごとにタグ付けが行える

このあと、エラーログを見るsourceも書いておいた。
タグは s3.httpd.error とした。


次に、さきほどのソースを、どれくらいの間隔で、どんな名前で、どこに転送するのかを定義するために、match節を記述する

<match s3.*.*>
  type forest
  subtype s3
  <template>
    aws_key_id your_key_id
    aws_sec_key your_sec_key
    s3_bucket bucket_name
    s3_region ap-northeast-1
    path ${tag_parts[1]}/${tag_parts[2]}_log/
    buffer_path /var/log/td-agent/s3/${tag_parts[2]}_log
    time_slice_format %Y/%m/%d/${tag_parts[2]}_log_%Y-%m-%d_%H
    time_slice_wait 10m
    #flush_interval 3s
    buffer_chunk_limit 256m
  </template>
</match>
match タグに対応している。このmatch節はどのソースに使うのか、ここで定義する
type さきほどインストールしたプラグインを使うのでforestとする
subtype S3に投げるため。この辺は用途によって決まってくる
aws_key_id IAMユーザのキーID
aws_sec_key IAMユーザのシークレットキー
s3_bucket S3バケット名。なければ作ってくれる
s3_region S3のあるリージョン。例では東京
path バケット直下からの共通のパス。forestによって、${tag_parts}が有効になっている。matchのところで指定した、*の部分が配列になって1から順に収まっている。このおかげで、複数のソースに対して1つのmatchで済むようになる
buffer_path S3へ転送する前のバッファファイル。わかりやすい名前にしておく
time_slice_format 生成したいファイル名。年月日とかはforestがなくても取れるから階層化しておこう
time_slice_wait S3へ転送する間隔
flush_interval こいつがあると、上記の間隔より優先される。例ではコメントアウトしているが、3秒なのでテスト向け
buffer_chunk_limit 指定したサイズを超えたバッファファイルはエンキューされて、次のバッファファイルが作られる

Fluentd を起動してみる

いつものやつで起動して、ログを見てみる。

$ chkconfig td-agent on
$ service td-agent start
$ td-agent td-agent:                                         [  OK  ]
$ cat /var/log/td-agent/td-agent.log

起動はしたが、エラーがでていた。

2016-11-08 17:13:58 +0900 [error]: Permission denied @ rb_file_s_stat - /var/log/httpd/access.log
2016-11-08 17:13:58 +0900 [error]: suppressed same stacktrace

出たー、Permissionだ!!!!
ありがちなハマり。

多少強引だが、他の設定をいじらなくてよいので、Fluentd の実行ユーザをrootにしてしまおう。
kenzo0107.hatenablog.com

TD_AGENT_USER=td-agent 
TD_AGENT_GROUP=td-agent 

↓ここをこう↓

TD_AGENT_USER=root
TD_AGENT_GROUP=root

restartしてログを見てみると、

2016-11-08 18:51:54 +0900 [info]: listening fluent socket on 0.0.0.0:24224
2016-11-08 18:51:54 +0900 [info]: listening dRuby uri="druby://127.0.0.1:24230" object="Engine"
2016-11-08 18:51:54 +0900 [info]: following tail of /var/log/httpd/access_log
2016-11-08 18:51:54 +0900 [info]: following tail of /var/log/httpd/error_log

ということで、無事にログファイルがフォローされた。

S3を確認

AWSのサイトでバケットを見に行こう。

今回の例では

すべてのバケット /bucket_name/httpd/access_log/2016/11/08/access_log_2016-11-08_17_2.gz

という具合になって、終わり。

*1:一覧 : docs.fluentd.org