WebSocket利用事例 - 「かんたんMarkdown」の上書き保存

## 初めに
「かんたんMarkdown」の上書き保存を実現するのにWebSocketをつかったというお話。


「WebSocketをどのように実装するか」という記事は多いのですが「WebSocketをどのように使ったか」という記事は少ないので書いてみます。事例の一つという感じで何かの参考になれば。



## 背景
私は「かんたんMarkdown」というMarkdownエディタを作っています。HTML, JavaScript, CSSだけで実装されているので、編集もプレビューも基本的にたった一つのHTMLファイルですべてが完結するちょっと変わったエディタです。

> かんたんMarkdown
> http://tatesuke.github.io/KanTanMarkdown/

> 完全に単一のHTMLファイルで動作するMarkdownエディタ作った
> http://qiita.com/tatesuke/items/225b51b270faf8b10923

自分でいうのもなんですが、それなりに便利なエディタで、私自身もそれなりに快適に使っていました。

しかし、かんたんMarkdownには大きな欠点が一つありました。それはエディタのくせに「**上書き保存ができない**」ことです。

ブラウザ上で動くjsからはローカルファイルの読み書きは基本的にできません。できるのは「名前を付けて保存」のみです。ですので、保存は毎回ファイルダイアログを表示させてファイル選択をしなければならず地味に面倒でした。

「使い物にならない」というほどではないとしても、Ctrl+Sによる上書き保存ができるともっと便利になるのにと常々思っていました。


## WebSocketをどう使ったか
結論から言えば、タイトル通りなのですが、上書き保存を実現するのにWebSocketを使いました。以下の構成です。

1. 上書き保存を利用する端末にローカルAppをインストールしておく
1. ブラウザ上のjsからローカルAppにWebSocketでデータを送信する
1. ローカルAppがローカルファイルにデータを書き込む
1. ローカルAppがブラウザ上のjsにWebSocketで結果を返す

このブラウザ上のjsとローカルApp間の通信（②と④）にWebSocketを使いました。

![ktmsaver.png](https://qiita-image-store.s3.amazonaws.com/0/29682/9555d1f5-4cc5-c111-bc2c-6f648e6fed6e.png)



## どうしてWebSocketを使ったのか
WebSocketを採用した理由は以下です。

### クロスドメインの問題がない
最初は「Ajax」や「iframeを使ったフォームの送信」等のHTTP通信で実現しようと考えていました。しかし、Ajaxはクロスドメインの制約によってデータの送信ができませんので不採用にしました。「iframeを使ったフォームの送信」はデータの送信はできましたが、「file:///」のスキーマからだとデータの受信ができませんでした。

### 早い
最悪、データの送信だけできればいいと思い「iframeを使ったフォームの送信」を採用しかけたのですが、大きなデータの送受信が非常に遅いことが分かりました。同一端末上でのHTTP通信なのに数十MBのファイルの送信に十数秒かかりました[^1]。

[^1]: 特にチューニングはしていない状態での話です

そこで、試しにWebSocketで大きなデータを送信してみたところ、特にチューニングもしてないのに1秒もかからず通信が完了したのです。

### 実績がある
端末内で完結する通信なんてWebSocketの使いどころとして変かとも思いましたが、1Passwordのブラウザ拡張も似たような仕組みで動いていることが分かりました[^2]。
[^2]: 調べると脆弱性の話ばかりですが・・・

### 単純に挑戦してみたかった
WebSocketはしばらく前から話題になっていましたが触ったことはありませんでした。面白そうではあったのでせっかくの機会に触ってみようと思ったのです。


## 実装言語
WebSocketクライアントは「かんたんMarkdown」なのでJavaScriptで、ローカルAppはJavaを使いました。

Javaを採用したのは次の理由からです。

* 単純に私が書き慣れている
* WebSocketを扱う標準規格(JSR-356)とその実装がある
* ファイル保存ダイアログを表示できる（これはアプリ要件）

最初はNode.jsでWebSocket.ioというライブラリを使っていたのですが、上記3番目の要件があったため、途中でJavaに切り替えました[^3]。

[^3]: ちなみにWebSocket.ioはとても使いやすく、Node初心者の私でも1時間くらいでやりたいことは大体実装できました

ところで、JavaでWebSocketというとJavaEEを想像する方もいると思いますが、使ったのはJavaSEです。ローカルAppという性質上、わざわざコンテナを立てたりするのは面倒だったのでJavaSE使えるのはありがたかったです。

JavaSEでの実装方法は以下のページが参考になりました。

> 裏紙 - Java SEでWebSocketサーバを立てて遊ぶ  
> http://backpaper0.github.io/2013/07/14/websocket.html

と紹介しておきながらなんですが、最終的に私は上記ページで説明されているTyrusでなくJettyを使いました。

> TyrusのStandalone Modeは0.0.0.0以外をListenすることはできない
> http://qiita.com/tatesuke/items/b1bc263272a393cc6ec2

> JavaSEでJettyを使ってWebSocket(JSR-356)サーバーを立てた
> http://qiita.com/tatesuke/items/bd6b23053aa5629f8434


## 困ったところ
時間が解決してくれることかもしれませんが、やはり現時点では情報が少なくて困りました。これに尽きます。自分のWebSocketの知識のなさと相まって、情報を仕入れるのは少々大変でした。

「メッセージの送信はこうやるよ」というような入門的な記事はたくさんありましたが、それをつかってある程度実践的なアプリの作り方を解説しているサイトは少なかったです。オブジェクトのライフサイクルを解説したり、チューニングのノウハウを説明したりする日本語記事はほとんど皆無でした。

一番困っているのは「これでいいのかな？」という不安感がぬぐえないことです。サンプルやAPIの説明を見れば試行錯誤でそれっぽい動きをするプログラムは作れるのですが、実装の良し悪しを判断する術がありません（勉強不足なだけという説もある）。


## 終わりに
WebSocketを使ってみて、まだまだ発展途上なんだな強く感じました。リアルタイムの双方向通信がこんなに簡単にできるなんてすごいなと確かに思いましが、一方で、使いどころがいまひとつピンとこないというのが正直なところです（サンプルとして挙げられるのはチャットばかりだし）。もっともっと多くの使用事例が出てきて、使いどころ、面白い使い方、ノウハウなどが共有されればいいなぁと思いました。

ということで、その共有の一環として、私は「かんたんMarkdown」の上書き保存機能にWebSocketを使ってみたというお話でした。


ちなみに上書き保存機能のソースはgithubにあります。

> かんたんMarkdownのソース(tatesuke/KanTanMarkdown)
> https://github.com/tatesuke/KanTanMarkdown

> 上書き保存のソース(WebSocket使っているのはこっち)(tatesuke/ktmsaver)
> https://github.com/tatesuke/ktmsaver