Block Rockin’ Codes

back with another one of those block rockin' codes

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 自体は、標準化してブラウザが実装しないといけません。 だから、理想のサイクルが回るにはもう少し時間がかかります。

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

2014 年をふりかえる

intro

恒例の振り返りです。

今年もこのブログを毎月更新するっていう目標は達成できました。

HTTP2

去年からはじめた HTTP2 まわりの活動はウェイトがかなり多かったですね。 HTTP2Study というコミュニティを小さく小さくやってきた感じです。

勉強会、ハッカソン、 issue-thon、conference とやって、アドベントカレンダーも無事完走しました。 自分でも HTTP2 の実装をするという目標があったので、それを粛々とやりました。

今まで HTTP レイヤやネットワークレイヤはきちんと勉強してこなかったので、この活動はかなり勉強になりました。

あと、今年は初めて IETF に参加しました。初参加でしかもちょっと話す時間をもらうっていうのは結構珍しいらしいです。

IETF89 で #http2study の話をしてきました。

この辺の活動は先日まとめました。

#HTTP2Study の軌跡

本当に凄い人たちが集まっています。この場を無駄にはしないようにしたいものです。 RFC が出るのが楽しみですね。

Go

Go研というこれまた小さくもガチな研究会をやってきました。 これも、イベントや発表会じゃない、本当の意味での「勉強会」ができた気がします。

github.com/goken/goken/

あと、Go には Tour of Go というチュートリアルがあるのですが、書籍など含めて「Tour of Go の次」っていうのがちょっと欠けていると思ったので、 Web+DB Press に Go の記事を書かせて頂きました。

Web+DB Press vol.82 で Go の特集を書かせて頂きました #wdpress

あとは、京都に行ってハンズオン してきたり、温泉行ってから GoCart やってきた。楽しかった。

Go のカンファレンス も関わらせてもらいました。 Rob 先生に直々に指導してもらえたのは貴重な体験でした。

Golang Error Handling lesson by Rob Pike

もともと HTTP2 のサーバを書くために始めたんですが、特に exe で動かしたいツールを書く時に便利で、地味に使ってます。

Node.js

やっと、やーーーーーーーっとのことで Socket.IO 1.0 がリリースされました。 その数日前に、 Guille から DM があったので、とりあえずまた来いということで呼んでミートアップやりました。

Socket.IO Meetup

1.0 が出たことで、仕事とかでもちょっと導入しやすくなりましたね。 ただ Socket.IO 自体は WebSocket の枠を超えてもっと広くネットワークプロトコルを抽象化していく存在になりそうなので、その辺はかなり期待しています。

個人的に Node.js でネットワークさわる何かを書く時は、 Socket.IO のインタフェースをまねるようにしてて、それがまあしっくり来ています。

High Performance Browser Networking

HTTP2 のカンファレンスに来てもらった Ilya が出版した HPBN の、ちょっとだけ?遅れた出版記念イベントをやりました。 彼の功績はホント凄いし、本当に良い話が聞けたなぁ。

HPBN meetup

Web のエンジニアが知っておくべきネットワークの知識としては、一つのマイルストーンとしてこの本は価値があります。

CROSS

次世代 Web セッションをやりました。去年に続き二回目ですね。 普段あまり表に出てこない方々も含めてかなり深い議論ができたと思います。

これからの Web の話をしよう。 (次世代 Web セッション @ CROSS2014)

ちなみに来年は出ませんが、このセッションはなんらかの形でやりたいなとは思ってます。

Podcast

「次世代 Web Podcast」ということで 4 月から始めました。

  • 今何がおこっていて
  • これからどうなっていくのか

これをテーマ毎に議論する感じです。

今年やったのは 13 テーマ、 intro と sideshow 含めて 18 エピソードでした。 合計で 20 人の最先端な方々に出ていただいて、お話を聞く事ができました。 出ていただいた方々には本当に感謝しかありません。ありがとうございました!

ぶっちゃけ「やるだけで精一杯」ですが、来年も地道にやっていければと思います。

2015 年に向けて

今年は、ずっと敬遠していた「標準化」というものに触れる機会が多かったです。 Web が成り立つ仕組みと、それが実際に行われている場面を自分の目で見る事ができたのは、自分の中で凄く良い経験だったと思います。

結果、俺自身はやっぱり標準化にはあまり向かないかなぁとは思います。求められるスキルも全然違うなと。

一方で、この標準化自体も見直されているという動きを感じます。


HTML5 も勧告されたし、 HTTP/1.1 もアップデートされました。今まで動いていたものを仕様にする作業は終わって、これから必要になる仕様を策定して行く作業が続いている中で、 これからの道の部分には、仕様のエディタや標準化団体だけじゃなくて、よりアグレッシブな Web の開発者の貢献が必要です。


標準化は、低レベルな API を提供するという形でそれの土台を整えてきました。 あとは、 Web の開発者がその上で新しい世界を築いて行くターンです。というかそういうことができるようになってきた。


それが、先日書いた話です。

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

ということで、来年は今年までに築いてきた HTTP やら HTML やらの土台の上で


自分なりに Web を進化させるためにできること(Extend the Web Forward) をやってみたいと思っています。

まずは土台から。

2015 年も、そんな感じに Web が盛り上がったら面白いなと思います。

来年もどうぞよろしくお願いいたします。

Jxck

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

Web based on Standards

Web は誰のものでもありません。

だれかプロダクトオーナーがいてその人が意思決定するとか、そういうのとは真逆の成り立ちをしています。

標準的な仕様を決めて、その仕様に則って Web の世界は成り立っている。

政府が作るサイトも、 Twitter も、学生が作ったブログも、全部同じルールで作られている。だから繋がる。


これって結構凄いことだと、自分は思っています。

Standarization

このルールの決め方にもルールがあって、ちょっと敷居は高いかもしれないけど、誰でも自由に参加して、自由に意見を述べることができる場があります。

標準化団体ってやつですね。

なんか一部の人たちが勝手にやっているように思えるかもしれないけど、それは選挙に行かない人の理論と同じです。

あなたが仕様について意見を持ってて、それが妥当であるならば、その発言は仕様を根本から変えることもできます。

その点で、選挙より結果との距離が近いかもしれないですね。

自分が標準化に参加していなくても、そのプロセスで行われた議論はオープンなので、追うことができます。


正直わかりにくい所もあるし、賛否があるのも分かる。時に政治色もある。 色々問題もあるかもしれないけど、これは今の Web を支える重要な仕組みなのは確かです。

Web の進化の速度

昔ブラウザベンダは、製品の差別化のために我先にと独自の仕様をブラウザに取り入れたました、 marquee タグとかああいうのですね。

でもそれだと他と繋がらなかった。

Web の利用者がどんどん増えて、その間で繋がることの価値が上回ったことで、どんどん消えて行った。

一方で、多くの人にとって価値のある仕様(XHR とか)は標準化のプロセスに乗せられた。

HTML5 関連の盛り上がりは、それが加速した結果だったと思います。


問題は、ステークホルダが増えたときの合議制にはやっぱり限界があったということ。 これが原因で標準化を嫌う人は結構いるんじゃないかな。

そして、仕様ができても、ブラウザの実装はすぐに行き渡る訳ではない現実がある。

ブラウザに実装されないと、俺らにとっては無いのも同じで。

考慮すべきブラウザがせいぜい両手で数えられるくらいなのが本当に不幸中の幸いだと思います。 もっと多かったらどうなっていたか。


本来、仕様を研ぎすますためには、実際にそれを使った開発者からのフィードバックが不可欠なはずなのに、 開発者の手元に API がやってくる頃には、時代のニーズは様変わりしているという事態も、現実に起こり始めていて。

その、わかりやすい例が Application Cache だったりするわけです。 標準化と、ステークホルダと、ブラウザの実装と、開発者のフィードバックと、時代の速度の微妙なずれの賜物。

Extensible Web Manifesto

結局、仕様の策定やブラウザの実装を待ってられないというか、そこに時間がかかりすぎることのフラストレーションって絶対あるんですよね。

仕様を策定してる横で、リアルワールドのユースケースはどんどん変化するし、それらに後追いで対応すると仕様は複雑になり、 いずれは誰の目にも「壊れたもの」にしか映らなくなる。 Application Cache の失敗 はつまりそういうことだったんだと思います。

オフライン対応を誰よりも待ち望んでいた 大手 SNS サービスあたりは「これはマズい」って気づいてい なんとかしようとした んだけど、根本的にはやっぱり難しく。

開発者が見向きしなくなった結果、どのブラウザもその仕様を実装しなくなって、きっとこのまま Web の歴史の闇に消えて行くだろうと思います。その方がいい。

最初の意図からどんどん外れて、継ぎ足し継ぎ足しで膨らんで来た XHR も、結構ギリギリな感じに見えますね。


これって、何がいけなかったのか、もしくは、本当に必要なものって何だったのか?


オフライン対応は絶対必要だけど、そのユースケースは仕様策定時に想定できる範囲を超えてどんどん広がっていってしまう。

「こういうことしたい」と現実の問題を解決して、その時に必要な API のどこまでをブラウザがやるべきか。

そのフィードバックこそが Web の健全な発展を後押しして、標準化のオーバーヘッドを最小限に減らすことができるはずなのに、そうなってるとは言い切れなかった。仕様策定側が「こういうことやりたいんだろ?こう使えよ」ってなってた。

実際は、細かく低レベルな機能を提供して、開発者がそれらを組み合わせながら自分たちのアイデアを素早く形にし、現実の問題を解決できるようにすることが必要なんだよね。

だから、そういうふうに

「開発者には、ユースケースじゃなくて、可能性とその道具を提供しよう」

そして

「開発者が仕様やブラウザの実装を待たずに Web を拡張していけるようにしよう」

と考えた人たちが出てきた。

それが、 Extebsible Web Manifesto の言いたかったことだと思ってます。

Extend the Web Forward

Extensible Web Manifest 自体は 2013 年の中盤くらいに出た話ですね。

f:id:Jxck:20141221134603p:plain

書いたのは、Ember.js その他沢山の開発者である Yehuda Katz や、 Service Workers その他沢山のの仕様を策定している Alex Russell、Fetch, Stream, Cache その他たくさんの仕様を策定している Anne van Kesteren など WHATWG で活動する人たちです。

イメージでは、仕様も書いてるけどコードも書けるし、「W3C になんか任せてられるか!」って思ってそうな人たち。

サイトに行くと賛同する主立った人たちの名前が載ってます。もし賛同するならリンクから自分の名前を登録することもできます(意思表明だけで、別に何が起こる訳じゃないけど)。


で、具体的に何が変わるのか?

その辺の話は、特に中心になって呼びかけてる Yehuda Katz のブログ あたりにも書かれています。

低レベルな API セットと可能性

実は、この考え方は早速 Application Cache の後継として話題沸騰中の Service Worker で実践されています。


Service Worker は本来 Offline 対応に必要な API を考えた結果、それらを細かい個々の仕様に分けて、組み合わせて使うようになっています。

まず、 Shared Worker のように別スレッドで処理を実行できる環境に加え、それを適切にアップデートする仕組みをきちんと整備しました(AppCache の反省が見えますね)。

また、キャッシュの管理もブラウザ任せにしないため、開発者側が完全にコントロール可能にできるよう、 Cache API を作りました。 JS の API です。

Cache したコンテンツをユーザに提供する仕組みですが、ここが面白くて、別のスレッドから「HTTP リクエストに介入する」という方式をとっています。これが Service Worker の本懐。

この Network Interapt の仕様は、オフラインの実現には使えるけど、オフラインの実現にしか使えない訳じゃないですよね。

そこで Cahce を差し込んで返すのは One of Them です。 たとえば、ブラウザが作るのに任せきりにしていたリクエストを奪って、独自にヘッダを足したリクエストを代わりに投げたりすれば、サーバとより柔軟なネゴシエーションができるはずです。

すると、ブラウザが発行するリクエストが再現できて、かつプログラムから必要十分なコントロールを可能にしたリクエスト API が欲しくなる訳です。 それが、前回解説した Fetch API なんですね。だからそもそも 「Fetch するってなにか?」もちゃんと整理されたと。

githubfetch polyfill が XHR で実装されてて勘違いしている人がいるようですが、実際は Fetch API の方ができること多いです(逆に XHR は Fetch API で作れます)。ただのモダンなインタフェースラッパーじゃないです。ただ polyfill するには XHR しか今無いってだけで。


もう一つ、派手さが無いので紹介する場があまり無いけれど、 HTTP 投げるなら URL の扱いは必須な訳です。でも実は長いこと まともな API って無かった んですよね。 これも合わせて定義され、 URL API ができました。 Fetch 同様「URL とはどういうものか?」みたいなレベルから整理されたんです(文字コードの話からも逃げてません)。 派生の URLSearchParams とか使うと、今まで文字列結合でやってたこともいらなくなったり、地味に便利です。なぜ無かったのかと。

さらに、バックエンドとの同期や、サーバからのプッシュの受信、位置情報をベースとしたイベントの取得など、低レベルな API を個々に提供してます。やりたい放題ですね。だから Service Worker は HTTPS オンリーなんですよ。


今はまだ、モダンブラウザのみの閉じた環境であるのをいいことに、ServiceWorker 内でしか使えない API が多いですが、いずれ外に出てくればオフラインの世界以外でも使い道ありそうですよね。

というか、ネットワーク的な意味では、最終的に TCP/UDP Socket 自体を扱えるようになれば、もっとやりたい放題な可能性も待ってます。


すっかりおなじみの WebComponents もそうです。

WebComponents を使えば、標準化を待たずに独自のタグを定義可能になるけれど、それだけのために仕様が多くて複雑で低レベルで扱いが難しいと思ってる人って多いんじゃないでしょうか?

逆です、「それだけのため」じゃないからです。

WebRTC も色々覚えること多いですよね。の割にシグナリングの方法は仕様に無かったりには、ちゃんと意味があるんです。

WHATWG に限らず、 ECMA Script の property descriptordynamic proxySymbol などなどのメタ API 郡も同じように解釈できそう。

低レベルは高レベルをかねる

ユーザは別に全ての API を HTML5Rocks に書かれた通りに組み合わせて使わないといけない訳じゃないんです。 それは一部のユースケースでしかない。

低レベルな API は、教科書の想像の範囲にとらわれず、自由に API を組み合わせて、自分が思う独自の世界をそこに気づき上げられる人たちの為に提供されています。どう使うかは、あなた次第。

ライブラリを作るもよし、フレームワークを作るもよし、誰も思いつかなかったような使い方で世界を変えたって全然構わない。

そうやってできたものが、 Polymer (polyfill 色が強いけど) や Peer.js みたいな高レベルな API を提供するライブラリになったり、それをコンセプトレベルで取り込んだ将来の Angular.jsFlux の先にある何かになったりすれば良くて。


そのままじゃ使いにくい という人はそれらを使えば良い。

開発者に可能性が増えたから、そのエコシステムの形成も進めやすい、これが重要。


そして生まれたユースケースから、欠けている API は提案すれば良いし、逆に頻出パターンにブラウザネイティブ実装の恩恵が欲しければ、より高レベルな API として提案するのもアリかもしれません。

少なくとも、ドラフトの上で生まれ ML で揉まれただけの API よりも、より洗練した実装に早くたどり着けそうな気はしますね。

Extensible Web の夜明け

マニフェストの公開は 2013 年なんで結構前ですが、そこで紹介されたような世界ってやっぱりすぐには実現しません。 仕様の策定とブラウザの実装に引っ張られない世界、それを実現するために必要な API だって、最初は仕様を策定しブラウザが実装する必要があるからです。

ServiceWorker や WebComponents は Polyfill じゃ完全には再現できないですし。


ところが、その実装も徐々に手元に届きはじめています。つまり、理想だった世界は徐々に現実味を帯びて来た感じがする。

リアルワールドな世界にシップするにはまだ早いかもしれませんが、それらの API を使ってアイデアを考え始めるには早すぎると言うことはありません。

自分の設計センスで高レベル API を提供するライブラリを作っても良い。 もっと全然別の、誰も思いつかなかったような使い方をしても良いはず。 そのアイデアが実現して広まるころには、ブラウザの実装ももっと進んで、リアルワールドに繋いでいけるようになっているはず。


自分は、こうして開発者が得た低レベル API という可能性に興味がある。そういうことを来年はやりたい。

草の根準備がちょっとづつ整って、仕様を書く側に偏ってしまった Web の進化の主導権が、コード書く側手に寄り戻ってくるというか、コード書く側が何かを形作って、リアルワールドからのフィードバックが健全に回る。

Web 開発者が、待ちのフェーズから、攻めのフェーズに移れる。

双方が適切な干渉をしあいながら、同じ過ちを繰り返さないようにちょっとづつ Web が進歩してゆく。


2015 年はそういう理想が、もっとリアルになっていくんじゃないかなと期待しています。というかなって欲しい。じゃないとつまらないので。

#extendthewebforward