概要
あるサイトではクライアント側(script.js)でXMLHttpRequestをつかってjson形式でデータの送受信を行っています。ところで、サーバ側(app.js)では圧縮形式とデータの形式を指定してやるとよしなにハンドリングしてくれるようなかんじになっていました。なにも考えずに通信を行えばjsonでやりとりされますが、そこにプロキシとしてServiceWorker(sw.js)をかませば、クライアント側の変更は無しで、ぼくがかんがえたさいきょうのデータ形式で通信することができます。
で、今回はLZ4+MessagePackでやってみたという話です。
ローカルで動かす
ServiceWorkerはlocalhost以外はhttps必須なので以下のデモをローカルで実行して下さい。
git clone https://gist.github.com/a6a4ae1b5197c18328d2.git sw-test
cd sw-test
npm install
node app.js
node v5.1.0、Chrome 47で動作確認しました。正しく動作すれば、contentType
とかにそれらしいものが入っているはず。
各ファイルについての解説
クライアント(script.js)
なんの変哲もないコードです。ServiceWorkerをインストールして、sendButtonをクリックしたらtextareaに書かれたjsonを送りつけるだけです。インストールする部分はHTML5 Rocksから拝借しました。
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(function(registration) {
// 登録成功
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(function(err) {
// 登録失敗 :(
console.log('ServiceWorker registration failed: ', err);
});
}
window.onload = function() {
var textarea = document.querySelector('textarea');
var sendButton = document.querySelector('#sendButton');
var formatButton = document.querySelector('#formatButton');
var pre = document.querySelector('pre');
formatButton.addEventListener('click', function() {
textarea.value = JSON.stringify(JSON.parse(textarea.value), null, 2);
});
sendButton.addEventListener('click', function() {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'json';
xhr.send(textarea.value);
xhr.onload = function() {
pre.textContent = JSON.stringify(xhr.response, null, 2);
};
});
};
サーバ(app.js)
/api
に対してpostされたリクエストのheaderをみて処理を振り分けます。X-Content-Encoding
がlz4
ならLZ4、Content-Type
がapplication/x-msgpack
ならMessagePack、レスポンスに関してはX-Accept-Encoding
がlz4
ならLZ4にして返すというかんじです。ちなみにX-hoge
となっているところはexpressに怒られたので付けただけです。
var express = require('express');
var bodyParser = require('body-parser');
var lz4 = require('lz4-asm');
var msgpack = require('msgpack-lite');
var app = express();
app.use(express.static('.'));
app.use(bodyParser.raw({type: '*/*'}))
app.post('/api', function(req, res) {
var buff = new Uint8Array(req.body);
var obj, ret;
console.log('byte length: ' + buff.length);
if (req.headers['x-content-encoding'] === 'lz4') {
buff = lz4.decompress(buff);
}
if (req.headers['content-type'] === 'application/x-msgpack') {
obj = msgpack.decode(new Buffer(buff));
} else {
obj = JSON.parse(new Buffer(buff).toString('utf8'));
}
obj.contentType = req.headers['content-type'];
obj.contentEncoding = req.headers['x-content-encoding'];
console.log(obj);
if (req.headers['x-accept-encoding'] === 'lz4') {
ret = new Buffer(lz4.compress(new Uint8Array(msgpack.encode(obj))));
} else {
ret = new Buffer(JSON.stringify(obj));
}
res.send(ret);
});
app.listen(3333);
ServiceWorker(sw.js)
/api
に対してリクエストするときだけjsonとLZ4+MessagePackの変換をするだけ(変換部分がやや雑な気がしますが)。時間がなかったのですが、見た感じfetch APIではStreamが使えるっぽいのでそのうち試してみたいですね。
importScripts(
'node_modules/lz4-asm/lz4.js',
'node_modules/msgpack-lite/dist/msgpack.min.js'
);
self.addEventListener('fetch', event => {
// apiだけlz4+messagepackで通信するようにする(手抜き)
if (/api/.test(event.request.url)) {
var headers = new Headers();
headers.append('X-Content-Type', 'application/x-msgpack');
headers.append('X-Content-Encoding', 'lz4');
headers.append('X-Accept-Encoding', 'lz4');
event.respondWith(
event.request.json().then(o => {
return fetch(event.request.url, {
method: event.request.method,
body: new Blob([lz4.compress(msgpack.encode(o))], {type: 'application/x-msgpack'}),
headers: headers
});
})
.then(response => response.arrayBuffer())
.then(buffer => new Response(new Blob([JSON.stringify(msgpack.decode(lz4.decompress(new Uint8Array(buffer))))])))
);
} else {
event.respondWith(fetch(event.request));
}
});
まとめ
ServiceWorkerは結構面白いですね。特に良いなと思ったのは以下の3つ。
- クライアントの既存コードを書きかえる必要がない
- 結構重い処理を書いてもWorkerなので画面が止まらない
- なんでもできる感
ただ、開発について、Chromeのインスペクタは便利なんですが、ファイルの更新をしたら古いWorkerが消えてインスペクタをもう一回立ち上げ直すのが面倒臭すぎます。