フルリモートワーカーになって半年ちょっと経ったのでまとめる

突然ですが、僕は青森県でフルリモートで働いている。出社は東京のオフィスに月に1回、だいたいが1泊2日。転職したのは2017年の7月で、今日で約7ヶ月半が経った。そろそろフルリモートについて軽くまとめておこう。
さらに突然ですが、僕には嫁さんがおり、その嫁さんはWebデザインやUI/UX系のお仕事をしており、むかしから家でフルリモートをしている。もともとはフリーランスだったのが、今は社員としてフルリモートをしているので、 フルリモート社員夫婦 という割りと稀有(だと思っている)な環境になりました。※会社はまったく別です

転職についてはそれほど言いたいこともないので、フルリモートそのものについてちょっとまとめておこうとブログ記事をおこした次第です。


トレードオフ

得たもの

  • 自分にあった椅子
  • 自分の気分で決められる音楽、音響
  • 自分にあった室温・湿度調整

失ったモノ

  • 仕事を切り上げるタイミング
    • 気になったり、修正方法を思いついたりすると、家のどこでも数秒でオフィスに行けるのと同じ。結果、煮詰まって辛くなるので、強い気持ちで「今日は終わり」とする。
  • 早起きに対するモチベーション
    • 出社に公共機関も使わないし、雪かきしないと車が出せないとかもない
    • こどもたちを送り出すためだけに起きる

フルリモートは楽か

  • 仕事に付随する仕事ではない活動が圧縮されるので、圧倒的に楽。
    • 電車、徒歩、車による通勤
    • 外食主体の昼食
    • 職場によっては電話や外来の対応
  • ただし、仕事でない活動の中に楽しさがある人はむしろ苦しいのかもしれない。
    • 通勤がないので何気ない外部の変化を感じない。桜が咲いた、とか。
    • 偶発的に得られる、知らないものへの認知を得ることができない。
  • 体力は必要
    • 出社が完全に不要な環境はなかなかない。平日に長距離移動しながらコード書いたりするのは想像しているよりも体を酷使する。

フルリモートはだれでもできるか

これは賛否両論だろうし、あまり強い言葉で主張しにくいのですが、また、決して自分が頑張っているという自慢の意味ではなく、そんな気持ちを一切抜きにして、 「No」 と言わざるを得ない。ある種の才能なのか、適正なのか、はたまた慣れや経験なのか、リモートが楽な人、そうでもない人、苦痛な人、がそれぞれいると思う。リモートだとチャットが唯一の社内コミュニケーションになることがおおいでしょうが、フルリモートの場合はそれに輪をかけて、チャットで観測できないものは会社で起きてないのと一緒で、 チャットが会社そのもの になる。だから最初の1ヶ月くらいはチャットを常に追いながらコードを書くという脳みそに慣れるのが結構大変だった。できるようになったからいいけども、出来ないひとがいても普通だコレ…というのが正直な感想。
僕の場合は先人のフルリモートワーカーがいてくれたおかげで、そのチャットの呼吸(としか言いようがない)をモデルに自分なりに染み込ませていった。


うまみとつらみ

うまみ

  • 地方だからといって言うほど物価が違うわけではないのだが(同じ日本だぜ)、主に子どもにかかるお金が安いのは助かっている。出入りでいうと割がいい。
  • 節約できる。外にいるとどうしても缶コーヒーとか買っちゃう。家は温かいコーヒーが淹れられる。
  • 思いっきり休憩できる。昼食をとった後に10分くらいソファやベッドで休んだり。

つらみ

  • エンジニア以外との交流が少なくなってしまうのはすこし寂しい。
    • ”袖振り合うも多生の縁”、袖振りあってないしなー。とか。
    • 「また次回」が難しい
  • 事務処理
    • なぜ日本の役所関係各位は、物理的な紙の提出を求めるんですかね。
    • 印鑑文化、そろそろ捨てようぜ

感謝

なんだかんだと書きなぐってみましたが、フルリモート楽しくやってます。ぶっちゃけフルリモートが可能なのって周りのリテラシーとかも必要で、そういう意味では恵まれた 弊社です。だけど周りがそのリテラシーを持ってることに対して「理解してくれて感謝」するのはちょっと違う気がしていて、その辺のコミュ力だったり気持ちは必要なのかもしれません。嫁さんもフルリモート歴が長く、家での仕事のことや体調管理のことに理解があったのもバックグラウンドにあります。ちょっとしたことで人間は割りと簡単にストレスを感じてしまうので、家のなかに仕事を持ち込むどころか家の中を仕事場にするというのはそれなりに考え方をはっきりさせておくのが大事なように思います。 「フルリモートの心得」 みたいな本、もしかしたら需要あるのかも。


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ディレクティブを使うらしいです。