Github Appsはいいねぇ。

リリンが生み出した文化の極みだよ。

Github Marketplaceには色々なアプリがある。
使いこなしていい感じに自動化したいし、READMEにgreenのバッジが並んでるとやっぱりいいなぁと思う。
TravisCI/CircleCI/Codecovはもはやメジャー過ぎて、どれも入ってない方が逆にやばい。
言語依存のものもあるので、全部は体験できないなぁとも思う。

学び

  • TravisCIなどメジャーなものも自分の知らない使い方が結構あったりして勉強不足だ
  • privateなもの、secureなモノがあるOrgリポジトリの場合と個人の場合で使い方が結構ちがう
    • その辺も意識していろいろとやってみたほうがいい
    • OSSなら無料のものはそのサービスのplaygroundリポジトリを作るのが良さげ
  • 似たようなサービスでも違いは多い
     - 応答速度や安定性
    • masterに変更が発生した場合の挙動
    • サポート&ドキュメント充実度

全体的に暗黙知になってるものが多い印象なので、Github Apps Meetupとかやったらいいのに(あるの?)

気持ち

コードの学習も大事だけど、使えるサービスを使い倒せるようになりたい。(小並)
できるだけbotに働かせよう。


Hexoにした

あまり気に入ってはないが、あまりにも放置状態でblog環境が死んでいたのでさっと移行できそうなHexoに移行した

気に入ってないポイント

  • templateがejs
  • themeにjQueryが使われてたりする
  • 割りとメジャー

楽だったポイント

  • gh-pageへのdeploy pluginがある
  • RSSも実装できる(atom.xml)
  • wintersmithと同じMarkdownフォーマット
    • 過去記事がコピペで済む
  • CNAME

日本語フォントが割りときれいなのは良いと思った。

TODO

  • customize theme
    • code (syntax highlight)
    • color
  • 3rd party
    • google analytics
    • SNS link

markdown sample

normal text
bold
italic
code

1
2
const message = "hello hexo"
console.log(message)


esa.ioを家庭で使ったらなんとも楽になった話

情報を育てながら共有するドキュメントサービス、esa.io(えさ)を使いはじめて1年以上が経過しました。
初めは嫁さんと一緒に趣味でつくっているナニカについて、タスクや設計、ナレッジを共有するために使う目的でしたが、
最近はもっぱら家のことを日報という形で共有するツールとして使っています。

そこで今回は”えさ”が家庭のタスク管理に非常に優れたツールであることをやや興奮気味に書きたいと思います。

esa.ioと家庭の親和性

すでに家庭での有効性についてはMoney Forwardのエンジニアさんがスライドつきでブログ
を書いていらっしゃるのですが、改めて。

まずはesa.ioのトップにも書かれているえさのコンセプトを。
esa.io は「情報を育てる」という視点で作られた、自律的なチームのためのドキュメント共有サービスです。

家庭はいつもWIP

「情報を育てる」というのは「中途半端な状態から情報共有を開始して、みんなで形にする」ということでしょう。
「中途半端な状態」はつまりWIP(Work In Progress)ですね。

さて、家庭にはいろいろなタスクがあります。

  • 家具家財の購入
  • 連休の予定
  • 保険加入やローン見積もり
  • 親戚の冠婚葬祭
  • 子どもの進学や習い事
  • 保育園のイベントや役員の仕事
  • 地域のイベント準備

もう毎週なにかを決めて予定を組んでいきますが、それでも手が回り切らないことも。
そしてすべてが「未定の予定」で、予定どおり進まないことなんてザラ。予定の組み換えをするのはしかたないのですが、
夫婦ともに同じものを見ていないと認識ズレがあったりしてコストもかさむ。
特に保育園なんかは紙媒体や担任の口頭で情報が来るので、「お互いがその情報を把握しているかを把握する」という多重苦です。
つまり、家庭はWIPのタスクをいくつも並行して抱えるわけですね!!

夫婦はすでにチーム

現代の我々世代の家庭であれば、役割分担はあれども、夫婦両方で家庭のタスクをまわすことが多いでしょう。我が家のように共働きならなおのこと。
つまり夫婦とは、家庭というシステムを保守しながらグロースアップさせるチームなわけです。
我が家は特に役割分担の境界線が曖昧なので、「自律的なチーム」と言えるでしょう。

「漢は黙って」だとか、言っている場合ではありません。「夫婦の阿吽の呼吸」だとか「ツーカーの仲」とか、そういうのは非効率。
さっと共有して、バシッと残しておく。長男のときに解決したことは数年後に次男にやってきます。
数年の間その解決手段を覚えておくことはできません。

家庭タスク管理とツール

もともと我が家は、家庭用のコミュニケーションやタスク管理ツールとしていろいろなものを使っています。

  • Twitter
  • Idobata
  • hubot
  • Redmine
  • github
  • Google Drive

hubotは軽量さとカスタマイズ性が良かったのですが、夫婦の間にbotが入る煩わしさ(どっちがhubotに命令したか、とか)
があって、それほど便利に使えていません。RedmineはiOSアプリもあって便利だったのですが、
家庭のタスクをチケット化すると、チケットのライフサイクルが短い割に大量の件数になってしまい、
運用コストが大変で管理できませんでした。(あと、嫁さんにとって使いなれていないツールだった)

githubはファイルとチケットを紐付けるのが楽なのと、夫婦共に使い慣れているということで
息の長いツールになっています。

家庭用のesaのはじまり

初めは、嫁さんがメモを書いたことでした。
タスクがつまり過ぎて把握できなくなりそうだったので、とりあえずここでいいや、って感じで。
その後も日付を見失わないように日報のテンプレートで、メモ書きが何度かされていました。
僕の方も嫁さんが忘れたときのために内容を把握したり、購入予定の商品を検索してコメントしたりしていました。

そのうち、嫁さんのメモ書きはTODOリストをならべた、ちゃんとした(?)日報になりはじめ、
後を追って僕も考えてることや買いたいもの、覚えているタスクを並べるようになりました。

例えば夕飯の献立だって、冷蔵庫の中身や買い出しの有無、作る時間がどれくらいとれるかで決めているみたい。
WIPの予定を嫁さんが立てれば、僕が食べたいものもリクエストできるし、嫁さんが献立のもう一品に
あれこれ迷う時間も減ります。

家庭用のesaの副次効果

もともとはタスク管理としてのえさですが、別の側面からもいい影響があります。
そもそも家庭が抱える解決すべき対象というのは、合理的な判断だけで決定できるものだけではないので、
ちょっとした摩擦になることも多々あるわけで。
そんなときに感情で議論すると大概、失敗です。もうどっちかが血ヘドを吐くまで、朝まで生テレビ状態。

チャットもよくありません。自分の間違いを訂正できないため、ちょっと混みいった話をするとすぐに
何の話をしてるかわからなくなります。住宅ローンの話を初めたのに、いつの間にか好きな猫の種類の話とかになってます。
2年ほど前の僕のツイートも貼っておきましょう。

はい。ややポエムっぽい文調がキモチワルイですね。
さておき、日報という形で「家庭のタスク」と「考えてること」を共有すると、言い難いことも柔らかく言えるように思います。
「事実」と「感情」が明確に区別して表現できるからでしょうかね。
コメントに引用リプライすることで小さな議論もできますし、書く側は自分の主張を全部だしきってから、読む側は読んだ後にじっくりと
考えてから意見をいえます。これもチャットには無い特性ですね。

まとめ

  • 夫婦は小さなタスクをたくさん抱える、小さなチームである。
  • 感情だけでは家庭はまわらないが、心を込めずに物事を決める家庭も価値が低い。そのバランスをうまーく保てるようにするのが大事。
  • esaの日報形式だと日毎にタスクを共有できるし、じっくりと考えを言い合える。Idobataに日報も通知される。
  • esaならMarkdownで構造化できるので、事実の部分(TODO)と感情の部分(所感)がちゃんとわかれてスッキリ。


Pebble 寿司ゆき Watch Faceをリリースしました

iOS/Android対応のスマートウォッチ、Pebbleを買ってしばらく遊んでいました。
せっかくなので自分で何か作りたいなぁと思いたち、まずは敷居の低そうなWatch Faceに挑戦。

久しぶりのC言語にガタガタ震えながら、LINEスタンプやUFOキャッチャーのぬいぐるみでおなじみの寿司ゆきで廻る寿司WatchFaceが完成、リリースしました!

Pebble 寿司ゆき watch face

寿司ゆきのPebble Watch Faceをリリースしました。
http://apps.getpebble.com/en_US/application/5616705a7e612637e600007f

5種類の寿司が右から左へ流れていき、無限に回転ずしを楽しめます。

qrcode

画面下には充電を%で表示しています。

Pebble

PebbleはKickStarから生まれたiOS/Android両対応のスマートウォッチ。
それでいて、価格は約$200、これは買いですね!!

寿司ゆき

寿司ゆきの画像は、CC-BY-NC-NDですでに提供されている画像を使用してプロトタイプを作っていたのですが、
作者である@awayukiさんのご厚意によりPebble用の回転ずしバージョンをご提供いただきました。
お忙しいなか、大変ありがとうございます。

Pebble 開発環境など

すでに詳しいブログや公式チュートリアルがあります。
開発環境からリリースまではこちらのブログが本当によくまとまっていて有りがたかった!

つぎはWatch Appか、違うタイプのWatch Faceを作りたい!!



iOSのopenURLでハマった

iOSアプリから電話発信をしたい場合にopenURLで標準アプリ(Phone.app)を呼び出すことができる。
それでウキウキと開発していたら発信できない番号に出会い、泥沼にハマったのでメモ。

openURLについて

電話にかぎらず、iOSではURLオブジェクトを開くことでアプリからアプリを呼び出す事ができる。ただし、これは見かけ上の話で、実際には間にURLを解決する
ナニカがiOS上で働いているようなので、Androidでいうインテントとはちょっと趣向の違う仕組みだ。
あくまでもURLを開く処理をアプリで実行すると、該当のURLスキーマを持っているアプリが起動するということで、アプリ自体を指定して起動しているわけではない。

例えばhttp://ならSafari、tel://ならPhone.appがそのスキーマに対応しているので、iOSからURLを受け取って動作する。
電話の場合は下のようにURLを開くことで電話発信が開始する。

1
2
3
NSString *url_string = [@"tel:" stringByAppendingString: @"117"]; //時報にかける
NSURL URL = [NSURL URLWithString:url_string];
[[UIApplication sharedApplication] openURL:URL]; // tel:のURLスキーマに対応する電話アプリが起動

実際にこれで発信自体うまくいくので、大したことはないと思っていたのだが、実際、めちゃくちゃハマった。

1. URLスキーマが曖昧(基礎理解編)

まず、電話アプリのURLスキーマがいろいろあって戸惑う。
調べていると、電話発信のURLスキーマはtel:tel://telprompt://があり、tel:tel://は大別が無く、どっちも同じ挙動となるらしい。
telprompt://は発信をするのではなく、確認ダイアログを表示し、発信ダイヤルを入力した状態で電話アプリを起動させるスキーマで、iOSの公式リファレンスにはないということだ。
リファレンスに載っていないとなると、正常に動作しようと、審査を通過できようと、どうにも使たくない。

2. 電話番号の仕様がよくわからない(困惑編)

電話番号は一般的には090-XXXX-XXXXなど、数字の羅列なのだが、普段使わないそれ以外の文字もある。*#だ。

そして、*#を上の[[UIApplication sharedApplication] openURL:URL]で開くと電話アプリが開いてくれない。
(正式にはアスタリスクや井桁じゃなために)そもそも*/#が発信する文字列として間違っているのか??

しかしiOSの連絡先アプリ(Contact.app)やUIKeyBoardTypePhonePadなどを見てみると、それぞれの文字列が電話番号の入力文字として間違っていないことが確認できた。

余談だが、電話で「*」を押すボタンは「アスタリスク」ではなく、「*」を90˚回転させた記号だ。
そして「#」の名称は「シャープ」ではなく、日本名は「井げた(いげた)」などという。井げたの記号は「#」ではなく「#」である(もう見た目違いがわからない)。

僕のiPhoneでは【しゃーぷ】の変換候補に「#(井桁)」が出てくるし、【いげた】の変換候補に「#(シャープ)」が出てくるし、「*」を90˚回転させたアレ
Unicode5.1で採用された特殊文字で普通は使われていないらしい。はー、紛らわしい。

3. ポーズすると発信できる(混乱編)

*#以外にも電話番号には数字以外の文字が存在する。国際番号を示す+や、ダイヤルの発信を一時停止する,である。
ちなみに、,は[ポーズ信号]と呼ばれ、iOSの場合は次の番号の発信まで、発信が3秒間スリープする。
そして*#はこのポーズ信号の後だとちゃんと電話アプリに渡り、電話発信が始まるのである。

oh…意味がわからない。

4. 公式リファレンスの説明が曖昧(解決編)

*#が含まれていると電話発信できなかったので、iOSのリファレンスをちゃんと読んでみよう。
Phone Links

To prevent users from maliciously redirecting phone calls or changing the behavior of a phone or account, the Phone app supports most, but not all, of the special characters in the tel scheme. Specifically, if a URL contains the * or # characters, the Phone app does not attempt to dial the corresponding phone number. If your app receives URL strings from the user or an unknown source, you should also make sure that any special characters that might not be appropriate in a URL are escaped properly. For native apps, use the stringByAddingPercentEscapesUsingEncoding: method of NSString to escape characters, which returns a properly escaped version of your original string.

さっと読んだ感じだと、URLなんだから特殊文字はstringByAddingPercentEscapesUsingEncoding:でエスケープしてって書いている。
実際、多くの方がそう解釈しているのをStackOverflowなどでみたし、僕自身、「Escapeすればいいんだ!」って嬉々としてコードを書いた。
ところが*#が含まれているとやはり発信できない。EscapeにつかっているEncode用の文字コードが悪いのかとか、いろいろ試行してみるも、ダメ。

そこで調べなおしてみると、どうやら上のリファレンスは説明がかなりひどい。

  • 電話のURLスキーマtel:については、*#が含まれていたらURL開かない仕様ですよ。

To prevent users from maliciously redirecting phone calls or changing the behavior of a phone or account, the Phone app supports most, but not all, of the special characters in the tel scheme. Specifically, if a URL contains the * or # characters, the Phone app does not attempt to dial the corresponding phone number.

  • (一般の)URLスキーマを使う場合に特殊文字があるのであれば、URLエスケープしてね

If your app receives URL strings from the user or an unknown source, you should also make sure that any special characters that might not be appropriate in a URL are escaped properly. For native apps, use the stringByAddingPercentEscapesUsingEncoding: method of NSString to escape characters, which returns a properly escaped version of your original string.

ってことらしい(英語圏の方が勘違いしていたくらいなので、英語力の問題ではないと信じたい)。

結論

*#が含まれるtel:はどうやっても開かない。ちなみにURLが開けるかどうかを判定する[[UIApplication sharedApplication] canOpenURL:(NSURL *)]という
メソッドがありますが、こいつはふつーにYESを返すので、チェックとして機能しない。開かないのに。



iOSでQRコードを読み取ってみる

iOS7からQRコードの読み取りが簡単になっていたらしいのですが、そもそも最近はQRコードをあんまり見なくなったのでした。
思い立って(?)、QRコード読み取りのアプリを作ってみたのでメモ。あ、言語はObjective-Cです。

プロジェクト作成

いつもどおり、[New Project]から、[Single View Application]を選択、LanugageはObjective-C.
プロジェクトの[General] > [Deployment Info] > [Device Orientation]でPortraitだけにしておく(面倒なので)。
今日現在でiOS 8.2がでてますが、iPhone6を8.1からアップデートしてなかったので[Deployment Target]を8.1にしておいた。

カメラプレビューはView全体を使っちゃうので、storyboardは特に変更なし。

ViewController.h

カメラ入出力を扱うのでAVFoundation関係のimportと、画像認識のメタデータを扱うdelegateが必要です。
あとカメラのセッションをインスタンスで持っておかないと使えないので、これもプロパティを用意しておきます。

1
2
3
4
5
6
7
8
9

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>

@interface ViewController : UIViewController <AVCaptureMetadataOutputObjectsDelegate>

@property (strong, nonatomic) AVCaptureSession* session;

@end

ViewController.m

viewDidload

とりあえずviewDidLoadでカメラ周りのインスタンスを生成しつつ、sessionを作成します。
本当はカメラが使えるかー?とかの判定があるべきですが、カメラある前提で書いていきます。
全コードをまず書いてしまうと、こんな感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (void)viewDidLoad {
[super viewDidLoad];

// Device
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// session
self.session = [[AVCaptureSession alloc] init];

// Input
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
if (input) {
[self.session addInput:input];
} else {
NSLog(@"error");
}

// Output
AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init];
[self.session addOutput:output];
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
[output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code]];


// Preview
AVCaptureVideoPreviewLayer *preview = [AVCaptureVideoPreviewLayer layerWithSession:self.session];
preview.videoGravity = AVLayerVideoGravityResizeAspectFill;
preview.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self.view.layer insertSublayer:preview atIndex:0];

// Start
[self.session startRunning];
}

ビデオデバイスとセッション、inputを作る。
previewや[self.session startRunning]特筆することはなく、カメラを使いたいときのいつもの感じです。

QRコード認識

delegateを受けておいて、delegateメソッドで認識した時の処理を定義します。
@[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code]を指定しているとこでQRコードの認識を設定。

1
2
3
4
5
// Output
AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init];
[self.session addOutput:output];
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
[output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code]];

認識時処理

認識したらcaptureOutputがdelegateメソッドで呼ばれるので、読み取ります。
[(AVMetadataMachineReadableCodeObject *)data stringValue]で文字列化しています。
顔認識の場合とか、他の識別が動作した場合もこのメソッドが呼ばれてしまうので、data.typeAVMetadataObjectTypeQRCode
ときだけ読み取り文字列をURLとしてSafariで開くようにしています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection
{
// 複数のmetadataが来るので順に調べる
for (AVMetadataObject *data in metadataObjects) {
if (![data isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) continue;
// QR code data
NSString *strValue = [(AVMetadataMachineReadableCodeObject *)data stringValue];
// type ?
if ([data.type isEqualToString:AVMetadataObjectTypeQRCode]) {
// QRコードの場合
NSURL *url = [NSURL URLWithString:strValue];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url];
}
}
}
}

試してみる

試しにこのブログのURLをQRコードにしてみた。識別されたらこのブログを自動的にSafariで開きます。

qrcode


Java(Android)でHTTPSクライアント認証

日本語で簡潔に説明されているものを探せず苦労したので雑にメモ。検索するとサーバ証明書関連が多くて、Androidアプリをクライアント認証させるのはあまり多い事例じゃないっぽい。

そもそもHTTPS認証ってなにかを雑に

証明書によって認証する。認証はドメイン(common name)単位で署名される。
サブドメインはワイルドカード指定可能(googleの場合だと*.google.comになってる)。
Androidに関する証明書形式の対応はnexusのヘルプページが参考になる。

HTTPSサーバ認証の雑な説明

  • https通信の信頼性を検証し、クライアントがサーバを信頼するために使用する。
  • クライアントは他者(認証局:CA)の署名を信頼し、その署名をもつサーバを信頼する。
  • 不特定多数のユーザに公開されているサービスで使用する。
  • 形式はX.509、拡張子は.crt一般的と思われる。
  • CAの署名(とその証明書)がサーバ証明書を証明する。
  • すなわちCA証明書はサーバ証明書の証明書
  • CAの証明書は自己証明書。
  • AndroidにはグローバルなCA証明書が最初から組込まれている。
  • Androidに後入れしたCA証明書はChromeで使用できる。

HTTPSクライアント認証の雑な説明

  • httpsで認証されたサーバが、特定のクライアントのみを受け付けるために行う認証。
  • 認証局が署名した証明書で署名される。通常、クライアント証明書には署名したCA証明書が含まれる。
  • インターネット上の特定多数(特定多数がイントラネットでしばれない)ユーザにのみサービスを公開する場合のプロトコルレベルでの認証。
  • 形式はPKCS#12、拡張子は.p12

HTTPSクライアント認証:サーバサイド(Apache2.2以上)

crt作成と認証局署名を行い、証明書をサーバに配置して通常のhttps設定の上、クライアント認証設定を追加する。
方法はmod_sslのドキュメントの通り、SSLVerifyClient requireを特定ドメインに設定する。

クライアント認証の対象をpathで絞りたい場合は<Directory><Location>を使う。さらにajpでリバースプロキシしている場合は以下のように行う。

1
2
3
4
5
6
7
8
9
10
11
# default is none.
SSLVerifyClient none
## ajp reverse proxy setting
ProxyPass / ajp://localhost/

# client certification for specific path
<IfModule mod_proxy_ajp.c>
<Location /secret/>
SSLVerifyClient require
 </Location>
</IfModule>

HTTPSクライアント認証:Android app(APIレベル8,21で確認)

org.apache.http.impl.client.DefaultHttpClientでGET/POSTをするときに以下のメソッドでHttpsスキーマを登録しておく。
クライアント証明書は/assetに入れておく前提。

ついでに

通常のHTTPSサーバ認証については、JSSECのサンプルが参考になる



Hibernate ValidatorはTomcat6だと動かない

JavaなWebアプリで入力チェックを実装する上でとても便利なHibernate Validator
その名の通り、一連のValidationをannotationベースで実装できる。元の仕様はJSR 303と呼ばれるもので、Spring MVCでは3.x系から取り込まれたらしく、annotation-drivenなコードが書ける。

だけど、Tomcat6だとチョットだけ問題があった。

Hibernate Validator?

JSR 303で定義されているannotationベースのvalidationに独自の拡張
を与えたもの。Webアプリでの入力チェックあるあるをほぼ網羅しているので、よく使われる形式のデータはannotationを記述するだけで入力チェックとして機能する。

Mavenプロジェクトなら、pom.xmlに追加すればよい(現時点での最新は5.x系)。って、ここに書いてる。

1
2
3
4
5
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.1.2.Final</version>
</dependency>

実装例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Human.java
*/
public class Human {

// 必須入力!
@NotNull
private String name;

public String getName(){
return this.name;
}

public void setName(String name){
this.name = name;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* RootController.java
*/
@RequestMapping("/")
@Controller
public class RootController {

/**
* app rootへのアクセス
*/
@RequestMapping(method = RequestMethod.GET)
public String index(
@Valid @ModelAttribute Human human,
BindingResult bindingResult
) {

if(bindingResult.hasErrors()){
// 入力チェックエラーの処理
}

return "index";
}
}

@Valid Humanannotationで@NotNullのチェックを実行させ、その結果をBindeingResultが保持(参照?)している。

複数の制約で一つの入力チェックとしたい場合は独自でannotationを実装する。これについてはこのエントリ
非常にわかりやすくてためになった。ちなみに、validationのエラー時のメッセージはpropetyファイルで管理されているので、メッセージの日本語化に関しても、上記のエントリがすごく役立った。ありがとうござます。

本題

で、だ。悲しい事件が起きたのはサーバにデプロイしたときである。なんか見覚えのないエラーが吐かれた。

1
NoSuchMethodError: javax.el.ExpressionFactory.newInstance()Ljavax/el/ExpressionFactory)

ググる。あった。

Hibernate Validator 5 requires the Unified Expression Language (EL) in version 2.2 or later.
While Tomcat 7 provides EL 2.2 out of the box, Tomcat 6 only comes with an EL 2.1 implementation which does not work with Hibernate Validator.

$CATALINE_HOME/libのELが2.2と2.1の間で互換性がないせいで、5.x系はtomcat6そのままだと動かないんだー。

You can therefore either upgrade to Tomcat 7 or update the EL libs in your Tomcat 6 installation.
To do the latter, replace the JAR files el-api.jar and and jasper-el.jar in $CATALINE_HOME/lib with an EL 2.2 implementation, e.g. from Tomcat 7.

Tomcat7のlibから2.2を持ってきて上書きすればいいよ、と添えられています。

僕の環境ではjarに対してシンボリックリンクが貼られていたので、上書きではなくリンク先をすげ替えることで無事動作しました。

おまけ

そもそもEL(el-api.jar)ってなに?

JSR 341で3.0について説明されていました。

上の例だと、Human.classのインスタンスhumanのプロパティに対して、通常のjavaコードではアクセサメソッドを経由しなければならない。
一方JSTLを呼び込んでいるJSPなどではhuman.nameで取得できる。この辺を旨いことやってくれているものがEL(Unified Expression Language)らしい。
CDIにも関わるとか。詳細はわかりません。



Apache-Tomcatのプロキシ設定を変更したらsessionがとれなくなった

Javaアプリでsessionスコープに保存したPOJOを取得できずにハマったときのメモ。

構成

  • アプリのフレームワークはSpring3。通常のServletアプリケーション。
  • 前段のWebサーバにApache、JavaアプリのコンテナとしてTomcatを用いて、両者の連携はmod_proxy_ajpを利用。

やりたいこと

ふつうにsessionスコープに保存したobjectを別のパスで取得する。
イメージとしては下の感じ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

package hoge.foo.contoroller


import hoge.foo.LoginForm

@RequestMapping(value="/user")
public class UserController{

private static final String SESSION_FORM_NAME = "user_input_data"

/**
* ログイン
*/
@RequestMapping(value="/login", method=RequestMethod.POST)
public String login(
@Valid @ModelAttribute LoginForm form,
BindingResult result,
HttpServletRequest request,
Model model) {

if(result.hasError){
return "login"; //入力エラー
}

// ログインに関する処理

request.getSession().setAttribute(SESSION_FORM_NAME, form); //session格納
return "dashboard";
}



/**
* セッションのユーザデータを参照して編集画面を表示する
*/
@RequestMapping(value="/edit", method=RequestMethod.GET)
public String editView(
HttpServletRequest request,
Model model) {

LoginForm form = request.getSession().getAttribute(SESSION_FORM_NAME);

if(form == null) {
return "login"; // ログイン画面に戻る
}
//後続処理

return "edit";
}

}

sessionからデータがとれない!

/user/editを叩いても常に/user/loginに遷移する。つまりform == nullが常にtrueとなってしまっている。
念のためデバッガでステップ実行してみても、やはりformがnullである。

セッションタイムアウトの設定やSpringでのセッションの利用方法など、あらゆるドキュメントを確認するも、
まったく解決の糸口が掴めない。なんだ….なにが悪いっていうんだ….!!

あっ!

ここで、前日の開発で別のコンテキストパスで動作させるために、server.xmlとhttpd.confのProxyPassを変更したことを思い出す。
httpd.confはこんな感じ。

1
ProxyPassMatch  /  ajp://localhost:8009/spring/

そしてJSESSIONIDがクッキーに保存されていて、クッキーにはPathがあって……!?

あれ…?もしかしてセッションIDのクッキーのPath、ずれてるんじゃない…?

とりあえずリバースプロキシの設定を戻し、ApacheとTomcatを再起動。

1
ProxyPassMatch  /  ajp://localhost:8009/

##あー、とれたわー。sessionから普通にobject取得できたわー。

ちなみにリバースプロキシでパス階層がずれる場合は、ProxyPassReverseCookiePathディレクティブを使うらしいです。



Cabin.jsでのブログ運用を保留した話

ブログ生成をNode.jsベースのstatic site generator、Cabinに乗換えようとしたが、すぐに移行したいほどでなかった。

発端

このブログはwintersmithというNode.js製のstatic site generatorを使って、Markdownで書いている。
記事はこちら

不満というほどではないが、懸念はいくつかある。

  • あまり活発に動いていないっぽい
    • 本家の更新がしばらく止まってる
    • プラグインが全然増えてない
  • masterにpushしてからgh-pagesにpushするデプロイステップがない
    • werckerを使って自動化しているが、使っているデプロイステップは自前のものじゃない
    • wercker待ちになるときがある
    • werckerを毎回見に行くのが面倒
  • Node.jsのうまみをもっと引き出した拡張性が欲しい
    • gh-pagesにpushするならGruntがよさそう
    • Gruntは活発に更新されているし、プラグインもたくさんでている
    • デザインもいろいろ選べるといい

Cabin.js

Cabinは、”ブログを書く”ことに注力したNode.jsベースのツールで、Octopressのようにgh-pagesデプロイが標準装備されている。
このデプロイはgrant-gh-pagesをつかって、Gruntファイルつきで用意される(後述)。

CIサービスを使ってデプロイするのも当然便利ではあるのだが、Gruntを使うことでpushとデプロイを分離できるメリットがある。
ブログを途中まで書いてpushしておき、別の場所で続きを書き始めるときなどは、push=デプロイじゃないほうが嬉しい。
また、CIサービスは待たされることがあるのも仕方ない。


Cabinは新規作成時に対話的にブログコンテンツのひな形をつくれるところも非常によい。
付属するGruntfileがこれを実現しているようだ。
npm initのように、何をつくるのか、どういう設定にするかを聞いてくれるので、それに答えるだけで設定が完了する。
ブログのデプロイ先としてgh-pagesを使うようにすると、grunt deployをgh-pages用としてGruntFileを作成してくれる。

また、テンプレートとしてjadeとejsが選べたり、デフォルトでデザインテーマが3つついて来たりする。
CSSもStylusを使ってライブリロードしてくれるので、デザインもストレスなく変更できる。
Disqusも標準で実装されているので、本当にすぐにブログが始められる。

でも移行は保留

非常に便利で素敵で使いやすくて、ブログを最初から作るなら迷わずこれを使っていただろう、とおもう。
だが現状のWintersmithから移行するか、となると話は別であった。

  • RubyとPythonが必要
    • Compassのインストールが必要なので、Rubyを動かす必要がある。(ライブリロードはcompass -wで動いているっぽい。)
    • Syntax highlightのために、Pygmentsを使うので、Pythonが必要。Pygmentsは非常に多くの言語をサポートしているので、好きではあるが。
  • Stylusの学習コストと用意されているコード理解のコストがそこそこかかる
    • そもそも普段はデザインをしないので、いまのところStylusの学習は優先度を上げたくないし、コストもかけたくない。
    • 用意されているStylusの内容を理解するのも結構大変
  • 環境が複数あると面倒
    • すでに複数環境でnpm installするのさえ面倒なくらい(怠惰)
    • ちょっと直したいなと思ってもRuby/gem(Compass)、Python、Node/npmまでやって、やっとのことできたのはHTMLの修正だとちょっと不釣合
    • 今のままでGruntからgh-pagesにpushできるようにした方がスマートな気がする
  • これまでのブログがちゃんと表示されるかチェックしいなといけない
    • Markdown基本の記法は一緒だが、generatorが認識するヘッダ部分とかが違う。デザインが違うので、微妙なずれとかもやっぱり出ちゃう。
    • Markdown意外にも、Google Analyticsとかtweetボタンとかもviewテンプレートに移行する必要がある。

それでもまだ、そのうち移行したいとは思っているが、移行コストほど困っているわけでもないので、とりあえず保留とした。
そのうち時間があれば、そしてGruntやStylusがメインストリームとしてもっと発展したら、移行する。かも。