Block Rockin’ Codes

back with another one of those block rockin' codes

「次世代 Web カンファレンス」を開催します

Intro

2015/10/18(日) に、「次世代 Web カンファレンス」を開催します。

  • 名称: 次世代 Web カンファレンス
  • 日時: 2015/10/18(日)
  • 場所: 法政大学
  • hash: #nextwebconf
  • 公式: connpass
  • 参加費: 無料

Motivation

「Web について話す場」

というか「Web そのものをテーマにした場」というのが、意外と少ないなとずっと思っていました。 (もちろん、結果として Web について話しているカンファレンスや勉強会はたくさんありますが。)

そして、スライドなどを用いて何かを「発表する」ニュアンスではなく、進化の早い Web で「今何が起こっているか?」と「これからどうなっていくのか?」という、答えの無いもの、でもみんなが気になり考えていること、今だからこそ考えないといけないことを真っ向から議論する場というのが、もっとあっても良いのではないかと考えていました。

今回開催するカンファレンスは、この「議論」だけからなるものです、それ以外のことはしません。 この趣旨に賛同してくださった、各分野のプロフェッショナルに協力頂き、「次世代 Web カンファレンス」として、開催させていただくことになりました。

やること

やることはシンプルです。

テーマ毎に、今 Web がどうなっていてこれからどうなっていくのか をひたすら議論するセッションのみのカンファレンス

やらないこと

以下はこのカンファレンスではやりません。

  • スポンサー募集
  • Tシャツ/ステッカー/チラシ作成
  • 公式サイト作成/ドメイン取得
  • 公式懇親会
  • LT大会/ドラ
  • 基調講演
  • メディア連携/対応
  • 何かの宣伝
  • お金を扱うこと全て

セッションは録画し後日公開します。配信はベストエフォートで実施します。

セッション

  • テーマ毎に 60~80 分のセッションを行う(調整中)
  • 3 トラック並行で実施
  • セッションオーナー(まとめ役) + 2~3 人の登壇者で実施
  • スライドやデモ・台本などは用意しない
  • プロジェクタは固定スライドを表示
  • 資料が無いので、資料公開はない。代わりにセッション自体の配信、録画を公開。
  • 登壇者の発言は、所属する組織とは関係なく、個人の発言である」を全体共通とする。

登壇者にお願いしてる事

全ての登壇者に以下をお願いしています。

  • 何かを宣伝する目的で出ないでください
  • 登壇者の発言は、所属する組織・団体とは関係なく、個人の発言である」を前提としてください
  • (初心者など)聞き手のレベルを意識せず、話したいレベルで話してください
  • 録画を公開させてください(声だけも可)
  • 資料やデモなどの準備はいりません
  • 細かく打ち合わせしすぎると、当日話す事がなくなり失敗するので、打ち合わせしすぎないでください
  • ひたすら日頃思っている事を議論してください
  • 登壇者にスタッフも兼ねてもらいます
  • 出演費・移動費など、お金は一切出ません
  • 実施形態に納得できない場合は出演を断ってください

予定しているセッション

今決まっているセッションのみ、テーマ名は仮です。

theme owner speaker
serverside_arch @ryushi @kuenishi, @yugui, @making
frontend_app @hokaccha @koba04, @armorik83, @laco0416
client_perf @summerwind @_furoshiki, @ahomu
server_perf @mirakui @xcir, @cubicdaiya
monitoring @songmu @fujiwara, @mikeda
js_next @teppeis @t_wada, @constellation, @vvakame
css @cssradar @hiloki, @morishitter_
security @hasegawayosuke @nishimunea, @kinugawamasato
webrtc @iwashi86 @voluntas, @tonofo
http2 @jxck_ @tatsuhiro_t, @jovi0608, @kazuho
authZ/authN @mad_p @nov, @kura_lab, (TBD)
standardization @myakura @azu_re, @lef, @ourmaninjapan

その他詳細は、募集開始時に Connpass に掲載します。

Jxck

HTTP2 の RFC7540 が公開されました

Intro

今朝、ついにずっと策定作業が行われていた HTTP/1.1 の後継仕様である HTTP2 と、 関連仕様である HPACK が、 RFC として公開されました。

それぞれ番号は 7540 と 7541 になります。

ちなみに HTTP/2.0 ではなく HTTP/2 が正式名称です。(マイナーバージョンアップでの HTTP/2.1 などはありません)

二年半

HTTP2 の前進である SPDY のコピーとして始まったのが 2012年11月28日。 このブログで最初のドラフトを紹介したのは 2012年12月でした。

次世代規格 HTTP2.0 のファーストドラフト公開 - Block Rockin’ Codes

もうそれから二年半くらい経つのですね。。あっと言う間でした。 そっから #http2Study という勉強会を主催したり、ブログも結構書きましたね。

自分で実装してみたりもしました、仕様を網羅するには全然足りてませんが。 これが Go を始めるきっかけでしたね。

けっこう色々やったなぁ。

http2study

日本にはかなりのガチ勢が集まっており、結果として日本のコミュニティの貢献は RFC の最後に以下の一文を載せていただくまでになりました。

The Japanese HTTP/2 community provided invaluable contributions,
including a number of implementations as well as numerous technical
and editorial contributions.

本当に嬉しいですね。

これから

HTTP2 の RFC 化は終わった訳ですが、これは HTTP2 にとってはむしろ「やっと始まった」ということです。 これから、実際に HTTP2 を使う上でまだまだ足りてないものは沢山有ります。

  • HTTP/1.1 からの移行
  • パフォーマンスチューニング
  • セキュリティ
  • 負荷分散構成
  • 実運用ノウハウ
  • gRPC のような使い方
  • etc

(先日あった Push に関する議論 も、実際にデプロイして検証していく必要がありますし。)

http2study も、 HTTP2 の「仕様を議論する」という最初のフェーズを終えて、第二フェーズとしてこれから「どうやって使っていくか」という話や、デプロイ・運用ノウハウの共有などに移っていければと思っています。

このへんは実に一ヶ月早く世界に先駆けて勝手に実施した RFC 発行祝賀会 でも話しました。

www.slideshare.net

もちろん、 WebSocket over HTTP2 や HTTP/3 の議論が始まれば、それも追って行きたいと思います。

Outro

HTTP2 各位、本当にお疲れさまでした。ここまでの 2 年半本当に楽しかったし、勉強になりました。

これからもコレを使いこなす/使い切るために、色々議論していきましょう!

HTTP2 時代のサーバサイドアーキテクチャ考察

update

色々と twitter で議論が起こったのでまとめて貼っておきます。

togetter.com

みなさんありがとうございました。

intro

HTTP2 の RFC 化も目前ということで、そろそろ実際に HTTP2 を導入していくにあたってサーバサイドの構成についても、具体的にどう変わっていくかという点を考え始めていく必要があります。

そんな話を @koichik さんとしていたら、色々と考えが膨らんだのでメモしておきます。

前提

今回は、中規模のサービスを想定し、特に HTTP2 のサーバプッシュを踏まえた上でのコンテンツ配信などに、どういう構成が考えられるかを考えていきます。

また、本エントリ内では独自に以下の表記を採用します。

  • HTTP/1.1 = HTTP/1.1 (平文)
  • HTTP/2 = HTTP/2 (平文)
  • HTTPS/1.1 = HTTP/1.1 over TLS
  • HTTPS/2 = HTTP/2 over TLS

HTTPS/1.1 での構成

まず、ベースとして HTTPS/1.1 でコンテンツ配信をしていた場合のサーバ構成を復習します。 小さななサービスであれば別ですが、ある程度のサービスであれば前段に Nginx などのサーバを Reverse Proxy として立て、その後ろにアプリケーションサーバの層がある構成が標準的でしょう。 なお、 Proxy よりも後ろはデータセンタ内の高速なネットワークを想定します。

この場合 Proxy の層は以下のような責務を担います。

  • TLS の終端
  • IP ベースのフィルタリング
  • コンテンツの gzip 圧縮
  • ヘッダの追加/削除
  • 静的ファイルの配信(ELB のようにしない場合もある)
  • etc

重要なのは、 URL が https: なリクエストが来た場合、クライアントから Proxy までは HTTPS/1.1 ですが、 Proxy とアプリケーションサーバは HTTP/1.1 で通信する点です。

C --- https/1.1 ---> P --- http/1.1 ---> S

HTTPS/2 での構成

最近では Proxy のポジションは Nginx が多いと思いますが、まだ HTTP/2 には対応していません。 HTTP2 に対応するには H2O や nghttp2 などがありますが、やはり Nginx と比べると実装の枯れ具合の部分もあるし、 すでに Nginx 使ってればそのまま行きたいという気持ちも有ります。

Nginx は 2015 年中には HTTP2 を実装するというロードマップがあり、 そこでは、 Nginx が nghttp2 の proxy 実装である nghttpx 相当の機能を実装するであろうと予想されるので、 今回は、先取りして Nginx にその辺の機能が実装された前提で考えます。

また HTTP2 の場合は、現在のブラウザの実装状況がこのまま続くと考えると、おのずとクライアントとの間は HTTPS/2 が前提になっていくでしょう。 したがって、サービスは全体的に HTTPS/2 になっていくと予想されます。

その場合も、これまで同様 TLS の終端は Nginx で行うことになるでしょう。

C --- https/2 ---> N --- ??? ---> S

では、そこからアプリケーションサーバの間をどうするのか。

大まかに二つが考えられます。

HTTP/1.1 で中継

Nginx で TLS と HTTP/2 を両方解き、 HTTP/1.1 にしてしまうという考え方です。

C --- https/2 ---> N --- http/1.1 ---> S

この方式は、既存のアプリケーションに手を入れる必要が無く、新規に作るとしても言語自体が HTTP2 の実装を持っている必要がありません。 HTTP2 の持つネットワーク上の最適化は Nginx の層で実施することでインターネット側で発揮され、高速なデータセンタ内では、従来と互換という移行しやすい方式かと思います。

HTTP/1.1 にしてしまうと、例えばサーバプッシュといった新しい機能が使えないように思えるかもしれませんが、 resouce hints という仕様を使うと、 Link ヘッダというヘッダを追加することでプッシュしたいリソースを Proxy に知らせることができます。

例えばアプリが index.html のレスポンスの前に style.css を Push して欲しい場合は index.html のレスポンスヘッダに以下のヘッダを入れます。

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Link: /style.css; rel=preload
...

すると Proxy は style.css を探して Push してくれるというものです。

この機能はすでに nghttpxh2o には実装されているため、 Nginx にも実装されることが期待できます。

HTTP/2 で中継

Nginx では TLS だけ解き、 HTTP/2 で中継するという考え方です。

C --- https/2 ---> N --- http/2 ---> S

この方式は、アプリケーションの層が HTTP/2 の平文実装に対応する必要があります。 データセンタ内であるため HTTP/2 のメリットはネットワーク最適化というよりは、サーバプッシュなどをよりアグレッシブにアプリケーションサーバから使いたい場合に取る戦略かと思います。

前述の HTTP/1.1 に解いた場合は、 Nginx に Push を伝える方法が Link ヘッダに限られてしまいます。 この場合、例えば時間のかかるリクエストの処理をしているが、結果がどうなろうと(status code が決まる前に)先に Push しておきたいコンテンツがあると言った場合に対応できません。 (Nginx にはレスポンスヘッダで伝えないといけないが、 status code が決まらないとそもそもレスポンスを作れないから)

アプリが直接 HTTP/2 を話せると、アプリから直接コンテンツを Push できるインタフェースを叩ける可能性があるので、よりアグレッシブなプッシュができるでしょう。

また、 gRPC など HTTP/2 の機能をよりふんだんに使った構成を考える場合も、こちらを選ぶことになると思います。

HTTPS/2 で中継?

ところが、 HTTP/2 はもともとおまけのような扱いで始まってしまったきらいがあり、若干軽視されている感があります。 特に言語での実装はどのくらいこなれて行くのか若干不安があります。

すると素直に HTTPS/2 のまま通せば良いのでは?という発想もあるかもしれませんが、あまり現実的とは思えません。

TLS の終端をしないと、アプリごとの証明書管理が必要になったり、アプリ側で OpenSSL のバージョンがバラけていたりした場合は、バグが出た時に対応が難しくなるなどの運用面での懸念があります。

なによりデータセンタ内ではセキュリティは IPSec や MACSec などによる対応が考えられるため、 TLS が必須ではないでしょう。

まとめ

結論として以下のようになるのかなと考えます。

  • 互換性を重視し Resouce Hints レベルで Push する: http/1.1 Forward
  • 積極的に Push する: http/2 Forward

こう考えると、当初「HTTP/2 を HTTPS だけにしない」という理由から付け加えられたような HTTP/2 の平文仕様でしたが、実は結構大事だったことに今更ながら気がついたということです。

特にブラウザはほとんど HTTPS/2 しか実装してないし、する気もなさそうですが、サーバサイドや Proxy を作るような場合は、これの変更考慮して HTTP2 の平文実装もぜひ無視しないであげてください。

また、特に今回書いたような中~大(not 超大)規模くらいのサービスを運用されている方々からのコメントをお待ちしています。

HTTPS 化する Web をどう考えるか

Update

  • 2015/5/8: 指摘頂いたタイポや誤訳などを更新しました。
  • 2015/5/8: 構成を一部修正しました。

Intro

4/30 mozaiila のセキュリティブログに下記のようなエントリが投稿されました。

Deprecating Non-Secure HTTP | Mozilla Security Blog

エントリはそこまで長くないので、ここに翻訳の全文を記載します。 そして、元エントリのライセンスである CC BY-SA 3.0 に則り、 本エントリも同じく CC BY-SA 3.0 とします。

Deprecating Non-Secure HTTP

原文: Deprecating Non-Secure HTTP

今日は、 non-secure な HTTP から、徐々に廃止していくという方針についてアナウンスします。

HTTPS が Web を前進させる手段であるという点は、広く同意が得られています。 ここ数ヶ月でも、 IETF, IAB (こっちの IAB でも), W3C, そして アメリカ政府 においても、 インターネットのアプリケーションでは、暗号化通信を広く使おうという声明が出ています。 Web について言えば、それは HTTPS です。

コミュニティ ML での活発な議論を踏まえ、 Mozilla はセキュアな Web の開発に新しい開発リソースを投入し、セキュアでない Web から機能を取り除き始めていくという合意にいたりました。 この計画は、大きく二つの要素から成ります。

  1. 期限となる日を決め、その日以降はセキュアな Web サイトでしか、すべての新しい機能が使えなくします。
  2. 特に、ユーザにとってセキュリティやプライバシー上のリスクが発生する可能性がある機能については、徐々にセキュアではない Web からはアクセスできないようにしていきます。

最初のステップとしては、この期限をコミュニティで決める必要があります。そして、どれが「新しい」機能なのかを定義する必要があります。 例えば、「新しい」を「Polyfill できない機能」と定義するなどが考えられます。 これなら、 CSS や他のレンダリングの機能は、ページ上で (<canvas> などを使って) 再現することで、引き続きセキュアではない Web でも使うことができます。 しかし、例えばハードウェアの機能にアクセスするようなものは、制限されます。

このプランの二つ目の要素は、セキュリティと互換性のトレードオフを踏まえて進む必要があります。セキュアでない Web から機能を削れば、壊れるサイトも出るかもしれません。 そのため、私たちはその破損具合をモニタし、セキュリティ的なメリットとのバランスを取る必要があります。 同様に私たちは既に、セキュアではないサイトから使われる機能のより緩い制限についても考えています。 例えば、 Firefox はすでにカメラやマイクへのアクセス権限の恒久的な取得を、セキュアでない Web には許していません。(訳注: http:// だと毎回聞かれるという意味) セキュアではない Cookie のスコープについても制限する提案がなされています。

注意して頂きたいのは、このプランは URI スキーマとしての "http" が古いコンテンツでも使えなくなるという意味ではないことです。 HSTS や CSP の upgrade-insecure-requests 属性を使うことで、 http:// スキーマがブラウザによって自動で https:// と解釈され、セキュアになります。

この取り組みのゴールは、 Web 開発者コミュニティに、 Web をセキュアにするべきだというメッセージを送ることであり、この取り組みは Web のコミュニティと強調して進めることで最も成果が得られます。 私たちは、まもなくW3C の WebAppSec Working Groupに対して何らかの提案を作成するつもりです。

この ML で議論に参加してくれた多くの人に感謝します。 Web をよりセキュアにしましょう!

Richard Barnes, Firefox Security Lead

要するにどういうことか

簡単に言えば、「これからの Web を http:// を使わず https:// を中心としたセキュアな世界にしていこう」という提案です。

これまでも、それを実現するためも「呼びかけ」というレベルでは MozillaGoogle 含め多くの人が多くの場所で行ってきました。 しかし、 Web にはすでに HTTP で公開されたサービスが多く有り、これは徐々にしか移行していくことができません。

そこで、「これから公開する Web については、極力 HTTPS にする」という状態を促すために、「これから Web に入る新しい機能は、 HTTPS のサイトじゃないと使えなくする」ようにすることで、開発者にHTTPS への移行を促すということです。

そのための作業として、まず以下があります。

  • それをいつからやるか?
  • 新しい機能ってどれのことか?

より詳細なプランはここに書かれています。

Insecure HTTP Deprecation Plan

何がおこっているのか?

言うまでもありませんが、 Web が一般化し、その使われ方も多岐にわたるようになりました。 しかも家で PC を開くときだけでなく、普段持ち歩く様々な端末が Web につながり、 その上で SNS やメールや金融取引まであらゆることを行います。

人がそこに依存すれば、同時に攻撃するメリットもどんどん増えていきます。

空港やカフェで Wifi を使えば、そのパケットはだれかに盗み見られているかもしれません。 どこかから送られて来たメール内のリンクは、罠サイトに通じているかもしれません。 そして、スノーデン事件によって NSA という米国機関までもが、大規模な盗聴を実施していた可能性がでてきました。

とはいえ、 Web のユーザの多くは、プロトコルや暗号の知識など無いでしょう。 そうした人にも安全に使えるように、安全側に倒されていくのは必然かと思います。

ログイン画面だけ HTTPS で後は HTTP という構成も多く有るかと思いますが、 誰がどこで何を盗聴しているかがわからない以上、「どれを暗号化すべきか」ではなく「基本的に全て暗号化する」という考えに移行し、 実際そういったサービスは増え始めています。 GoogleTwitterGithub のコンテンツは、もしかしたら今までなら「ログイン画面と設定画面以外は平文で良いだろう」と思われていたかもしれませんが、実際これらサービスはフル HTTPS 化が完了しています。

また、新しく出てくる Web の APIプロトコルには、セキュリティ上の理由から HTTPS で無いと使用できないものもあります。(後述)

一方で、 HTTP で公開され続けている古いコンテンツや、新しいにもかかわらず HTTP で公開されるコンテンツはまだまだあります。

Mozilla だけでなく Google を始めとする多くの団体や企業が、これから新しく公開されるコンテンツについては極力 HTTPS に移行するように呼びかけてきました。 今回の提案は、それをより加速するために、一定の制約を設けることで、開発者の HTTPS への移行を促すものと解釈できるでしょう。

どうなっていくのか?

端的に言えば「暗号化すべきかどうか」ではなく、「暗号化しない理由があるか」を考えることになっていくんだと思います。つまり、基本は HTTPS という世界です。

個人的にはずっと扱ってきたテーマでもありますが、 Web の HTTPS 化っていうのは結構前から始まっていました。 しかし、いよいよ本格的にそういう時代になっていくんだなという気がします。

もちろん、 Web に携わっている人なら思うところはあるでしょう。でももうそういう事を言ってられる局面ではないのかもしれません。

そして、 API 的に言っても「新しいことをやりたければ HTTPS」になっている実感はありました。

でも今回の提案はそれを逆手に取るというか、より「HTTPS じゃないと新しいことができない」のニュアンスを強めるということでしょう。

俺自身は、特に「ビジネス用途」で「これから作る」ものであれば、 HTTPS を選ばない理由はあまり無いと思っています。

しかし、 Web は別にビジネスだけの基盤ではありません。どんな使い方もできて、何をしてもいい。

それをふまえて一番気になっているのは、

「それが Web を窮屈にするのかどうか」

です。

確かに、デバッグしにくいなどの気持ちもわかります。自分も自分のサイトワイルドカード証明書を入れ年間 26000 円くらい払っています。

でも、例えば遊びで作った WebRTC や ServiceWorker が悪用されて誰かに迷惑をかけた場合どうでしょう。 そういう事例があった場合、証明書で検証できないドメインで公開されたデモアプリは手軽に試せるでしょうか? 俺にはまだその答えはわかりません。 でも、これはこれからの Web を考える上で重要な問題だと思います。

単純に是非だけで割り切れる問題ではないし、個々の立場やコンテキストによって結論は変わっていくでしょう。 必要があれば声を上げる必要が有るし、必要に応じて準備をしていく必要が有ります。

今回は、そうした議論をする上で知っておくべき動きについて知っている範囲で紹介します。

知っておきたい動き

もう少し技術的な側面から、 HTTPS が要求される流れやその理由を紹介します。

新しいプロトコルからの視点

新しいプロトコルを Web にデプロイするには、中間に入る全てのネットワーク機器(Proxy, FireWall, NAT など、 Intermedialies や Middle Box と言われる) が、そのパケットを正しく通過させる必要があります。

例えば WebSocket や HTTP2 の平文通信が使っている Upgrade ヘッダは、 Middle Box で意図的に落とされてしまい接続が確立できない場合が報告されています。 また、新しいプロトコルを新しいポートに割り当てるには、全ての FireWall でそのポートを空ける必要もあります。

このパターンに対処する一番簡単かつ確実な方法の一つが、経路を暗号化してしまう事です。 End-to-End で暗号化されていれば、 Middle Box は Upgrade ヘッダはもとより、自分を経由したパケットを一切見る事ができないため、基本的には丸っと通すしかなくなります。 そして HTTPS のデフォルトポートである 443 を通してしまえば、新たな穴を空ける必要も有りません。

HTTP2 や QUIC がそうであるように、今後出てくる新しいプロトコルについても、同様に TLS+443 という組み合わせに載ってくる可能性が予想されます。

新しいブラウザ API からの視点

最近ブラウザに追加された新しい機能の中には、非常に重要な情報やハードウェアへのアクセスを行う API があります。 こうしたものは、仕様自体が最初から暗号化前提で作られているものがあります。

例えば WebRTC の通信は DTLS での暗号化が必須になっています。

ServiceWorker は、 HTTPS でないと登録することすらできません。

そして、 WebRTC とセットでよく使われる getUserMedia によるカメラやマイクのアクセスは、 HTTPS では初回のみ使用許可を求められますが、 HTTP の場合は毎回求められます。

最近の流れで言えば、ブラウザのかなり低レベルな機能へアクセスする API が整備されつつ有ります。 こうした仕様の策定は、セキュリティサンドボックス化が非常に重要になり、慎重に設計されています。

こうした理由からも、これから出てくる新しい API について、 HTTP ではそもそも使えない、もしくは制限がかかるものは増えていくと予想されます。

移行を助ける仕様など

全てを暗号化前提にするといっても、現実的に難しい場面があることは既知の問題です。 そうした問題に対する解決策や、それに準ずる回避策として、最近ある話題について紹介しておきます。

Explicit Trusted Proxy

すべての通信が暗号化されると逆に困る場面も考えられます。 例えば、ペアレンタルコントロールやウイルス対策のようなフィルタリングサービスを、プロバイダなどが提供していた場合、 通信が暗号化されていては、中身が見えないのでフィルタリングできません。 同じように、平文だったから提供できたサービスは実は色々あります。

そこで、そうしたサービスが提供する Proxy を「ユーザが信頼した上で明示的に許可」することで、 その Proxy が途中で通信をほどいて、そうしたサービスが提供できるようにするための仕様が考えられています。 要するに MITM を明示的に許すというイメージです。

これを "Explicit Trusted Proxy"(明示的に信頼された Proxy) と呼ばれ、議論されています。

Opportunistic Encription

HTTPSTLS で暗号化するため、 TLS の持つ証明書検証の仕組みなどを使います。そのためクライアントは、サーバが提示する証明書を CA に問い合わせて検証し、相手を確認する必要があります。

ところが、NSA のような大規模な盗聴を考えると、「相手の検証」といった HTTPS の大事な部分を犠牲にしてでも 「とりあえず通信経路を暗号化」できるとうれしいという考え方もできます。

これを実現するのが "Oppotunistic Encription"(通称 OE) と呼ばれる方式で、日本語では "日和見暗号" と呼ばれます。

ALT-SVC というヘッダを用いてクライアントを暗号化通信に誘導します。 そもそも検証が行われないことが前提なため、証明書はいわゆる「オレオレ証明書」でサーブできます、ただしエラー画面は出ません。

もちろん検証が行われないため TLS の仕組みから見て「安全な通信」とは言い切れません。 移行が可能であれば TLS にし、そうできない場面への対応というとこになるでしょう。

Upgrade Insecure Request

HTTPS で提供されたコンテンツの中に HTTP で提供される JS や画像や iframe などのコンテンツが含まれる場合、 HTTP のコンテンツが改ざんされるとそこを経由して HTTPS のコンテンツも攻撃される可能性があります。

こうした混在したコンテンツは Mixed Content と呼ばれ、現状ブラウザはコンテンツに応じて以下のようなアフォーダンスを提供します。

  • HTTPS でも、 URL バーの TLS 表示にマークが付く
  • コンソールに warn や error メッセージを表示する
  • HTTP コンテンツをブロックする

特にコンテンツがブロックされる場合、正しい表示にならなくなります。 これは、多くのコンテンツが http:// の URL でハードコードされたコンテンツにとっては頭の痛い問題で、 これが HTTPS への移行を妨げる原因になる場合があります。 (ドラフト では BBC や NYT などの膨大なレガシーコンテンツを例に出しています。)

この Mixed Contents への対策として提案されているのが Upgrade Insecure Requests です。

これは CSP のヘッダに Upgrade Insecure Requests を指定すると、コンテンツ内の http:// リンクを https:// に書き換えてリクエストを発行してくれるというものです。 これにより URL がハードコードされたコンテンツも、コンテンツを一切いじらずサーバの設定変更だけで HTTPS オンリーに移行できるということです。

content-security-policy: upgrade-insecure-requests

しかし、ここまで単純な例ばかりではないため、実際はより細かい設定が可能になっています。

ちなみに最後のデモは、レスポンスヘッダではなく meta タグで指定されています。

Let's Encrypt

TLS の証明書取得には、種類などあれどそれなりの費用がかかります。 もちろん、ビジネスでやっているのであればしかるべき証明書を買うべきだと思いますが、 個人で趣味でやっているサイトなどで証明書を入れるのは、手軽とは言えない現実もあります。

そこで、 MozillaCiscoAkamai などが共同で作ったのが "Let's Encrypt" であり、 これは誰でも無料で手軽に証明書を取得できるというものです。

無料なため、例えば組織の身元を証明するような機能は無く、単純にドメインの所有権が正しいかを見る(DV 証明書)だけのものになっており ビジネス用途で使うものではありませんが、個人が趣味で使うドメインなどに使うことができます。

パフォーマンス関連

TLS にすれば、 TCP の 3-way-handshake に加えていくつかのやり取りが両者間で発生します。 通信する両者だけでなく、 CA など外部とのやり取りも発生します。 接続が確立した後は、平文の HTTP に比して暗号/複合の計算コストも追加されます。

しかし、こうした問題は既知であり、最適化の手段も整備されつつある印象です。

例えば、以下のようなものがあります。

  • Session Resumption/Session Ticket などによる接続開始の簡略化
  • OCSP-Stapling による失効確認の最適化
  • AES-NI によるハードウェアサポートによる高速化

この辺は、そのうちまた別途まとめて書きたいと思います。

Acknowledgement

翻訳部分は @jovi0608 さんにレビュー頂きました。

また公開後以下の方々にフィードバックを頂きました。

有難うございました!

Service Worker で prefetch

update

2015/3/31

  • @hirano_y_aa さんの指摘頂き訂正しました。
    • response.body の clone
    • TextDecoder の利用

intro

Service Worker の「オフライン以外」の利用第二弾です。 以前、 Service Worker で XHR のモック をしましたが、今回は Prefetch を実装してみようと思います。

prefetch とは

例えば /index.html が以下のような HTML だったとします。

<!DOCTYPE html>
<meta charset="utf-8">
<title>title</title>

<ul>
  <li><a href="1.html">1</a></li>
  <li><a href="2.html">1</a></li>
  <li><a href="3.html">1</a></li>
</ul>

この場合、ユーザは次に 1.html, 2.html, 3.html のいずれかに遷移する可能性が高いため、 ユーザが index.html を閲覧している途中に、バックグラウンドでこの三つの HTML を取得してキャッシュしておく事で、 実際にいずれかの <a> タグがクリックされた際にキャッシュヒットし、一瞬で画面を表示できます。

この、「バックグラウンドで次のページを先(pre)に取得(fetch)しておく」のが prefetch です。

rel="prefetch"

実はこれはモダンなブラウザなら HTML だけでできます。 (あまり知られていない?)

<!DOCTYPE html>
<header>
  <meta charset="utf-8">
  <title>title</title>

  <link rel="prefetch" href="1.html">
  <link rel="prefetch" href="2.html">
  <link rel="prefetch" href="3.html">
</header>
<body>
  <ul>
    <li><a href="1.html">1</a></li>
    <li><a href="2.html">1</a></li>
    <li><a href="3.html">1</a></li>
  </ul>
</body>

他にも HTTP のレスポンスに Link ヘッダを指定する方法や、それを <meta> タグで指定する方法も有ります。 "prefetch" ではなく "next" という仕様もありますが、微妙に挙動が違うので今回は "prefetch" にしぼります。ページではなく DNS の名前解決だけを先にやる dns-prefetchprerender という仕様もありますが、そちらは今回のスコープ外です。 詳細は以下。

課題

この "prefetch" 属性は <a> とは別に <link> タグを <header> 内に書く必要があります。 これは保守が微妙に面倒で、素直に考えればリソースへの参照を書いた部分に直接書いて、記述を一つにしたいものです。

そこで、今回は Service Worker を用いて、この rel="prefetch"<a> に書けるようにしてみようと思います。

(本当は、 ここ に「クエリパラメータがついてるのは prefetch しない」って書いてあったから、じゃあそれでもキャッシュ可能なアプリ向けに、クエリパラメータを prefetch するのを書こうと思ってたんですが、やってる最中に実は ChromeFirefox もクエリパラメータ付きをばっちり prefetch してたので慌てて題材を変えました。まあやってることはほぼ同じ。)

方針

  1. ブラウザが index.html へのアクセスする
  2. それを onfetch でフックし、変わりに fetch() で実際のリクエストを投げる
  3. response から body を取得する
  4. body を取得したら response をもとに戻して respondWith() する
  5. ブラウザには index.html が表示される
  6. その裏で非同期に body を解析する
  7. "prefetch" がついているリソース(1~3.html)を取得する
  8. response をキャッシュしておく
  9. 1~3.html へのリクエストにはキャッシュを返す

6 番の解析は DOMParser とか使いたいところですが、 Service Worker 内は DOM に触れないというか DOM API がごっそり有りません。なので、とりあえず片手間な正規表現でやっています。

ちょうど今週末 Service Worker ハッカソン があるらしいので、そこで fetch した HTML をメッセージングで一旦表に戻しつつ DOM を解析するような改善でもしようかなと思います。

しかし、この "prefetch" のクエリパラメータの扱いって、仕様というよりは実装判断で割と曖昧な気がしますね。 そういう点は、宣言的な仕様よりも、こういう「コードでの表現」の方が細かい制御は効くとみることもできるかもしれません。コード書かないといけないし、バグったとき解析しにくくはあるんですが。

実装

コードはここにおきました。横着して前回のブランチ。

response-injection/sample at cache · Jxck/response-injection · GitHub

肝になる部分はこんな感じです。

if ('ServiceWorkerGlobalScope' in self && self instanceof ServiceWorkerGlobalScope) {
  console.log('service worker');

  function prefetch(html) {
    'use strict';
    console.log('start parse');
    let files = html.split('\n').map(function (line) {
      return /a rel="prefetch" href="(.+?)"/.exec(line);
    }).filter(function(line) {
      return line !== null;
    }).map(function(line) {
      return line[1];
    }).forEach(function(file) {
      // list for rel="prefetch"
      caches.open('prefetch').then(function (cache) {
        // check cache exists
        cache.match(file).then(function(matched) {
          if(matched) {
            // cached
            console.log('already cached', file);
            return;
          }
          // prefetch
          fetch(file).then(function(response) {
            console.log('prefetch and cache', file);
            cache.put(file, response)
          });
        });
      });
    });
  }

  self.onfetch = function(event) {
    'use strict';
    console.log('fetch for', event.request.url);
    event.respondWith(caches.open('prefetch').then(function(cache) {
      return cache.match(event.request).then(function(matched) {
        if (matched) {
          console.log('cache hit!!!!');
          return matched;
        }
        return fetch(event.request.clone()).then(function (response) {
          if (response.headers.get('content-type') !== 'text/html') {
            // don't parse body
            return response;
          }

          return response.clone().body.getReader().read().then(function(body) {
            let decoder = new TextDecoder;
            let bodyString = decoder.decode(body.value);

            // prefetch in async
            setTimeout(function() {
              prefetch(bodyString);
            }, 0);

            // re-generate response
            let responseInit = {
              statue: response.status,
              statusText: response.statusText,
              headers: response.headers
            }
            let res = new Response(body.value, responseInit);
            return res;
          });
        });
      });
    }));
  };
} else {
  Promise.race([
      navigator.serviceWorker.register('prefetch.js', { scope: '.' }),
      navigator.serviceWorker.getRegistration()
  ]).then(function() {
    console.log('service worker ready');
  }).catch(console.error.bind(console));
}

ノウハウ

response.body は Clone されない

fetch() で扱う RequestResponse には clone() が生えていて、 DeepCopy を取得することができます。

[訂正] 本来仕様通りであれば body も clone() されるようです。実際に clone() されていないのは、現在の chrome の実装の問題だそうです。 よって、以下の解説は自分のミスでした。そして、今の実装は単なるワークアラウンドで、実装が直ればコードも直せるようです。

Stream の tee は俺が完全に勘違いしていただけなので、もう少し Stream の仕様が固まったら、また別でブログか何かで書こうと思います。

First draft at tee algorithms, for critique by domenic · Pull Request #302 · whatwg/streams · GitHub

しかし、 body だけはもとの参照が引き継がれます。

clone() の仕様に以下のように書かれている tee が (linux の tee コマンドのように分岐を意味してるんだと思いますが) 要するに実態は一個で、2つに clone() してもどちらかが消費したら消えてしまうという事のようです。

body is a tee of request's body.
body is a tee of response's body.

https://fetch.spec.whatwg.org/

今回は、 Responsebody を一旦取得しきって、その値(HTML)を解析する処理をバックグラウンドに回しながら、表ではそのレスポンスを返す必要があります。 この場合は、一度取り出した Buffer と、 Response の status, statusText, headers をもとに、もう一度 Responseインスタンスを作ってあげる必要があります。 これは恐らく、次に述べる 「body が Stream だ」ってことを考えるとしょうがないのかもしれませんが、注意が必要です。

body is ByteReadableStream

bodyStream です。この Stream は以前ブログにも書いた、 WHATWG の定義する新しい Stream API です。

Stream API がブラウザにやってくる - Block Rockin’ Codes

この body から HTML を文字列で取り出すには、 stream を読み切った結果を一旦 Uint8Array として取り出して String.fromCharCode もしくは実装されていれば String.fromCodePoint を用いて変換します。

訂正

ここで String.fromCharCode を Apply してもいいですが、テキストに変換するなら TextDecoder() を使う事もできます。自分でも実装したくせにすっかり忘れていました(汗 ソースは後で直しておきます。

Encoding Standard

Service Worker Cache

cache の view は自分でリロードしないと保存されてても表示されません。 すげーハマった。

代替手段

単純に <link> 書きたくないだけなら、 <a> などに書いた prefetch を見て、 href をかき集めて <link> を動的に生成して <head> に突っ込む方がブラウザの実装に任せられます。作り込めないならこちらの方が安全でしょう。こんな感じ。

let prefetch = document.querySelectorAll('[rel=prefetch]');

Array.prototype.slice.call(targets).map(function(target) {
  return target.href || target.src;
}).forEach(function(url) {
  let link = document.createElement('link');
  link.rel = 'prefetch';
  link.href = url;
  document.head.appendChild(link);
});

それと比べた Service Worker による実装のメリットは、一度 register させることができれば、あとは勝手に裏でやってくれるので、他のコンテンツに一切修正が必要ないということかと思います。

この考え方は確かに旨味があるんですが、今後 Service Worker が広がったとして、 「修正範囲の見積もりが面倒」とか「とにかく最小のコストでなんとか直したい」みたいな場合に、場当たり的な分岐ロジックでごまかした Service Worker を登録させる延命治療が流行って、逆に Service Worker 内が大変なことになっていくような世界もちょっと懸念されます。ここが体のいいゴミ捨て場になっていきそう。

メタな環境を得たからといって、ご利用は計画的にしないと、後でつらい目に合いそう。

Ajax 誕生から 10 年とこれから

Intro

誕生と言うのが正しいか微妙だけど、多分誕生でいいと思います。 というのも、「Ajax」という名前の出典は以下の記事で、この記事が書かれたのが今日からちょうど 10 年前でした。

f:id:Jxck:20150218010312p:plain

Ajax: A New Approach to Web Applications

(当時から、 URL が一回変わっている)

Web 初めてまだ 10 年たって無いんで、全部見てきたってわけではないですが、個人的にはちょっと思い出深い記事だったりするので、ちょっと振り返ってみます。

Ajax: A New Approach to Web Applications

筆者の Jesse James Garrett 氏は UX のコンサルティング会社である Adaptive Path の創立メンバーの一人で、 UX エンジニアです。 この記事の趣旨は、当時既にあった Google MapsGmailGoogle Suggest といったアプリの挙動について分析し、それを Ajax と名付けるものでした。

当時、特に Google Map が世に出た時に、時代的には多くの人はその UX を「Flash で実現しているんだろう」と思ったでしょう。 ああやってグリグリ動かせるマップを作る事は、従来の Web の考え方ではできませんでした。

ちなみに当時の地図といえば、概ねこんな感じでしたね。

f:id:Jxck:20150218010114p:plain

地図の周り 8 方向の矢印や拡大率は全部リンクで、クリックするとその場所や拡大率を表示したページを遷移する。なんと懐かしい UI だろう、今だとイライラして最悪な UX ですが、昔の地図とはこうだったと思います。

Yahoo 地図 は今でも、この画面遷移タイプの地図が残っていました。

ちなみに地図をクリックで掴んでずらそうとすると、こうなるwww (この地図もそのうち消えるだろうし、せっかくなので考古学の資料として残しておこう)

f:id:Jxck:20150218010241p:plain

で、 Google Map は Flash が入っていなくても画面なんか遷移せずグリグリ動かす事ができ、今ではどの地図もだいたいそうなっています(もちろん Yahoo の地図も)。

他にもブラウザでのメールはリロードして受信して、メールを開くのは画面遷移だったし、入力フォームも今のように候補を出してくれたりはしなかったし、検索結果も勝手に絞り込まれたりしなかった。

Garrett 氏が書いたこのエントリは、こうしたアプリが Flash を使わずにどうやってその UX を実現しているのかを解析し、そこに名前を付けるものでした。 だから実際彼がこのエントリを書く前から、 Ajax 的なものは少しづつ出ていて、彼がそれに受けの良さそうな名前を付けたというもの。

Ajax の何が良かったのか

何よりもインパクトが大きかったのは、それが Flash のようにプラグインのインストールを必要とせず、 HTML + CSS + JS (+ XML) だけで成り立っていたという事だったと思います。 導入にインストーラはいらなかったし、ということは手順書もいらなかった。 そして、当時新しい技術は「ベンダ」が主導していくのが主流だったところ、 Ajax はブラウザが持ってる標準技術だけでできた。平たく言えばどこかに高いライセンス料を払う必要がなく実現できた。

そして、この記事が「New Approach」と言っているの通り、使っている技術はすでにあるもので、その組み合わせ方が新しかった。

これによってそれまで文書(Document)を閲覧(browse)するために使われていた Web が、アプリを動作させるプラットフォームとしてスタートしたきっかけだったのは確かだと思います。 (そういえばそれを Web2.0 とか言ってましたね)

まあ実際それで Google Map 並みのアプリを作るのがどれだけ大変かは別としても、夢は広がっていたんだと思います。

当時の JavaScript なんて、マウスカーソルに変な装飾を施したりする程度のものと、完全におもちゃ扱いされていた頃でした。 そしてセキュリティ的な事情から、ブラウザで JS をオフにしているユーザが沢山いることを理由に 「JS 無しでも動く」が Web 開発の用件としてよくあったらしいし、動いたとしてもブラウザの実装差異なんて目も当てられない状態だったと思います。 (この時期に Google Map とか実現していたということを考えるとヤバ。)

Ajax の由来

記事では Asynchronous JavaScript + XML の略だと書かれています。

The name is shorthand for Asynchronous JavaScript + XML

通信フォーマットは XML だ!みたいな感じがしており、このおかげで 「Ajax でやりとりするのは XML だ!」 みたいな無益な原理厨を産んだという意味では害悪しか無い名前ですが、 一応このエントリの FAQ にも 「XML じゃなくてもいいよ、 JSON とかいいよ!」って書かれてます。

なんでかというと、正直これは単なる洗剤の語呂合わせだと言われています。(もし省略なら普通 AJAX と全部大文字にする)

f:id:Jxck:20150218010044j:plain

なんで洗剤かというと、そのころ SOAP (石けん)という技術があってだなという話らしいけど、まあ名前なんてそんなもんですよ。 大事なのは響き。そしてたぶん本人の想像以上に成功したんじゃないでしょうか。今コレが何の略かなんて誰も気にしてないだろうし。

その後の流れ

まず、 JS が見直されましたよね。書く人が圧倒的に増え始めた。 ツールやエコシステムが出始めて、 jQuery が出たのはこの翌年だそうです。 $.ajax() なんて冷静に考えれば酷いメソッド名ですね。

そして、閲覧ソフトだったブラウザ(Browser)の重要性があがり、この 3 年後の 2008 年には Google 自身もブラウザを作り、Chrome の beta が公開されました。 同時に、ブラウザ同士が差別化のために独自機能をつけまくってた時代が終わり(というか立ち行かなくなり)、ちゃんと標準化しようという流れになって、 だんだん HTML, CSS, JS の仕様が重要性を増して行きました。それが HTML5 という盛大なお祭りに繋がって行きましたね。

XHR を使って Comet でチャットとかが流行だして(Lingr とかもこの時期難じゃないかな)、 XHR でデータ連携で「マッシュアップだ!」みたいな時代あたりから俺も記憶があります。 そして JSONP という裏技を駆使してセキュリティ的にヤバいことに気づいて CORS を含む XHR2 に繋がったり、 Push も含めて WebSocket に繋がっていきました。

HTTP + JSONAPI を公開するサービスが増え、アーキテクチャとしての REST がやたらと見直されたり、認証として OAuth なんかも出てきたりした。

2009 年には、 V8 をサーバに仕立てた Node.js を Ry が作って、いきなり Socket.IO というキラーライブラリが出てきて、リアルタイム的な話と盛り上がった。

プロトコルとエコシステムとブラウザの API がそろい始めると、いよいよフロントで完結するロジックが増えて SPA 的な話が出たり、 フロントエンド含めてフレームワークが雨後のタケノコ並に現れては消えてった。

そうやって、 HTML も JS もブラウザもプロトコルもこれでもかというほどに進化してきたなぁという気がします。もちろんまだまだ足りてないものはたくさんありますが。あと CSS 頑張れ、超頑張れ。

その後も色々なことが起こったけど、結局いまだにこの記事に書かれていることと、そう変わらない発展系をやっているだけで、 つまりこの 10 年間は、 Ajax をひたすら消化した期間だったのかなぁという気がします。

WebSocket も WebRTC も面白いけど、結局通らなかったりするけど、最後に Ajax は必ず通る。 その安心感とシンプルさが今の Web をかなり支えている気がします。

これから

今読んでみればもちろんなんてことは無い記事なんだけど、 Web のことを勉強し始めてしばらくしてからこの記事の存在を知って、読みながらワクワクしてた気がします。 多分、自分自身が Web が面白いと本格的にのめり込むきっかけだったのかもしれません。

Web がアプリケーションプラットフォームとしてのスタートを切ってから 10 年たっても、未だに人の欲は尽きず、まだまだ進化の余地を見せている気はします。 新しい事は放っておいても起こるし、その途上を体感できるので、タイミング的には凄く楽しい。

一方で、ブラウザとかもう人類がゼロから作るには複雑すぎる存在になってしまし、もはや OS になろうとしている。ブラウザを積むデバイスも増えたし、 Web に載せるアプリも増えた。 あと、作るという立場で言うと Web って勉強するのがもの凄く大変になってしまった気がしています。初心者が「どこから始めたらいいのか」と思うのも無理は無い。 最近は正直 Web 自体が、その自重でつぶれないかと思う事がたまにあります。

それでも Web 自体は良くなっていこうとしてるし、それはもう少し続くんじゃないかな。今はまだ人にとって欠かせないものになっていると思うし。 何より、自分は「Web が好きだ」と思えているので、Web がこれからも良くなって行けばいいなと思うし、そうしていきたいと思っている。 Web が「今どうなっているのか」、「これからどうなっていくのか」も、もうしばらく真剣に考えてみたいと思ってる。楽しいし。

次の 10 年はどうだろう、もしかしたらそろそろ Web の次が出てきて、それに引導を渡して行く 10 年になるかもしれない。 であれば、 Web の最後がどう息を引き取るのか、何が次の世界を作っていくのか。それもまた楽しみかもしれない。

Extensible Web を支える低レベル API 群

Intro

最近 Extensible Web の話がたまに出るようになりましたが、なんというかレイヤの高い概念(ポエム)的な話が多い気がしてます。

もう少し具体的な API とか、「それコード書く上で何が変わるの?」って話があまりないので、今日はそこにフォーカスして、 Extensible Web 的な流れの中で整理された API の話をします。

しかし、実際には API が 「Extensible Web という理念で生まれたかどうか」は自明ではないので、 今標準化されている低レベルな API を拾い、それを整理するというエントリだと思ってもらと良いかもしれません。

あまり知られてない API もあると思うので、これを期に「これがあれば、今までできなかったアレが、標準化や実装を待たなくても、できるようになるな」と思ったら是非書いてみると良いと思います。 実際はそれこそが Extensible Web の目指すところなので。

Extensible Web とは

年末にエントリを書きました。

Extensible Web の夜明けと開発者が得た可能性の話

あと mozaic.fm | #15 Extensible Web でも全部話しています。

簡単に言うと、こんな感じの方針です。

足の遅い標準化や、時間のかかるブラウザ実装を待たずに、
開発者が自分のアイデアをコードで実現して Web を進化させられるように、
必要な機能をモデル化し、そのモデルにアクセスする
低レベルな API を提供するようにしよう。

出典は The Extensible Web Manifesto という 2013 年のマニフェストで、 そこから徐々にこの方針に則ったと思われる API が整備され、だんだん実装まで降りて来始めたというフェーズです。

(先日 マニフェストの翻訳 を本家に取り込んでもらったので合わせてどうぞ。)

TOC

  • HTML 拡張系 API (WebComponents)
    • Custom Elements
  • バイナリ系 API
    • ArrayBuffer / ArrayBufferView / DataView
    • Blob
    • Encoding
  • ネットワーク系 API
  • オフライン系 API
    • Service Workers
    • Caches
  • URL 系 API
    • URLSearchParams
    • FormData
  • 非同期処理系 API
    • Promise
    • Streams
  • 低レベル API との向き合い方

リンク貼れたらなぁ。

HTML 拡張系 API (WebComponents)

例えば HTML 自体に新しいタグを追加するとなれば、 W3C/WHATWG で標準化する必要がありました。 しかし開発者がコードで新しく独自のタグを定義し、その挙動を JS で実装できるようになりました。

これが WebComponents と呼ばれる API 郡です。

Custom Elements

タグ名に - をつける必要がありますが、新しいエレメント(タグ)を定義することができます。 また、新しいタグを定義しなかったとしても、既存のタグを拡張して、そのタグに対して機能を追加することができます。

今までは、独自のタグを定義する標準的な方法がなかったため、 div / span などの汎用タグに class 属性などを付けたうえで拡張する挙動などを定義してきました。

しかし、 CustomElement を用いると、任意の挙動を実装したオリジナルのエレメントを定義することができます。 これを用いると、完全に独自なエレメントを、標準化やブラウザの実装を待たずに定義することができるのです。

var xFoo = document.registerElement('x-foo');
// xFoo に挙動を定義

// オリジナルのエレメントを作成
// <x-foo></x-foo>

しかし、HTML の既存のタグは既にかなりのノウハウと実績を持っているため、それらを無視した完全にオリジナルなエレメントを作成しても、それが従来の Web と自然な形で馴染むところまで作り込む事は実際には難しいです。

そこで、ブラウザが標準で実装しているものをベースとし、拡張機能を定義することもできます。 この方法は、もし CustomElements にブラウザが対応していないとしても、通常のタグの動作にフォールバックできるというメリットもあります。

document.registerElement('x-form', {
  extends: 'form',
  prototype: Object.create(HTMLFormElement.prototype)
});

// 既存のエレメントを拡張
// <form is="x-form"></div>

タグを定義可能な API であることを利用し、「HTML の標準エレメントを Custom Element で再実装する」という実験プロジェクトもあります。

https://github.com/domenic/html-as-custom-elements

これにより、以下を調べる取り組みのようです。

  • Web の持つ機能を再現可能かを調べることで、足りてない API が無いかを調べる。
  • Custom Elements の API が、プラットフォームを再現するものとして十分なデザインかを検証する。

つまり、 Custom Elements は、 Web が持つ HTML エレメントという低レベルな要素を、 API を用いて再構築可能なまでに低レベルな API を目指しています。そうして開発者から出てきた新しいエレメントが有用であれば、標準化のプロセスを経てブラウザに実装されるかもしれません。

ただし、エレメントを実装するには、既存の DOM ツリーに依存しないスタイルの適用や、その定義を読み込む機能が欲しくなります。 こうした足りてない部分を補うために、以下の 3 つの API が新たに提供され、一般的にはそれをまとめて WebComponents と呼んでいます。

http://w3c.github.io/webcomponents/spec/custom/

バイナリ系 API

JS は長らく、バイナリデータを扱う標準 API がありませんでした。 特に WebGL の導入に伴い、こうした API の需要が強くあったという経緯もあるようなので、 Extensible Web よりも少し前な気もしますが、重要な低レベル API であり、他の低レベル API もここに依存するものが多いので、あらためて整理しておきます。

ArrayBuffer / ArrayBufferView / DataView

ArrayBuffer はバイナリデータが詰め込まれた、読み取り専用のバイト配列です。 バイナリデータを返す API などは、この ArrayBuffer の形式で返すものがあります。

実際に ArrayBuffer からバイナリデータを取り出すのが ArrayBufferView です。 もし JavaScript に uint8 や int32 型などの、サイズ固定な数値型が定義できれば、その型で宣言した変数にデータを取り出すといったことができますが、 JavaScript には number 型しかないためその方針はとれません。

そこで、 ArrayBuffer に詰め込まれたバイナリデータを uint8 や int32 といった Chunk ごとに区切られた配列とみなし、 その配列からデータを取り出した結果が求めるサイズの number になっているという方針をとります。 この任意のサイズに ArrayBuffer を区切るためにかぶさる View が、 Uint8Array や Int32Array といった ArrayBufferView になります。

Uint32Array: [                            4035938432]
Uint16Array: [             34944,              61583]
Uint8Array:  [     128,      136,      143,      240]

ArrayBuffer: [ 1000000, 10001000, 10001111, 11110000]

しかし、 ArrayBufferView は固定長に区切られたデータを扱うには向いていますが、たとえばネットワークやファイルのような、ヘッダにある様々なのサイズの情報を読み込むような場合には、そのたびに View の型も変える必要がでてしまいます。

そこで、こうしたタイプのデータは DataView を用います。 DataView は getUint8(), getInt32() といった具合にデータを指定サイズで都度取り出せるため、パケットの解析などに適しています。もちろん setter を使えば、データを送る/書く側(サーバなど)に使うことができます。

ArrayBuffer: [ 1000000, 10001000, 10001111, 11110000]

getUint8(0):  128
getUint16(0): 32940
getUint32(0): 2156433392

この ArrayBuffer, ArrayBufferView, DataView を合わせて、一般に TypedArrays と呼ばれています。 後述する多くの API がバイナリを何らかの形で扱っており、その場合この TypedArrays を用いることになるため、非常に重要な低レベル API と言うことができます。

https://www.khronos.org/registry/typedarray/specs/latest/

Blob

ブラウザ上のバイナリデータの固まりを扱うオブジェクトです。 文字列や ArrayBuffer, ArrayBufferView などをもとに生成します。

バイナリデータを「ブラウザという世界」に持ってくる際にに経由する、地味だけど非常に重要な API です。

特徴は URL.createObjectURL() に渡すと URL が生成できることです。 これは img, video などの src の値や、 xhr など URL を扱う API にそのまま渡せるということです。 データの基本であるバイナリオブジェクトをブラウザが基本要素として扱う URL に変換できる事によって、全てのデータがブラウザと繋がることになります。

例えば File オブジェクトも Blob の拡張として成り立っており、 URL に変換してして <a> の href に繋ぐことでダウンロードできます。これは <a> の href の扱いが既にブラウザ上で規定されており、そこに任意のバイナリデータを URL を介して接続できている事を意味します。

var blob = new Blob(arrayBuffer, { 'type' : 'audio/mp3' }); // バイナリ
var url = URL.createObjectURL(blob); // URL
document.querySelector('a').href = url; // ファイルがダウンロードされる

自分で何かバイナリを扱う API を考える場合に、基礎として使うことができます。

https://developer.mozilla.org/en-US/docs/Web/API/Blob.Blob

Encoding

今まで String.prototype.charCodeAt()String.fromCharCode()、 ES6 では String.fromCodePoint() などで、文字を Unicode の code point と相互変換することができましたが、任意のエンコードとの変換はバイナリ操作によって自分で行う必要がありました。

Encoding API を用いると、文字列を「文字コードに応じた」バイナリデータと相互変換できます。 インタフェースは、内部のエンコーディング形式をそれぞれに持つ事ができるように TextEncoder/TextDecoder に分かれています。

TextEncoder は 'utf-8', 'utf-16be', 'utf-16le' のみに対応しており、 encode() で String をその文字コードの Uint8Array に変換します。

var utf8 = new TextEncoder('utf-8');
utf8.encode('あ'); // [227, 129, 130]

エンコードする文字コードが限定されているのは、 Web において基本的にこれらの文字コードしか使わないようにさせるためです。 Web の世界ではもう utf-8 以外を使うことは、極力避けて行くべきという旨が仕様にも書かれています(https://encoding.spec.whatwg.org/#preface)。

TextDecoder はかなり多くの文字コードを指定することができ、 decode() で Uint8Array を String に変換します。 (どのくらい多いかは、 https://encoding.spec.whatwg.org/encodings.json を見るとわかります。レガシーな文字コードもきちんとカバーされてて本当にすごい。)

例えば shift-jis だと以下のような感じです。

var shiftjis = new TextDecoder('shift-jis')
shiftjis.decode(new Uint8Array([130, 160])) // 'あ'

デコードでは多くの形式が対応しているのは、例えば任意のテキストファイルを File API で読み込んで、その内容を表示するといった場合に、多くのエンコーディングに対応する必要があったからかと思います。 utf-8/16 以外の文字コードの文字は Blob として取得し、 utf-8 の文字に直してから別の処理を行うといった形になっていくと思います。

また、 utf-8 値が取得できるため、非推奨な escape() の変わりに UTF8 パーセントエンコーディングなども可能になります(エンコードして toString(16).toUpperCase() して '%' をつけるだけ)。

https://encoding.spec.whatwg.org

ネットワーク系 API

Fetch

ブラウザから発生するネットワークアクセスは基本的には HTTP です。 しかし、単純な HTTP/1.1 GET リクエストは、 telnet などで発行すれば 3 行程度で済みますが、 ブラウザはブラウザ特有の様々な挙動が追加されるため、一見単純な GET でも複雑なリクエストになります。

例えば以下のようなものです。

  • UA が自動で付与される
  • リファラが付与される
  • Cookie が付与される
  • ブラウザがキャッシュを管理し、付随するヘッダ処理をする
  • CORS の制限が適用される
  • 必要に応じて自動で preflight が発生する
  • etc

こうしたブラウザ特有の挙動を加味して発行された HTTP リクエストで、ブラウザはサーバからリソースを取得します。この行為を Fetching と呼んでいましたが、その行為は XHR や CORS によってどんどん複雑になってきました。

Fetch の仕様は 「ブラウザが Fetch するとはどういうことか?」という概念をきちんと整理して定義しており、その最小限の API として実装されたのが fetch() API です。

fetch() は XHR と違い Promise を返す API であることから、単にモダンな感じにしただけかと思われがちですが、 fetch() 導入に伴って以下が変わっています。

  • Request, Response, Header などのクラスが定義された
  • fetch() 自体はオブジェクトではなく単なる関数
  • chache や origin や credential など細かな制御が引数で可能になった
  • Promise を返す

したがって、ブラウザがデフォルトで発行するリクエストと同等なものを用いたネットワークアクセスが必要なライブラリなどを書く場合は、継ぎ足しで作られた XHR の古めかしいインタフェースにとらわれる事無く実装ができます。

fetch('http://my.api.org/', {
  method: 'post',
  headers: {
    'content-type': 'application/json'
  },
  body: JSON.stringify({
    user: 'Jxck'
  }),
  credentials: 'cors',
  chache: 'force cache'
}).then(res => {
   console.log(res.url, res.type, res.status);

   if(res.headers.get('content-type') === 'application/json') {
     res.json().then(json => console.log(json));
   } else {
     // res.arrayBuffer();
     // res.blob();
     res.text().then(text => console.log(text));
   }
}).catch(err => console.error(err));

注意点としては、今はまだ abort() が無く、 Promise を返すので onprogress 相当がありません。 これは今後 CancelablePromise や Stream などの議論とともに進んでいく予定のようです。それが実装できると、 XHR は fetch を使って完全に再現できる筈です。

http://fetch.spec.whatwg.org

TCP and UDP Socket API

WebSocket は「Web 上での双方向通信」を目的としており純粋な TCP ではないし、 WebRTC は「Web 上での P2P」を目的としており純粋な UDP ではありません。 fetch() は「ブラウザからの」 HTTP リクエストとして一番低レベルでした。

そこで、もういっそ TCP/UDP のソケット API がそのままブラウザから使えるようにしてしまえ、という感じのコンセプトです。 Socket APITCP/UDP への write() はもちろん、 listen() もできます。つまりサーバが立ててしまう可能性があるのです。

// write
var client = new TCPSocket('http://example.com', 80);

client.writeable.write('ping').then(() => {
  client.readable.wait().then(() => {
    console.log(client.readable.read());
  });
});


// listen
var server = new TCPServerSocket({'localPort': 300});

server.listen().then((connection) => {
  connection.readable.wait().then(() => {
    console.log (connection.readable.read());
  });
});

例えば MQTT などを WebSocket 介さず実装したり、現在議論中の ORTC のようなものを PoC 実装したりする際に使えるだけでなく、 ブラウザを積んだデバイスが、 ServiceWorker 内で TCP サーバ立てたり、 DNS の名前解決するなんてことができてしまうかもしれません。

ブラウザのレベルまでこの API を持ってくる事は、セキュリティのサンドボックス化が非常に気になると思います。確かに従来 OS が扱っていたレベルまで下がっている Socket API の実装ではそこが非常に重要になります。 そこで Extensible Web の中には、「それこそが標準化がリソースを裂くべきところだ」と明記されています。高レベル API の策定ではなくセキュリティ的に安全な低レベル API の提供に注力するべきという方針です。

一方で、例えば raw socket を扱うコードが JS で完結すると、今まで拡張やプラグインで無茶したコードのセキュリティホールによって発生していた、ネイティブ層まで突き抜けるような脆弱性が、減少する可能性も考えられます。

http://www.w3.org/TR/raw-sockets/

オフライン系 API

オフライン化するために必要な機能をモデル化すると以下の三つに大別することができました。

  • 通常の JS のコンテキストとは、別のコンテキスト(スレッドと言っても概ね良い)で JS を動かす環境 (service worker)
  • キャッシュを保持する機能 (cache)
  • ブラウザのリクエストを再現する機能 (fetch)

これらを合わせて 「オフライン Web を実現する API」だとされている感がありますが、それは使い方の凡例の一つです。 各々は別にチュートリアルに書かれてる通り、オフライン化するために「しか」使えないわけではありません。

Service Workers

ブラウザの JS とは別のコンテキストで動作する(別のスレッドが立っているイメージ)環境を提供します。 それだけなら WebWorker と同じですが、 SW はブラウザとネットワークの間に挟まる Proxy のように動作します。

例えば、ブラウザ上で発生した HTTP リクエスト(つまり Fetch) をイベントで検知することができたり、 外から Push API で飛んで来たメッセージをイベントで受け取ることができます。

ブラウザとは独立したコンテキストで、ブラウザの裏でメタな処理が可能な環境を提供するのが、 SW の本質です。 この事をふまえて、よく言われている「オフラインアプリ」や「プロキシ」として使う事もできるというだけであって、そう使わないといけないというものではありません。

// 登録した SW 内で
this.addEventListener('fetch', (e) => {
  // ブラウザで発生した fetch (リンククリックや XHR) を取得
  var req = e.request;

  // req に細工
  req.header.set('x-foo', 'bar');

  // 細工したリクエストを fetch して response 返す
  e.respondWith(fetch(req).then((res) => res));
});

このように fetch() が介入できるのは、 fetch の仕様で紹介した Request/Response/Header クラスによって、ブラウザで発生した Request オブジェクトをいじったり、それを用いて fetch() して得た Response をブラウザに返したりというインタフェースが整ったからです。

https://slightlyoff.github.io/ServiceWorker/spec/service_worker/

Caches

外部から取得したリソースのキャッシュを管理するためのオブジェクトです。 この API は純粋に、 Request にひもづいた Response を保存できます。

したがって、ブラウザが発行した Request に、変わりに fetch した Response を保存するといったことが可能になるのです。 Request をキーにしたオブジェクトのように扱えるため、 API の粒度が細かくプログラマブルであり、 Application Cache API とは違い、一部のリソースだけキャッシュを更新すると言った事が柔軟に可能です。

SW + Cache が Application Cache API に変わるオフラインアプリの作成で注目されているのはこの部分です。

// request に紐づいたキャッシュを返す
self.addEventListener('fetch', (e) => {
  event.respondWith(caches.match(e.request)
    .then((response) => response));
});

しかし、オフライン化のためにしか使ってはいけない訳ではなく、例えばオンライン状態でも純粋なキャッシュとして利用して、パフォーマンスの向上などを行う事もできます。(その場合は本来は cache-control ヘッダを使うべきですが)

https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache-objects

URL 系API

Web の基本かつ重要要素である URL を、 JS から手軽に扱うことができます。なんで無かったのかというレベルです。 実は難しい URL のパース/シリアライズが可能で、パーセントエンコーディングpunycodeIPv6 や Base Path などなどを、 new するだけでまるっとやってくれる API です。(今までは <a>タグを使って無理矢理 やるか、自分でパースするしかありませんでした。)

var url = new URL('http://user:name@www.ドメイン.com:8080/login?foo=bar#hash')
url.hash;     // "#hash"
url.host;     // "www.xn--eckwd4c7c.com:8080"
url.hostname; // "www.xn--eckwd4c7c.com"
url.href;     // "http://user:name@www.xn--eckwd4c7c.com:8080/login?foo=bar#hash"
url.origin;   // "http://www.xn--eckwd4c7c.com:8080"
url.password; // "name"
url.pathname; // "/login"
url.port;     // "8080"
url.protocol; // "http:"
url.search;   // "?foo=bar"
url.username; // "user"

URL.domainToAscii('ドメイン名.com'); // 'xn--n8jwd4c7c.com'
URL.domainToUnicode('xn--n8jwd4c7c.com'); // 'ドメイン名.com'

domainToAscii()domainToUnicode() については、執筆時点で実装しているブラウザはないようなので、サンプルです。 パーセントエンコードなどがきちんと考慮されるのは、 EncodingAPI によって utf-8 がきちんと扱えるようになったためと言えます。

注意点として、標準の DOM API はこの URL オブジェクトを内部で使えど、外に公開するためには用いないということです。 外に公開する場合は、従来通り URL を文字列として公開すべきという旨が書かれています。(https://url.spec.whatwg.org/#url-apis-elsewhere) 実際 fetch() などは内部で URL オブジェクトを使っていますが、 Request クラスが公開しているのは文字列型の url プロパティです。(https://fetch.spec.whatwg.org/#request-class)

片手間な正規表現で処理していたスクリプトは、すぐにでもこのクラスで置き換える方がいいでしょう。 また、新しく書くライブラリも、 URL 文字列を受け取るなら、まず最初にこれでパースし、公開時に toString() するくらいが良いと思います。

https://url.spec.whatwg.org/

URLSearchParams

URL のパラメータ部分を扱うためのオブジェクトです。 また、 Form を POST するときなどに使う form-urlencoded 形式の表現を得ることができます。 非常にシンプルな API ですが、シリアライズ時には適切にエンコードされた文字列が得られます。

var params = new URLSearchParams('a=b&c=d&a=x');
params.has('a') // true;
params.get('a') // 'b';
params.getAll('a'); // ['a', 'x']
params.delete('a');
params.append('あ', 'い');
params.toString(); // 'c=d&%E3%81%82=%E3%81%84'

もし適当に &=split(), join(), encodeURIComponent() していた処理があったとすれば、この実装で置き換える事でブラウザと完全に互換な実装にできます。

自分で実装するライブラリがパラメータを扱う場合は統一した API としてこれを使う事が考えられます。

現時点では XHR でこのオブジェクトを直接 form-urlencoded として送るなどのことはできません。 それだとちょっと微妙なので、提案しました。今 議論中 です。

https://url.spec.whatwg.org/#interface-urlsearchparams

FormData

その名の通り Form の Data を扱うオブジェクトです。 URLSearchParams と似ていますが、こちらは DOM 上の Form から直接生成する事もでき、そのまま XHR で送信も可能です。

また DOM の Form が <input type="file"> を許容するように、 FormData にも File オブジェクトを含む事ができます。 なので、そのまま XHR で send() すれば手軽に File アップロードが可能です。

var form = document.getELemeneById('#login_form');
var formData = new FormData(form);
xhr.send(formData);

気をつけないといけないのは、 FormData 経由で送ると必ず multipart/form-data 形式になるため、たとえ文字列しかなくても x-www-form-urlencoded で送りたければ URLSearchParams に詰め替える必要があります。(content-type を指定しても無視される)

先ほどの提案した内容は、これと同じように Form から URLSearchParams を生成できるようにする方向で動いているので、それが実装されたら以下のようになるでしょう。

  • URLSearchParams: テキストのみ x-www-formurlencoded
  • FormData: Blob 込み multipart/form-data

https://xhr.spec.whatwg.org/#interface-formdata

非同期処理系 API

WHATWG で策定されている新しい API で、非同期を扱うものは基本的には Promise/Stream ベースで設計されています。

Promise

Promise 自体は DOM から始まった仕様ですが、 ECMA に移され ES6 の API となりました。 これまでは onxxxxxx という関数にコールバックを登録したり、 addEventListener を用いる API が主流でしたが、単発の非同期処理は Promise を返す API に統一されつつあります。

例えば前述の fetch や service worker などはすでにそうなっています。

Promise の仕様が ES のものであるメリットはでかく、 node/io と browser で完全に互換な API で使えます。

http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects

Streams

node/io でおなじみの Stream と同じようなイメージです。

Stream API がブラウザにやってくる

特にイベントが連続的に発生する API では、途中の chunk データを emit するために、 Promise の代わりに stream を返す API に統一されていくようです。

node.js とは、例えば pipe() ではなく pipeTo() であったり、引数の API が違ったりしますが、基本的な考え方は近いので、どちらも Stream ベースで書くことができるでしょう。

https://streams.spec.whatwg.org/

低レベル API との向き合い方

HTML5 の一連の新規 API 系の話は、割と使い方とセットで提供されていたかもしれませんが、 ここに上げたような API は、どのような使い方をしても構いません。よくあるチュートリアルにある使い方に限定する必要はありません。

SeviceWorker はオフラインだけのためじゃないし、 WebComponents は 4 つの API セットで使わないと行けない訳ではないです。

そして、何かをするためには低レベルすぎて使いにくい・煩雑だと思うかもしれませんが、それは当然です。 あえて低レベルとして提供されているものなので、その上で高レベル API としてのフレームワークやライブラリ(例えば Polymer のような) を作り、使うのが良いでしょう。今はまだ揃ってはいないので、逆に自分が考える最強のライブラリを作るべき段階と言えます。

そして、そうやって低レベル API の上に作られた高レベル API が良いものであれば、 jQuery が querySelector() に繋がったように標準化がそれを取り込んで、やがてブラウザに実装されて、ライブラリ無しで使えるようになる可能性があります。

要するに、標準化やブラウザベンダのことなど何も気にせずコードを書けば良く、それが Extensible Web の目指すところです。

一番の問題は、こうした低レベル API 自体は、標準化してブラウザが実装しないといけません。 だから、理想のサイクルが回るにはもう少し時間がかかります。

その辺が最近自分が取り組んでいる事なんですが、長くなったのでその話はまたいずれ。