ボクココ

個人開発に関するテックブログ

拡張テキストエリアを ContentEditable で実現しよう

ども、@kimihom です。

今回は ContentEditable という知る人ぞ知る HTML5 で導入された HTML 属性についてご紹介する。

まず始めに、 大いなる力には大いなる責任が伴うということを伝えておこう。ContentEditable を本気でやろうとすると確実にどハマりし、長い開発期間を要することになる。そして本当にイメージする機能を実現できるかの保証はできない。しかし、その険しい道の行く末に、誰も経験したことのないような、拡張テキストエリアの UX を実現することができる!

ContentEditable で実装できること

まず ContentEditable の簡単な概要をご紹介する。最も一般的に ContentEditable が利用されているのは、おそらく Twitter だろう。公式 Web サイト上のテキストエリアに # の文字を入力してタグ付けをしてみよう。

f:id:cevid_cpp:20171002184710p:plain

ご覧の通り、テキストエリアなのにまず色付けが行われている。そもそも普通の <textarea> ではこれすらも実現不可能だ。また、 # を押した後にはタグの候補が表示されたり、対象のメンションをマウスオーバーするとユーザー情報が表示されたりする。

マークダウンとはなんだったのか。そんなふうに思ってしまうような拡張テキストエリアを、ContentEditable なら実現できるのである。

そして、ContentEditable の原理としては至ってシンプルだ。

<div contentEditable='true'></div>

なんとこれだけで編集可能なテキストエリアを実現できる。Twitter のテキストエリアの部分のHTMLソースを見てもらえばわかる通り、<textarea>ですらないのがポイントだ。CSS や JavaScript を駆使して、 Twitter のツイートエリアのような拡張テキストエリアを作り上げるのである。

ContentEditable を始めてみる

ContentEditable にワクワクした読者の皆様、ようこそ茨の道へ。これの実現には本当にたくさんの罠をくぐり抜ける必要がある。

そもそも、ContentEditable は よくあるブログの編集画面のような、ツールボタンをクリックして文字を太字にしたり色付けしたり画像を挿入したりといった使われ方を想定した設計になっている。これであれば、ContentEditable は容易に実現できる。

f:id:cevid_cpp:20171002185433p:plain

具体的には、 document.execCommand を呼び出すことで書式変更をする。つまり、書式変更ボタンをクリックしたら、document.execCommand('bold') といったようなコマンドを実行するだけで、書式変更のかかった文字を ContentEditable 内で入力させることができるようになる。

しかし、今回私たちが最終的に実現したいのは、Twitter のようなリアルタイムで変わるクールなインタフェースだ。これの実現は半端なく難しいので、チャレンジできる人だけ来て欲しい。

まず、 Twitter ライクな実装を実現するためには、個々のキー入力イベント "keydown" , "keyup" を補足し、その文字によって対応する処理を変えていく必要がある。「"スペース+@"の文字が入力されたら、候補となるユーザー一覧をテキストエリア下部にリスト表示する」 といった実装となる。ご想像の通り、あらゆる文字入力が考えられる中で、その全てのキー入力に対応して違和感を起こさないような実装が必要となってくる。単なる文字だけでなく、Enter, Space, 上下左右、Shift, Control とった文字まで、keydown, keyup イベントは全てを補足するので細かな実装とテストが必要だ。 一応記しておくと、keydownは文字入力される前に呼ばれるイベントで、keyup は文字入力された後に呼ばれるイベントだ。ContentEditable ではこのイベントの使い分けも非常に重要になる。

最終的に生成される文字列は、当然 HTML だ。改行なども<div>タグで表現されたり、書式は <span> で登録されたりする。そのためちゃんとしたテキストとしてデータベースに保存したいのなら、そのHTMLを解析して最終的に整形した文字列を保存しなければならない。そのまま保存して表示みたいなことをすれば、簡単にXSSの脆弱性を生み出してしまうことだろう。

また、キー入力イベントのハンドリングだけではなく、ContentEditable 内のカーソル移動も検討する余地がある。例えば自動入力でテキストエリアに文字を挿入した場合、カーソルはそのテキストエリアの前や後ろに持っていきたいなど、機能の特性によってカーソル位置を変えたいことが出てくる。そこで登場するのが、Range という概念。これも ContentEditable の実装をすると確実に必要になって来るのでドキュメントを見て学んでいただきたい。また、ExecCommand の一部が正しく動作しないとか普通に起こるので、替わりに Range を使って代用する方法など、数多くの地雷が待ち受けていることだろう。

やがてあらゆるハードルを乗り越えれば、リアルタイムに編集しながら自由に HTML を作り上げることができるという夢のような機能を実現できる。

そこまで苦労して ContentEditable を実現したいか。この点に関してはきっとサービス特性によって異なることだろう。

終わりに

私は ContentEditable の持つ魔の力を利用して、拡張テキストエリアを実現することができた。Twitter のタグを見ればわかる通り、普通ならタグ入力の別の検索ボックスのようなものを用意しなければならなかったのが、1つのテキストエリアで全てが表現できるようになった。ContentEditable はそこに価値を感じるか感じないかの世界だ。シンプルで柔軟性のあるサービスを本気で目指しているのなら、ContentEditable の実装を検討してみてはいかがだろうか。

大いなる力には大いなる責任が伴う。ContentEditable で拡張テキストエリアを実現するかしないかを検討してみていただきたい。

続編書きました。

www.bokukoko.info

Node.js Express で非同期処理を next で対応する方法

ども、@kimihom です。

今回は Node.js の Express を使った場合の非同期処理のスマートな対応方法をご紹介する。

Node.js の非同期処理の重要性

簡単な比較をすると、Ruby では非同期処理をほとんどしない代わりに、それぞれのリクエストの一連の処理がが終わるまでサーバーはそのために仕事をする。例えば、DB の読み書きの処理は ActiveRecord を使うが、そこで DB との接続をして処理が終わるまでサーバーの一つのリソース(プロセス)は占有されることになる。これはこれでシンプルなコーディングができるので良いのだけど、処理効率という観点ではあまりスマートな方法ではないと言われている。

Node.js でプログラムを書いていると、DB処理を含めたあらゆる処理が非同期となる。これによってより多くの処理を1つのサーバーで処理することが可能になる。しかし、JavaScript 特有の大量 function ネストが続くコールバック地獄が頻繁に発生する。

さて、Express ではこのコールバック地獄を防ぐために next という便利な引数が存在する。今回はこの next についてご紹介しよう。

使い方

まず Express の基本的なルーティング処理は以下のような感じだ。

router.get('/', function(req, res, next) {
  res.render('index');
});

"/" へアクセスが来たら、index の View をレンダリングせよという処理が書かれている。同じようにして、その下に router.get('/foo'... と書けば、/foo にアクセスした時の処理を書くことができる。んで、Express で書くときに意識したいのが、「リクエストが来たら、コードの上から順番にリクエスト処理がマッチするかを探しに行く」ということだ。そしてこれが next を使う上で大切となる。

早速、先ほどの index に非同期処理を噛ませてみよう。今回は JavaScript で定番の setTimeout でレスポンスを遅らせてみる。

router.get('/', function(req, res, next) {
  setTimeout(next, 3000);
  // setTimeout(function() { next(); }, 3000);  上と同じ
});

router.get('/', function(req, res, next) {
  res.render('index');
});

Express はリクエストが来たらコードが上から評価されるということを把握しておけば、"/" へアクセスが来たらまずは上のコードが実行され、next によってその次にマッチする下のコードが実行されるという流れを理解できるはずだ。

Express の app.js に 404500 の処理が最後にあるのはそのためである。最後の404の部分でマッチしてしまったら対応する定義が見つからなかったということで 404 をエラーを含めてnextに渡す。next の第一引数に エラーオブジェクトを入れておけば、他にマッチせず最終的に一番最後に定義されたエラーハンドリングの処理にマッチすることになる。

/// app.use ~~でルーティング定義

// 一番下に...
// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};
  console.error(err);

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

終わりに

今回は Express を使う上で大事なリクエストが来た際のコードが実行される順番について解説した。

next の仕組みを理解することが、Express のコード実行の流れを理解することに繋がってくるので、これから Express を触ろうと思っている方は是非マスターしておいていただきたい。

Express をうまく使いこなせれば、多くの処理をハンドリングする高性能な Web アプリケーションを作ることができるので、シンプルな Web アプリケーションは Express で作ってみても良いかもしれない。

Heroku の Papertrail でログからコードを実行する方法

ども、@kimihom です。

今回は Heroku アドオンの Papertrail の活用方法についてご紹介。

このアドオンが単なるブラウザ上で綺麗にログを見れるだけのアドオンかと思っていたら、それは Papertrail の 3分の1の魅力しか知っていないことになる。Papertrail の本当の威力を発揮するのは、Alert 機能だ。今回は、この Alert 機能を使って、ログから得られたデータを下に任意のコードを実行する方法についてご紹介する。

概要図

今回は特定のログが吐き出された時に、任意のコードを実行するような想定を考える。以下のような構成図だ。

f:id:cevid_cpp:20170608221458p:plain

“Heroku は AWS で動いているだろ” という 細かいツッコミは置いておいていただいて、AWS Lambda と API Gateway の部分は自前の AWS で用意する必要がある。なので今回はその流れについてもご案内する。

STEP 1. JSON のログを書き出す

まずは Papertrail のイベントをキャッチできるようにするために、アプリケーションから特定のログを出力する必要がある。JSON 形式で書き出すようにすると、あとで処理が楽なのでオススメだ。一応 Ruby (Rails) でのサンプルコードを書いておく。

user = User.find_by(params[:id])
log_info = {
  tag: "PTLog",
  name: user.name,
  id: user.id,
  email: user.email
}
logger.info(log_info.to_json.to_s)

ここで任意の “タグ” を用意することが肝心だ。この値を下に Papertrail の Alert として設定するようにする。

STEP 2. Papertrail で Webhook の設定を実施

さて、特定のログをHeroku から出力したら、いよいよ Papertrail の出番だ。Papertrail アドオンをインストールした後の手順をご紹介する。

f:id:cevid_cpp:20170608222342p:plain

  1. 出力した “タグ” にマッチするログだけを出力するために、 “PTLog” と入力し、Search をクリック
  2. Save Search をクリックし、検索を保存
  3. 名前をつけて Save & Setup an Alert

そしたら今度は Alert をセットする方法について選択する項目が出てくる。 Slack や HipChat を使っている場合は、このまま重要なログであれば通知すれば済むだけの話だけど、今回はコードを実行したいので、"Webhook" を選択。

f:id:cevid_cpp:20170608222838p:plain

Frequency に関してはしょっちゅう起きるイベントなら 1min、そうでなければ 10min とかお好みで問題ない。同じログが短期間で一気に来たとしても、 Papertrail は 配列として データを送ってくれるため、心配ご無用だ。

さて、肝心の Webhook の URL が今回のキモだ。

STEP 3. AWS Lambda と API Gateway のセットアップ

最近は便利になったもので、 AWS Lambda に色々とデフォルトで用意されたテンプレートがあるのでそれを利用しよう。

  1. ランタイムの選択で Node.js 6.10 を選択
  2. microservice-http-endpoint を選択
  3. API 名を適当に作り、セキュリティをオープンで Next
  4. 関数名と名前を適当に編集
  5. コードを下のように修正
  6. ロールは無ければ作って次へ
  7. 最終確認して作成ボタンをクリック

Node.js コードのサンプルは以下のようなイメージだ。

exports.handler = (event, context, callback) => {
  console.log('Received event:', JSON.stringify(event, null, 2));

  const done = (err, res) => callback(null, {
    statusCode: err ? '400' : '200',
    body: err ? err.message : JSON.stringify(res),
    headers: {
      'Content-Type': 'application/json'
    }
  });

  let body = event.body;
  let log = JSON.parse(decodeURIComponent(body.slice(8, body.length)));
  console.log(log);

  log.events.forEach((evt) => {
    let json = JSON.parse(evt.message.slice(0, evt.message.length - 1));
    // ログで出力した JSON が処理できる!
  });

  done();
}

ここで decodeURIComponent したり slice したりしているのは Papertrail が Webhook で書き出すデータを微調整するために行っているもので、きっと誰もが共通の処理をすることになるだろう。これで晴れて Heroku のログに出力した関数を AWS Lambda 内で好きに扱うことができるようになった!

おっと、最後に Papertrail の Webhook URL を API Gateway が生成した URL にセットするのをお忘れなく。 https://xxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/yyyyyyyy のような URL になってることかと思う。

あとは煮ても焼いても・・・。お好みに。きっと運用フェーズで例えば外部サービスの API を呼んだりすることで、データ連携や重要な通知をしたりといったことができるはずだ。

終わりに

運用フェーズではいかに効果的にデータを集積し、サービスの改善に生かせるかが課題になってくるかと思う。しかし、通常のアプリケーション内でそのような情報を外部に送るようなコードを書いてしまうと、そのぶんユーザーへのレスポンス速度が遅くなり、ユーザー体験を悪化させてしまう。そんな時はログからイベントをトリガとして発生させ、それに応じてコードを実行させるようにすることでユーザー体験を損ねることなくデータを蓄積していくことが可能だ。

最後に宣伝になるがこのような Heroku の開発/運用 Tips をシェアする Heroku Meetup があるので、よければ参加してほしい!今回は Heroku をガチで使っている方々からの運営事例と、Heroku 最新 Tips をお届けする予定だ。

herokujp.doorkeeper.jp f:id:cevid_cpp:20170608230018p:plain

WebRTC の Media, Stream, Track について

ども、@kimihom です。

最近の週末は Twilio Video を使ってビデオ通話アプリケーションを作成している。Twilio Video は今どんどん進化していて、単なる2,3 人でのビデオ通話をするにとどまらず、面白いことができるようになっている。特にスクリーンシェアの機能を Chorme 拡張機能を使うことで Twilio Video でもデスクトップ画面共有のアプリケーションが作れてしまう。ちなみにデスクトップ画面共有のドキュメントは1週間前に出たばかりなので、最新情報だ。

さて、この デスクトップ画面共有の概念を理解しようとすると、途端に WebRTC の Media, Stream, Track というそれぞれわかりづらい概念をそれなりに理解しなければならなくなる。今回は自分なりに勉強して理解したことをまとめようと思う。

Media, Stream, Track

最初に自分が理解した 図を貼り付けよう。少々見づらいかもしれないがこの点は勘弁してほしい。

f:id:cevid_cpp:20170305215612j:plain

今回は、ローカルのメディア、つまり自分のブラウザ環境だけに焦点を当てている。実際は LocalMedia での Track だけでなく、 Remote つまり相手の Track と合い重なってそれぞれのビデオ通話を可能にしている。

さて、まず全体を取り巻くのが Local Media だ。Media には複数の Stream を持つことができる。WebRTC といえばのメソッド getUserMedia で取ってこれるのは、デスクトップカメラとマイクの2つだ。 これらはそれぞれ VideoTrack, AudioTrack がある。てことで、大抵の WebRTC でのビデオ通話では、1つの Stream で 2つの Track を持った状態で相手と通信をすることになるだろう。

そしてその下、getUserScreen と書いたが、これが Chrome の画面共有などのメソッドとして仮に定義したものである。実際は Chrome 標準で作られているものではないので、Chrome 拡張機能 として getUserScreen を実装しなければならない。んで、getUserScreen を呼ぶと、もう一つの Stream が出来上がり、その中に VideoTrack が作られることになる。こんな感じで一つの Local Media に 2つの Stream が存在することも可能だ。

ここまで説明すれば、というより図を見た時点で Media, Stream, Track が理解できてのではないかと思う。ただ実際に Twilio Video のドキュメントを読んでみると、これらが複雑に絡み合ったようにそれぞれがそれぞれの参照を持つようになるので、何が何だかわからなくなる。こういう図を最初に見ておけば、混乱することもないかと思う。

Twilio Video でのサンプルコード

さて、ここまでくれば、Twilio Video のサンプルコードも理解できるだろう。 Twilio Video でのビデオの初期化はこんな感じで実装できる。

    _localMedia = new Twilio.Video.LocalMedia();
    Twilio.Video.getUserMedia().then(function(mediaStream){
      _localMedia.addStream(mediaStream);
      _localMedia.attach('#video-view');
    });

    getUserScreen(['window', 'screen', 'tab'], "your chrome app id").then(function(stream) {
      _localMedia.addStream(stream);
    }).catch(function(error) {
      console.error("Screen Capture is not instaled!");
    });
  }

2つの MediaStream を add して追加し、Media を div 要素に Attach することで その中に Video や Audio 要素が追加されるようになる。

それぞれ LocalMedia を addStream すると、先ほどの図のように Track ができあがるので、Media#event:trackAdded イベントが呼ばれる。これでそれぞれの Track を管理して、例えば停止したりミュートしたりの操作も可能になるだろう。

終わりに

今回は Twilio Video でデスクトップ共有ができるようになったので、 Media, Stream, Track の3つについて勉強し、学んだことをシェアした。もし誤りなどあれば、指摘してくれると私の勉強にもなるので大変嬉しい。

個人的に Twilio はこのビデオ分野が今アツいと思ってるので、今後も追っかけていく次第である。

Rails assets 内の JavaScript のメソッドを View 内から呼び出す方法

ども、@kimihom です。

Rails で開発していると、assets/javascripts 内で定義したメソッドを View 内に書かれた <script>タグから呼び出したい時がある。Rails は基本的に assets/javascript 内のコードを全てひとまとめにして一つのapplication.jsってのを作るから、特定のページのロード完了後に特定の JavaScript コードを呼び出したい時にちょっと困ることになる。そんな時は View 内に <script>タグを入れる必要が出てくるだろう。

View 内と Assets 内の JavaScript の混在による課題

だが、View と Assets 内に JavaScript を混在させるといろいろな問題が出てくる。そう簡単にはいかない問題だ。

外部ライブラリの読み込みの前にメソッドを呼んでしまう

jQuery のような JavaScript ライブラリは、より早く HTML を表示させるために <head>タグではなく、<body>の一番下に書くことがあると思う。そうなると、レイアウト内の yield の後に jQuery が読み込まれることになる。つまり、View 内でベタ書きした <script> タグの方がライブラリよりも先に呼ばれてしまうことになる。これはよろしくない。


app/views/layouts/application.html.erb

<html>
<head>
  <title>Sample App</title>
  <%= stylesheet_link_tag    'application', media: 'all' %>
  <%= csrf_meta_tags %>
</head>
<body>
   <%= yield %>
   <%= javascript_include_tag 'application' %>
</body>
</html>


app/views/users/index.html.erb

<div id="users">
  .....
</div>
<script>
  $("#users")....   // <- "$" が呼び出せない!!
</script>

ソースコードのカオス化

View 内に <script> タグをどんどん入れて行ってしまったり、ロジックをその中にベタ書きしてしまうと、assets/javascripts にある JavaScript ファイルとごちゃごちゃになってメンテナンス困難なソースコードが出来上がってしまう。

だからできれば assets/javascripts 内に JavaScript のコードを集約させたい。

View から Assets 内の JavaScript を呼び出せない

かといって、assets/javascripts 内で functions を定義しても、Rails の AssetPipeline による JavaScript の Minify によってメソッド名を指定することができない。

app/assets/javascripts/users.js

function setUser() { 
  ......
}


app/views/users/index.html.erb

<div id="users">
  .....
</div>
<script>
$(function() {
  setUser(); // <- "setUser" が呼び出せない!!
});
</script>

content_fortrigger によって解決しよう

さて、本記事のメインテーマである解決策を掲示しよう。まずは1点目の <script>タグの読み込み順について。これは content_for を使うことで解決できる。

app/views/layouts/application.html.erb

<html>
<head>
  <title>Sample App</title>
  <%= stylesheet_link_tag    'application', media: 'all' %>
  <%= csrf_meta_tags %>
</head>
<body>
   <%= yield %>
   <%= javascript_include_tag 'application' %>
   <%= yield :footer %>
</body>
</html>


app/views/users/index.html.erb

<div id="users">
  .....
</div>

<%= content_for :footer do %>
<script>
  $("#users")....   // <- "$" が呼び出せる!
</script>
<% end %>

これで、 jQuery が読み込まれた後に、 <script> タグを読み込ませることができる。

続いて JavaScript の Minify によって指定のメソッドが呼び出せない問題。これは trigger を定義してあげよう。

app/assets/javascripts/users.js

$(function() {
  $(document).on("users:loaded", "#users", function() {
      //.....
  });
});


app/views/users/index.html.erb

<div id="users">
  .....
</div>

<script>
<%= content_for :footer do %>
<script>
  $("#users").trigger("users:loaded");
</script>
<% end %>

trigger には引数を与えることもできるので、とても柔軟に View と Assets の JavaScript を通信することが可能だ。

Rails の content_for と jQuery の triggerによって、個別 View のロード時に特定の JavaScript を実行することができるようになった。

終わりに

trigger はいろいろと応用が利くとても便利な jQuery メソッドだ。より詳しく知りたい方は以下の記事も参照されるといいだろう。

今更ながら jQuery の trigger の魅力について語らせてもらう

Rails のレールに乗った美しいコーディングを目指していこう。

独学での Web プログラミング学習まとめ

ども、@kimihom です。

自分で何かサービスを出したいという方は多いかと思う。今回はそんな方へ最低限知らなければならないリストを上げようと思う。ターゲットとしては Web アプリケーションということにしたい。

UNIX コマンド

最初は黒い画面に慣れる必要がある。いつまでも Mac の Finder でダブルクリックしてるようではプログラミングの効率が悪すぎる。UNIX に関してひとまず知らなきゃいけないのはディレクトリ/ファイル操作閲覧やエディタ(Vim or Emacs)、各種プログラミングコードの実行といった基本的なことで十分だ。次第にリダイレクトやパイプとかを知っていくと、より効率的な操作ができるようになる。最初から一気にマスターする必要はないが、シェルスクリプトとかも書けるとさらに良い。興味の度合いに応じて UNIX を学んでみよう。

これらはとても基本となる操作であり、UNIXの基礎を知らないと、これから学ぶことに対してことごとく UNIX コマンドから調べなきゃいけなくなったりと地味にきつく挫折する原因にもなりうるので最初にがっつりやってみよう。

例えば以下のような本。読んでないけどおそらく正しいこと書いてると思う。

MacOS XユーザのためのUNIX入門―ターミナルから覗くUNIXの世界

Ruby

ひとまず Web アプリケーションなら Ruby でいいだろう。なぜならあらゆるドキュメントが整備されていて、いろいろな問題にあたってもネットにたくさん情報があり、そして何より書いていて楽しい言語だからだ。複雑でぐちゃぐちゃになるような言語(何とは言わないが)を使うと、プログラミング自体がつまらないものになってしまう。考えてみてほしい。これからあなたはプログラミングをすることになるが、そのコードと四六時中付き合わないといけない。 楽しく学べる Ruby がいいと私は思う。(ここら辺はいろいろと論争を生むところだからあまり突っ込みはしないでいただければ幸いだ)

Ruby でターミナル上で動く簡単なサンプルプログラムを書けるようになろう。ruby test.rb とかを実行して色々と学んでいくのだ。Ruby と UNIX は当然のことながら深く結びついているので、UNIX を深く知れば知るほど、Ruby とのつながりを感じて感動することもある。

これに関しては 2冊くらい読んでおいたほうがいいと思う。王道がこの2冊。

たのしいRuby 第5版

プログラミングRuby 1.9 −言語編−

後者は Ruby 1.9 とちょっと古いんだけど、Ruby 2.x でも十分動くと思う。ちょっと古くてもお勧めしたい本だ。

HTTP

HTTP プロトコルはあらかじめ知っておいてたほうがいい。これを勉強しないでいると、今後の Rails の勉強の時に意味わからないことがたくさん出てくる。本当に基本だけでいいので、さらっとだけでも本を読んでおくといいだろう。

特に GET/POST などの HTTP メソッド、200, 400 などの レスポンスコード、Cookie、セッション などは知っておいたほうがいい。これはWeb標準なので今後絶対に知らないといけない知識になってくる。

HTTPの教科書

HTML/CSS/JavaScript

Web ならこの基本を押さえておかないといけない。この3つに関してはいろいろな本が出回っているから、自分にあったのを読めばいいと思う。最近は HTML5 とか CSS3とかもでてきて情報が錯乱しつつあるので、ググりながら実力をつけるってのがちょっと難しくなってきてる感じもする。

こういうのは必要な時に必要なコードが書ければいいと思うので、辞典っぽいのを買って一通りさらっと読んで気になるのがあればコード書いてみてブラウザで確認するみたいなことをすればいいんじゃないかな。

例えば好きなサイトの完全コピーを HTML/CSS/JavaScript で作ってみよう。そこで「これどうやってやるんだろう?」ってのが出てくることかと思う。そんな時は Chrome Console を開いて、HTML要素やCSSを見て理解できるようになろう。

詳解HTML&CSS&JavaScript辞典 第6版

Web開発でよく使う、特に使えるChromeデベロッパー・ツールの機能 - Build Insider

jQuery, jQuery プラグイン

今でも大活躍の jQuery。初学者は jQuery を知ることは今でも有効だと思う。 DOM 操作によってページの要素が動的に切りけるような基本を身につけることが重要だ。最初は汚いコードでも構わないので、jQuery で色々と遊べるようになるとサービス作成の幅が広がると思う。また、それにあわせて ajax も早いうちに学んでおくと、なお良い。

jQuery プラグインでさらに自分の欲しい機能を早く実現できるような仕組みも学んでみよう。DOM操作ができれば、そのjQueryプラグインをさらに自分好みにカスタマイズできるようにもなるだろう。大事なのはとにかく自分で書いて JavaScript を好きになること。それこそがさらなる勉強の意欲と成長を生むからね。

jQuery レッスンブック jQuery2.X/1.X対応

データベース

Web アプリケーションを作るなら、データベースの知識は必須だ。今でも現役バリバリのリレーショナルデータベースを理解しよう。テーブル設計を正しく行い、SQL が発行できるようにならないといけない。

これも勉強してみればパズルみたいなもので案外面白いものだ。ビビらずに軽い気持ちでチャレンジしてみてほしい。

【改訂第3版】 SQLポケットリファレンス (POCKET REFERENCE)

まずは最低限の SQL さえかければいいんじゃないかな。今後ゼロからがっつり Web アプリケーションを作るならテーブル設計も正しくできるようにならないといけない。

Git, Github

Git はソースコードを管理するためのバージョン管理システムだ。あなたが書いたコードを前の状態に戻したかったり、他のメンバーと開発したコードを組み合わせたり、そんな作業を Git はしてくれる。これも絶対に学んでおかないといけない技術になってきている。

最初のうちはそこまで詳しく知る必要はない。add, commit, branch, checkout, push, fetch, merge, log, diff くらいで最初は十分だろう。

Gitが、おもしろいほどわかる基本の使い方33

この時点で自分の Github アカウントも作っておこう。

Ruby on Rails

ついにだ。やっとここで Ruby on Rails を学習できるようになる。ここまでの基礎がないと、Rails を学んでも自分の作りたいアプリは作ることはできない。決してここまでの勉強は無駄ではないので安心してほしい。

Ruby on Rails は今までの総まとめみたいなものだ。今まで学んだ知識をうまく組み合わせてくれるのが Ruby on Rails だと思ってもいいかもしれない。ここでも何冊か本を読んでしっかりとマスターしたいところだ。

RailsによるアジャイルWebアプリケーション開発 第4版

パーフェクト Ruby on Rails

Heroku

作ったアプリを公開するときにはサーバーが必要だ。 Heroku。この神がかったサービスをぜひ使おう。あなたの書いた Ruby on Rails コードが数行のコマンドを叩くだけで世界中に公開できる。こんな素晴らしいことが他にあろうか。

ここまでくると Qiita とかでたくさん記事が出回っているので、それらを読んでデプロイしてみよう。なぁに、ここまで勉強してきたあなたなら難しいことは何もない。一番簡単な記事はこれかな。

railsアプリを5分でherokuにデプロイする - Qiita

独自ドメインとか SSL とかそういうのもみんなネット上に情報はたくさんある。問題なくできるはずだ。

終わりに

ここまで学べば、ある程度自分の力で Web アプリケーションが作れるようになるだろう。ここから先の技術は、自分でどんどん作って壁にぶち当たり、その壁を突破していくことで体得し、成長することができる。

そのぶち当たった壁はあなたのブログに書き残していってほしい。あなたの貴重な時間の記録を残すという意味で重要だ。その情報が次なるプログラマーを助けるヒントになるだろう。

本気で Web アプリケーションを作りたいあなたなら、きっとここまで勉強してくれることだろう。ぜひこのレベルにまで来てほしい。そしていつかたくさんの人に使われる Web アプリケーションをあなたの手で作り上げていただきたい。その日が来るのを私は心待ちにしている。

Cookie とログインと remember_me のカラクリ

ども、@kimihom です。

Cookie のすごい基本的なところなんだけど、どハマりしたのでメモ。何時間もかけて原因を探って解決した先に、成長ってのはあるものだな。いい経験をした。

デスクトップアプリで発覚した問題

んでどういう話なのかっていうと、運営している Web サービスで、基本的にログインしたら使い続けられる機能がある。そこで、Electron を使ってデスクトップアプリ化をしようとしていた。

f:id:cevid_cpp:20161109232915p:plain

もともとシングルページっぽいアプリケーションだったので、デスクトップアプリ自体としてうまく機能していた(ログイン周りを除いては)。なぜかデスクトップアプリを起動するたびにログインしなければならない状態になっていた。毎回デスクトップアプリを閉じるたびにログアウトされてしまっているような感じになってしまっていて、相当不便な状態だった。

普段の Web アプリケーションとデスクトップアプリとでは何かが違う。何が違うのか。

デスクトップアプリの Cookie の管理

そもそも Electron はブラウザではないので Cookie に該当するようなことは自分で用意しないといけない。Electron での Cookie は、アプリのデータとして保存されている。 Mac の場合は ~/Library/Application\ Support/myapp-170c4a/Cookies。ここの情報を読み込んで、Web サーバーに Cookie を送っている。

これ自体は全く問題ないのだけど、どうやら Electron で Webアプリを埋め込む場合は、起動の度に別のセッションとしてリクエストが発生するっていう特徴があるようだ。これが今回ハマった原因。

普段私たちが使っているブラウザは、session_idとして Cookie に保存される。Cookie を閲覧するツールを見ると確認できる。

f:id:cevid_cpp:20161109232147p:plain

さて、ここで気になるのが セッションっていうチェック。Cookie には Session Cookie と Persistent Cookie の2つがあるらしく、Session Cookie を指定しているということのようだ。これにより、セッションごとに有効な Cookie であるということを示しているらしい。

Session Cookie はセッションごとにしか存在できない Cookie なので、割とすぐに有効でなくなるとお考えかもしれない。しかし、普通にブラウザを使っている場合は、ページをタブで開き続けていたりセッションを残している場合があったりして、毎回アクセスのたびにログインといった感覚ではなくなる。だから、あまり Session Cookie について意識しなくてもいいような感じになってしまっている。

だけど Electron を使うと、これが顕著に大切になってくる。Electron を起動するたびに Session は入れ替わっているので、Session Cookie として session_idにセッションを保存しただけではダメなのである。

Remember Me の役割

ここでログイン時にたまに見る Remember Me(ログインを保持する) のチェックだ。これは、Session が入れ替わるたびにログインしなおさなければならない問題を解決してくれる。これにチェックを入れると、以下のような Cookie が新しく付与されていることに気づく。

f:id:cevid_cpp:20161109232158p:plain

今度はセッションにチェックが入っていないことが確認できる。先ほどの session_id が異なってログアウト状態になったとしても、この remember_user_token が存在して正しいものであれば session_id を発行し直す的なことをしてくれるようだ。

Electron で Web アプリを埋め込む場合は、この処理が実装されていることが絶対条件になる。

これにより、無事 Electron でもセッションを保持することが可能になった。

終わりに

Cookie と Session ってのは割と Web アプリケーションの基本ではあるが、実装を詳しく見てみると新しい発見があるものだ。今作のブラウザではセッションを割と長く持つ傾向があって、あまり恩恵を感じない ログイン状態を保持するチェック。まさか Electron で埋め込む時に問題に直面するとは思わなかった。

何はともあれ、一つまた成長ができてよかったよかった。

クラウドを活用した WebRTC コールセンターの最前線

ども、@kimihom です。

掲題のタイトルで Open Cloud Innovation Festa 2016 で発表してきた。資料は以下。補足的な資料も含めたらめちゃ多くなってしまったw

speakerdeck.com

発表の裏話的な

今回は参加者がエンジニアだけじゃなくて一般の方も来るってことだったので、ごく一般的な内容を中心に解説していった。本記事で裏話的なことをちょいちょい書いていこうと思う。

まず、私と WebRTC は実はちょっと遠い存在だ。 WebRTC メインでプロダクトを1年以上運営しているが、WebRTC コアについて深く学び始めたのは最近のことだ。なぜなら、Twilio の ClientVideo を使えば、 WebRTC の知識はほとんどなくても利用可能だからである。Twilio を使えば、 WebRTC に関して知らなければならないことはほんの少しで、WebRTC の知識がちょっと必要になるのはデバッグの時くらいになる。

そんな折、私はもっと WebRTC について詳しくなりたいと思った。以下の書籍だけが日本語であったので購入して読んだ。

WebRTC ブラウザベースのP2P技術

WebRTC ブラウザベースのP2P技術

NAT越えや STUN/TURN、ICE など Twilio を使って開発していた時にあまりよく把握していなかったワードが解説されていて、WebRTC への理解が深まった。でも実際にゼロから WebRTC を使って何かを実装するにはやっぱりハードルが高そうな感じはする。WebRTC のコアは Web と言うよりネットワークの領域だ。もっとコアの部分を知っていかなければ、 WebRTC を完全に理解することなんてできない。奥の深い技術テーマだ。

私は WebRTC は本当にすごいと思っている(そうじゃなきゃ WebRTC サービス運営はしていない)。WebRTC のすごいところって、Webやスマホ、電話網、SIPフォン、その他デバイス が WebRTC を通じて直接繋がることだ。今まで接続が難しかったことが 全て共通化されてリアルタイムコミュニケーションの幅が広がる。私たちのサービスでは Web と 一般電話公衆網を WebRTC を通じて繋いでいる。今回の発表を通して、ビデオ通話だけが WebRTC じゃないよってところが伝わればよかったかな。

WebRTC とそのほかの HTML5

私たちが開発しているサービスは、音声通話を利用した WebRTC サービスだ。音声通話をしている時に、普通にページ遷移してしまうと通話が終了してしまう。だからこそ Ajax を通じてページ切り替えをしないといけないし、戻るボタンを動作させるために History API を用いて履歴管理をしなければならない。

電話の着信フローの設定をよりわかりやすく表現するために Canvas が必要だった。着信時にスマホ通知を実現するために ServiceWorker が必要だった。電話を保留しているときに他のオペレーターがそれに気づけるように WebSocket が必要だった。最近の HTML5 のあらゆる技術は、私たちのサービスをより使いやすくするために必要なものだった。だからこそ WebRTC 中心にサービスを作れば、必然と他の HTML5 技術が必要になり、自然と SPA っぽくなっていく。

実はスマホ(Android Chrome) でも WebRTC で電話の受発信ができる。これをどんどん突き詰めれば、ソフトウェアをインストールすることなく Web アプリとしてスマホを携帯電話として利用できるようになるだろう。私はそんな未来を予想している。

組み合わせの大切さ

最後のまとめで話したことである「組み合わせの大切さ」。何か固有の技術が出てきた時に、それだけで爆発的なヒットを生むサービスを作ることは難しい。 WebRTC の例で言えば、ビデオ通話だけできるようなサービスを作っても今の時代ユーザーに価値を与えることはできないだろう。

サービスはあらゆるものの組み合わせだ。WebRTC と他の何かを組み合わせ、今までのコミュニケーションを劇的に改善することができてユーザーに価値を認めてもらって初めてサービスは成功する。そこで必要なのは WebRTC 以外の引き出しだ。今流行りの技術を組み合わせる例で言えば、人工知能を組み合わせたり、ビッグデータを活用して戦略的に改善できる通話サービスを作ったり、IoT と組み合わせたり。。日頃からいろんな技術を触ってみて、自分の引き出しを増やすことが大事になる。

Web 1.0でインターネットが使えるようになってポータルサイトが出来上がり、より多くの負荷に耐えられるようになって Web 2.0でSNSが広がってきた。そして次の時代の基盤とも言える HTML5 の登場ってのは何らかのチャンスと言えるのではないだろうか。

終わりに

思っていることをダラダラと書いてまとまりのない感じになってしまったが、今回の登壇で伝えたかったことはそんな感じ。発表を聞いてくれた方、この記事を読んでくれた方に何か響くものがあれば幸いだ。

Rails の render で部分的に動的な HTML を生成する

ども、@kimihom です。

Rails で CSS フレームワークとかを使っていると、例えばモーダルウィンドウを出すためにヘッダやフッタで共通の HTML を使うことになるだろう。これらは大抵の場合、共通の UI となる。それでも当然、モーダル内の body の部分はそれぞれ別々の HTML を書けるようにしたい。そんな場合にどうしたらいいのかをご紹介しよう。

共通部分を 別 html で切り出そう

Rails といえば DRY(Don't Repeat Yourself)だ。同じコードをコピペするようなことはしてはならない。それは HTML でも同様のことだ。同じコードが出てくるようなら、まずは app/views/home/_modal.html.erbのように切り出そう。今回のHTML断片は Bulma を使ったモーダルの例。

modal.html.erb

<div class="modal <%= class_name %>">
  <div class="modal-background"></div>
  <div class="modal-card">
    <header class="modal-card-head">
      <p class="modal-card-title"><%= title %></p>
      <button class="delete"></button>
    </header>
    <section class="modal-card-body">
      <%= yield %>
    </section>
    <footer class="modal-card-foot">
      <a class="button is-primary action"><%= action_name %></a>
    </footer>
  </div>
</div>

そんで、呼び出し元の HTML を <%= render %> の引数とブロックで値とHTMLを引数として渡すことができる。

<%= render layout: "modal", locals: {
  class_name: "hello",
  title: "Hello World",
  action_name: "Agree"
} do %>
  <p> Are you agree? </p>
<% end %>

こうすれば、柔軟なモーダルがどんどんと量産することが可能だ。注意が必要なのは、renderlayout オプションで指定してあげること。こうしないとブロックを利用することができなかった。

あとは JavaScript 側で Modal を表示するような処理を書けばOKだ。

モーダル内容を JavaScript で動的に書き換える

まぁたいていの場合ってのはこのモーダル内容をさらに JavaScript で動的に表示したいということがあるだろう。 Rails の View 内でそれが完結できればいいが、 JavaScript 側の値で動的に表示したい場合は、modal 表示前のイベントをキャッチして、HTMLを書き換える必要がある。

  $(document).on("click", "#console-ctrl .open-modal", function() {
    var target = $(this).attr("data-modal");
    var attr = $(this).attr("data-modal-attribute");
    $target = $("#console-ctrl .modal." + target);
    $target.trigger("modal:before-open", attr);
    $target.addClass("is-active");
  });
  
  $(document).on("click", "#console-ctrl .modal .delete", function() {
    $("#console-ctrl .modal").removeClass("is-active");
  });

  $(document).on("modal:before-open", ".modal.hello", function(attr) {
     console.log("hello modal before open");
  });

こんな感じにしてあげれば、 モーダルを表示する HTML 側で共通で書くことができる。

<i class="fa fa-plus-circle add-person open-modal" data-modal="hello"></i>

このフォントをクリックすると、さっきの hello なモーダルが表示されるようになる。そしてその描画前に、modal:before-openのイベントが発火するようになる。

終わりに

特にCSSフレームワークとかを使っていると、共通な部分ってのはよく出てくる。これらをうまく共通化して、テンプレートとして保存するようにすればより美しく汎用性の高いコードを書くことが可能だ。

今回は jQuery の trigger を利用した。この trigger については以下の記事でご紹介しているので、もし知らなかった場合は調べておくと良いだろう。

今更ながら jQuery の trigger の魅力について語らせてもらう

Service Worker でスマホ Web 通知を実現する

ども、@kimihom です。

Service Worker といえば、最近出てきたバックグラウドでごにょごにょできて通知できるアレでしょ?程度にしか思ってなかったが、思いもよらず使う場面が出てきたのでメモ。本記事はおそらく Service Worker の中でも最もシンプルなサンプルコードの部類に入ると思う。

要件

Web Notification を スマホ Web でも実現したい。ページにとどまっている前提で、そのページで何かしらのイベントを発火した時に通知できればOK。ページにいない時にプッシュ通知を送るとかはしない。

Web Notification と スマホ Web

さて、普通の PC ブラウザでプッシュ通知を実現するのは、割と簡単にできる。以下のページから引用してきただけだが貼っておく。

Web Notifications を使用する - WebAPI | MDN

  if (Notification.permission === "granted") {
    var notification = new Notification("Hi there!");
  } else if (Notification.permission !== 'denied') {
    Notification.requestPermission(function (permission) {
      if (permission === "granted") {
        var notification = new Notification("Hi there!");
      }
    });
  }

PCのブラウザでは正しく通知が届くのだけども、これを スマホの Chrome とかで実行すると通知が出てこないんだよね。これを何とかしたいってのが今回の問題。

Service Worker を使う

Service Worker を使うことでスマホ Web でも同等のことが実現できた。

// 初期化
navigator.serviceWorker.register('/sw.js');

// 通知を送りたいタイミングで
navigator.serviceWorker.ready.then(function(registration) {
  registration.showNotification('CallConnect', {
    icon: '/icon.png',
    body: '通知です。'
  });
});

showNotification の細かいオプションはドキュメントを参照していただきたい。

んで/sw.js にはこんな感じの JavaScript をパブリックな場所に置いておこう。

self.onnotificationclick = function(event) {
  // close notification
  event.notification.close();

  event.waitUntil(
    clients.matchAll({includeUncontrolled: true}).then(function(clientList) {
      for (var i = 0; i < clientList.length; i++) {
        var client = clientList[i];
        if (client.url == "/" && 'focus' in client) {
          return client.focus(); 
        }
      }
    })
  );
};

これでスマホでの通知をタップしたら、その通知を消して通知を出したページに飛ぶことができる。(client.url == の部分は変える必要あるかも)

もちろんこれは通常の PC Web でも同じ動きになるので、統一したければ統一しても良いだろう。個人的には まだ出たばかり感の強い Service Worker の利用は最小限にとどめたいので スマホ Web だけ Service Worker を使って通知するようにした。

Debug Tips

一度 Service Worker にブラウザを登録すると、キャッシュ的な感じでページを更新しても 古い Service Worker の JavaScript のまんまだったりする。

その時は、chrome://serviceworker-internals/ を叩いて開き、そこにある該当の Service Worker を消すことで対応できる。

f:id:cevid_cpp:20160726212834p:plain

終わりに

Service Worker を動かすページにいる前提で通知を出すくらいだったら、手軽に Service Worker で スマホ通知を使えることをお分かりいただけたかと思う。

これがページにいないときにでもプッシュ通知を送るとかいう仕様にした途端に面倒なことが増えるので注意。てかページにいないのにプッシュ通知を送りまくるみたいなそんなページは滅びて欲しいので、それは知らなくていいことのように思える。

Service Worker をはじめとした最新の Web 技術は、モバイルアプリとモバイルウェブの垣根をなくしつつある。ゆくゆくは全ての モバイルアプリは HTML をベースとした Web アプリで置き換えられる日が来るのもそう遠くはないのかもしれない。

JavaScript SPA 周りの議論で出た私なりの答え

ども、@kimihom です。

ここ最近、ずっと React.js か jQuery かみたいな話題が定期的に持ち上がってきている。事実、私も最近の JS ライブラリの人気に疑問を呈した人物の一人である。この記事を書いたのも1年前か。

なんだかんだで SPA から jQuery に戻った話

そして、現在も jQuery(厳密に言えば jquery-rails) でアプリケーションを書いている。そしてその決断を特に後悔したことはない。本記事では、よく聞くReact とかに移りたい理由をもとに、私なりの回答を記すことにする。

jQuery の DOM 地獄から解放されたい

「jQuery で DOM 操作なんてもうしたくないんです」これこそ React や Angular を使う最大の理由であることが多いかと思う。確かに jQuery の DOM 機能は頑張ればいろいろなことができるので、無法地帯になりやすいという性質を持っている。

だが私が思うのは、 jQuery で複雑な DOM 操作なんてする必要がないということである。そもそもの発想として、JavaScript で状態管理をするという考え方自体に問題があるのではないか。状態を保っている根幹はサーバーにあるDBであり、それを無理にフロント側で持とうとするから大変なことになる。Web の特徴として、"常にオンラインであること前提でいい"というのがあると思う。オフライン対応の Webアプリケーションを作るという話はなしということにとりあえず同意していただいたとして、そうすれば常に最新情報が取ってこれる前提で物事を考えてよいことになる。

となると大活躍する Rails の View オプションは、remote: true だ。これを a タグや button タグ、 form タグなどで追加するだけで Ajax 化できる。さらにそのイベントは ajax:successajax:beforeSend, ajax:error などで結果をキャッチできる。

するとどのような実装ができるかというと、レンダリングは常にサーバーサイドで行うことができる。jQuery でやるべきことは、 Ajax でリクエストを送り、帰って来た HTML をそのまま html(response.body) と言った感じで描画すればいいのだ。そうすれば面倒なDOM操作なんて一切必要なく、 Rails を使っているのであれば html.erb で動的なレンダリングを実現できる。今回のサンプルは Turbolinks を使わない場合のパターンだが、 Turbolinks でもほぼ同等のことが可能だ。Turbolinks を使えばデフォルトで HistoryAPIも備わっているので、戻るボタンも簡単に実現できる。ちなみに私は自前で History API を使って実装した。

Rails を使っている人たちはぜひ一度、なぜ Rails は jQuery を今でも採用し続けているのか、なぜ Controller を生成した時にCSS, JS ファイルも一緒にできるのかを考えてみて欲しい。私なりの答えを書くとRails のレールに乗りながら SPA を作るなら、以下のようなコードになるのではないかと思っている。頭の中で書いただけなので動作保証はしないので注意。

- layout
<div id="base">
   <%= link_to "Ajax!", remote: true, class: "getUsers" %>
    <div class="wrapper"></div>
</div>

- controller
class UsersController < ApplicationController
  layout false

  def index
    @users = User.all
  end
end

- view
<div id="userCtrl">
  <ul>
    <% @users.each do |u| %>
        <li><%= u.name %></li>
     <% end %>
  </ul> 
</div>

- css
#userCtrl {
  # userCtrl だけに適用する CSS
}

- js
$(function() {
  $(document).on("ajax:success", '#userCtrl .getUsers', function(q, data, xhr){
    $("#base .wrapper").html(data);
  });
});

私はこの開発方法を見つけた時、実に美しいと感じた。それぞれの影響を最小限に抑えつつ、 Rails の View を完全に活用して jQuery としての SPA を実現できた、と思った。そして今もなおこの方法で DOM 操作をほとんど行わず、 Rails の View をうまく活用することで Rails のレールに乗っている。こうすれば Rails のレールに乗りながら SPA を簡単に作れる。DOM なんてのは一切出てこないし、フロントエンドでの状態管理も存在しない。複雑な JavaScript コードとはおさらばできる。

Rails で AssetPipeline を使わなかったり、 jQuery じゃなくて React-Rails みたいなものを使っている方は、上記の jquery-rails の使い方を知った上で そのような選択をしたのだろうか? 自ら進んで Ruby on Rails からの レールを外れるという選択をした方には、それなりの苦労が必ず発生する。 Rails の思想と反する行為をしているわけだからね。

スマホとかタブレット、IoT 機器とかと統一した APIを中心としたアーキテクチャにしたい

私もこの考え方がすごい好きだった。 Web ってのはあくまで端末の1つに過ぎず、 API がすべての中心となりサービスの根幹となるものであると考えていた。確かにその考え方だと上記方法は利用することができず、 SPA フレームワークを使わないといけない。

ただ、 Web とその他で圧倒的に違う点が一つある。

それが先ほど申し上げた「Webはオンラインであることが保証できている」という点である。だからこそ常に最新のHTMLコードは ブラウザ側でなくサーバー側で持っていられるようになるわけだ。これがスマホとかだとそうはいかないので、スマホ側でうまくアプリないDBとかに保存して描画したりする必要が出てきてしまう。

API に関して言えば、そんなに機能が複雑になることはないだろう。だから そこはそもそも Rails である必要もないだろうし、別で作るべきだと思っている。同時接続とか多くなるだろうし。

終わりに

今回の話は Rails に限った話であり、例えばそもそもバックエンドがシンプルな Node.js の Express などを使っているのであれば、 SPA を選択してフロントエンドゴリゴリで頑張るという考え方もあると思う。本記事はあくまで Rails にレールを乗って SPA を作るための提案の1つである。

AWS Lambda の Node.js で連続で外部APIを叩く作法

ども、@kimihom です。

今回は AWS Lambda における Node.js のコードの書き方について。

実装したいこと

例えば、id を複数持った配列があるとして、その配列を 一個一個 HTTP リクエストで叩きたい、ということがあるだろう。id単位でしかリソースを削除できないような API があった場合などは必ずそんな場面に出くわす。

これを AWS Lambda で実装するには、どうすれば良いだろうか。ここに AWS Lambda の落とし穴が潜んでいる。何も考えずに書くとこんな感じになるだろう。

var request = require('request');

exports.handler = function(event, context) {
  var host = "https://api.awesomeapp.com";
  var ids = [1,2,3,4];
  ids.forEach(function(val) {
    request.delete({
      uri: host + "/records/" + id,
      json: true
    }, function(err, response) {
      context.done(); //?
    });
  });
};

これだと当然、期待した動作にならないのはわかるだろう。 AWS Lambda は、最後まで処理を終えた時点で context.done() を呼ばなければならず、これだと処理の途中で呼ばれてしまう。じゃあと言って、コールバック地獄の Node.js コードを書いてしまったら負けだ。非同期前提の JavaScript で頭を悩ますことになる。さて、どうすべきか。

Async を使う

AWS Lambda のドキュメントにもあるが、Async を使ってこの問題を解消させよう。最初は特殊な書き方に手こずるかもしれないが、慣れればなんとかなる。

今回は、 async.mapSeries を利用してこの問題を解決する。

var request = require('request');
var async = require('async');

exports.handler = function(event, context) {
  var host = "https://api.awesomeapp.com";
  var ids = [1,2,3,4];

  async.mapSeries(ids, function(id, callback) {
    request.delete({
      uri: host + "/records/" + id,
      json: true
    }, callback);
  }, function(err, res) {
    context.done();
  });
};

mapSeriesは第一引数に連続処理させたいアイテムを指定する。そんで第二引数の function の id にそれぞれが渡るようになっている。 function の中で callback を非同期に呼ぶような処理を書き、すべての ids の配列ループが終わったら、 第三引数の function が呼ばれる。

このように書けば、すっきりと連続した 外部 API のリクエスト処理を AWS Lambda で動かすことができる!

あと たいていの Node.js ライブラリは、 コールバック引数に (error, response) の順番で入ってくることを意識しよう。この慣習を守ることで、 Async の処理をより簡潔に記述することができる。上記コードの callback もその仕組みを利用して省略して書くことができている。より冗長的に書くと、以下のように書くことも可能である。

    request.delete({
      uri: host + "/records/" + id,
      json: true
    },  function(err, res) {
        callback(err, res);
    });

async には、mapSeries の他に、シンプルなコールバック地獄を防ぐための waterfall も用意されている。 AWS Lambda を Node.js で扱うには、これらのメソッドを使いこなし、すべての非同期処理が完全に終わったタイミングで、 context.done() を呼ばなければならない。Asyncにはその他様々なコールバック操作の仕組みが用意されている。詳しくはドキュメントを参照していただきたい。

終わりに

普段 Node.js をあまり書かない人にとって AWS Lambda を使いこなすには、 Async のような非同期処理のライブラリの知識が必須とも言える。私も普段は Ruby でプログラムを書いているし、Webフロントエンドの JavaScript ではあまり複雑に非同期処理を書いたりしないため、なかなか慣れずに苦労した。本記事が AWS Lambda で Node.js を扱う方にとって有益な記事になれば幸いだ。

JavaScript で画像をリサイズする方法

ども、@kimihom です。

前回の記事で画像のリサイズはサーバーサイドでって話だったんだけど、調べてみると Canvas でリサイズまでできてしまうという衝撃の事実が判明し、それで簡単にリサイズを実装できてしまった。

またもや HTML5 の技術に驚かされることになったので、ここに記す。

JavaScript でリサイズするのが理想的である理由

まず何故 JavaScript 側でリサイズするのがいいのかというと、アップロード時のネットワーク負荷が劇的に下げられるからだ。サーバーサイドでリサイズするとなると、まずそのでかい画像をアップロードしなければならない。この時点で重い画像だとアップロードに数秒かかってしまう。

これを JavaScript、つまりフロントエンドでリサイズできれば、ネットワーク負荷を抑えてアップロードが可能だ。これも HTML5 から登場した Canvas のおかげではあるが、そのメリットを存分に享受しよう。

縦横比を合わせた画像のリサイズ

リサイズするならそのままの縦横比で小さくしたいところ。てことで以下のような感じで canvas タグを作って当てはめる感じでソースを書いていく。

  var resizeImage = function(base64image, callback) {
    const MIN_SIZE = 1000;
    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');
    var image = new Image();
    image.crossOrigin = "Anonymous";
    image.onload = function(event){
      var dstWidth, dstHeight;
      if (this.width > this.height) {
        dstWidth = MIN_SIZE;
        dstHeight = this.height * MIN_SIZE / this.width;
      } else {
        dstHeight = MIN_SIZE;
        dstWidth = this.width * MIN_SIZE / this.height;
      }
      canvas.width = dstWidth;
      canvas.height = dstHeight;
      ctx.drawImage(this, 0, 0, this.width, this.height, 0, 0, dstWidth, dstHeight);
      callback(canvas.toDataURL());
    };
    image.src = base64image;
  };

前回の記事で画像をクロップする方法を書いたが、できればそのクロップをする前にこの処理を書くことが望ましい。やはり画像がでかいとその分 クロップの処理にも時間がかかり、カクカクになってしまうことがあるためである。

てことで3記事連載で書いてきた画像の流れとしては、以下の通りとなる。

  1. 画像をドラッグアンドドロップで File オブジェクト取得
  2. File オブジェクトの content-type や ファイルサイズの検証
  3. File オブジェクトを readAsDataURL で Base64 エンコード
  4. Canvas を用いて画像のリサイズ
  5. cropper.js で画像の切り取り
  6. 画像を Blob 化
  7. S3 に画像をアップロード

割とこの手順を踏む必要のあるウェブアプリケーションは多いように思う。

この中では AWS-SDK, Canvas, File API などたくさんの技術を扱うが、それぞれしっかりと理解してコードを書いていこう。

関連記事

www.bokukoko.info

www.bokukoko.info

終わりに

最近の3連載で、HTML5による画像のアップロードを扱った。なかなか体系的にまとまっているドキュメントがなかったりするので、ファイル選択だけで満足してたようなウェブアプリケーションを所有している読者がいれば、この機会にドラッグアンドドロップやリサイズ、クロップなど学んでみてはいかがだろうか。

HTML5 Canvas

HTML5 Canvas

Amazon

画像の縦横比を合わせてアップロードするまで

ども、@kimihom です。

前回の記事で、画像ファイルのドラッグ&ドロップの手法を紹介した。これで File オブジェクトを取得することができる。

www.bokukoko.info

さて、今回はその持ってきた画像の縦横比を合わせてS3にアップロードしてみよう。

画像の縦横比の重要性

まぁ説明なんていらないと思うけど、Webの画像はできるだけ縦横比を合わせた方が扱いやすいことが多い。例えばアイコンであれば正方形の形が望まれるし、記事であれば長方形っぽい写真の方がいい。縦横比をしっかりと合わせないと統一感のないWebサイトとなってしまう。(縦横比を合わせなくても上手くやった Pinterest はやっぱすごい)

そんなこんなで縦横比を合わせるわけだが、それをサーバー側で処理するとなると、どの部分を切り取ればいいかユーザーが指定できない。ページ上(JavaScript)でどの部分を切り取ればいいのかを指定できるようにしたいところだ。

縦横比を合わせる処理(クロップ)を実現しよう

f:id:cevid_cpp:20160326203802p:plain

今回利用したのは Cropper っていう JS ライブラリ。かなりよく出来てた。具体的にどう使うのってのはGithubのドキュメントを読めばわかるけど一応紹介。

<div>
  <img id="image" src="picture.jpg">
</div>

$('#image').cropper({
  aspectRatio: 16 / 9,
  crop: function(e) {
    console.log(e);
  }
});

こんな感じ。 cropper を任意のタイミングで呼べば、divのエリアがクロップできるようになる。んでここで前回のファイルアップロード方式の際に問題が起きる。画像 src に URL を指定できないのだ。でもこれも Base64 エンコードを使えばうまくいく。Base64エンコードってのはまぁ基本的なことなんだけども、画像のようなバイナリを文字列で表現しちゃおうってもの。生画像より容量は大きくなるけど、文字列で表現できるのがGood。

では、早速 クロップした画像を取得できるようにし、S3にアップロードする手順に移っていこう。

クロップデータの取得

クロップデータは getCroppedCanvas を呼べば取得できる。今回はたまたま jcanvas を使っていたので、それを使ってのサンプル。jcanvas を使わないで実現する方法は調べていない。cropImage メソッドの引数にある file はファイルのドラッグ&ドロップでで取得した Fileオブジェクトだ。ちょっと長いけどこんな感じ。だいぶ自分の環境で動いている元のコードを修正しているので、動作保証はしない。

  var cropImage = function(file) {
    var fr = new FileReader();
    fr.onload = function(evt) {
      // evt.target.result に Base64エンコードされた画像が入る
      $(".crop-image").attr("src", evt.target.result);
      $('.crop-image').cropper({
        aspectRatio: 1,
        minContainerWidth: 360,
        minContainerHeight: 230,
        crop: function(e) {
          var base64img = $($('.crop-image').cropper('getCroppedCanvas')).getCanvasImage('png');
          uploadtoS3(base64img);
        }
      });
    };
    fr.readAsDataURL(file); // base64へ変換
  };

  var uploadtoS3 = function(base64img) {
    var s3 = AWS. // AWS SDK から S3 オブジェクト取得
    var filename = "";
    s3.putObject({
      'Key': filename,
      'ContentType': "image/png",
      'Body': toBlob(base64img),
      'ACL': "public-read"
    }, function(err, data){
      // after upload
    });
  };

  // base64 -> blob
  var toBlob = function(base64) {
    var bin = atob(base64.replace(/^.*,/, ''));
    var buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
        buffer[i] = bin.charCodeAt(i);
    }
    return new Blob([buffer.buffer], { type: 'image/png' });
  };

結構めんどいんだけど、ファイルオブジェクトのBlobを一旦 Base64 に変換して Cropper へ渡し、その結果(Base64)をまた Blob に戻すってやり方をやっている。 S3 へ Base64形式でアップロードしても、正しく画像を表示してくれなかったので、こんな対応になった。

ちなみに画像の圧縮に関してはどうするのか意見が分かれるところだ。一番手っ取り早いのは File オブジェクトからファイルサイズが取得できるので、例えば 3M バイト以上のファイルをアップロードできないようにすることがある。より丁寧にやるにはどんな画像でもアップロードできるようにして、サーバー側で圧縮してあげる方法もある。ただそれでもファイルサイズの限度を定めないと、とんでもないファイルをアップロードされる可能性もあるので注意しよう。私なら AWS Lambda を利用して圧縮する方法を選ぶ。これはググって調べてみてほしい。

終わりに

Web上の画像処理は割と難しいように見えるが、うまくライブラリを駆使するともろもろうまくいく。なかなかこうしたテクニックは体系的に解説されていないので、本記事が参考になれば幸いだ。

より良い画像を扱うWebサービスが増えることを祈る。

HTML5 時代のファイルアップロード方法

ども、@kimihom です。

今までファイルのアップロードといえば、 input type="file" な要素を作って、 multipart なフォームで送信、というやり方が一般的だった。が、最近はAWSやHTML5の登場により、よりクールで使いやすいファイルアップロードを実現できるようになっている。

今回はその方法をご紹介したい。

File API によるファイル選択とドラッグアンドドロップ

最近のウェブサービスなら一般的になりつつあるファイルのドラッグアンドドロップ。今後は むしろドラッグ&ドロップのないサイトは古臭いし使いにくい というような所まで一般的な方法となるだろう。最近は HTML5 の提供する API のおかげでとても簡単に実装できるようになっている。

ファイル選択とドラッグ&ドロップの両方でキーになってくるのが、Fileオブジェクト。どちらの手法でやっても最終的に得られるのが File オブジェクトになる。

まずはファイル選択から見ていこう。

ファイル選択で File オブジェクトを取得

ファイル選択は、一般的な input type="file" なフォームを作って、そのchangeイベントを取得すると、Fileオブジェクトを取得できる。

  $(document).on("change", ".image-field .file-upload", function(e) {
    var file = e.target.files[0];
    // file オブジェクトが取得できた
  });

ファイルを複数選択した場合は配列で files が格納されているので、1つ1つ処理していけばよいことになる。

ドラッグ&ドロップで File オブジェクトを取得

続いてドラッグ&ドロップ。drop をはじめとした見慣れないイベントを定義してあげる。 dragover はファイルが対象のエリアに入った時点で呼ばれるイベント、dragleaveはファイルが対象のエリアから外れた時点で呼ばれるイベントである。

  // body dragover
  $(document).on("dragover", "body", function(e) {
    e.preventDefault();
    $(".image-field .droparea").addClass("droppable");
  });
  // body draglave
  $(document).on("dragleave", "body", function(e) {
    e.preventDefault();
    $(".image-field .droparea").removeClass("droppable");
  });
  // imagefield dragover
  $(document).on("dragover", ".image-field .droparea", function(e) {
    e.preventDefault();
    $(".image-field .droparea").addClass("hover");
  });
  // imagefield dragleave
  $(document).on("dragleave", ".image-field .droparea", function(e) {
    e.preventDefault();
    $(".image-field .droparea").removeClass("hover");
  });
  // imagefield drop
  $(document).on("drop", ".image-field .droparea", function(e) {
    e.preventDefault();
    $(".image-field .droparea").removeClass("hover droppable");
    var file = e.originalEvent.dataTransfer.files[0];
    // file オブジェクトが取得できた
  });

たくさんイベントを定義したが、大事なのは一番最後の drop イベントだけだ。他は UI をよくするためだけのイベント定義となっている。なぜ body にも dragover, dragleave イベントを定義しているのかというと、後察知の通り画面に画像などを持ってきた時にここにドロップしてね!とより視覚的に分かりやすくするためである。もちろん Slack のように body 全体をドロップできるようにしてもよいだろう。

e.preventDefault() はとても重要だ。これを書かないと画像だけがブラウザに表示されるようになってしまう。

ファイルをアップロード

あとは Amazon S3 にファイルを直接アップロードしよう。アップロード方法の詳細に関しては、以下に記述してある。 Amazon Cognito を用いたファイルアップロード方法だ。

www.selfree.co.jp

より発展的な実装

それでは画像を縮小させる、クロップ(縦横比を合わせる)にはどうすればいいか。

縮小に関しては AWS Lambda を使って、 S3 にアップロードしたタイミングをトリガとして、AWS 側で縮小させる方法がある。これに関しては別記事に譲るとして、クロップに関しては今度書く記事でご紹介したいと思う。

あわせて読みたい

www.bokukoko.info

www.bokukoko.info

終わりに

HTML5のおかげでファイル操作が非常に簡単に行えるようになってきた。ファイルアップロードは File API を用いて実装することが一般的になってくるだろうから、ぜひここで基本を抑えておきたいところだ。