3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

WACULAdvent Calendar 2015

Day 15

ServiceWorkerを使ってLZ4+MessagePackで通信してみる

Posted at

概要

sw.png

あるサイトではクライアント側(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とかにそれらしいものが入っているはず。

スクリーンショット 2015-12-15 14.45.58.png

各ファイルについての解説

クライアント(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-Encodinglz4ならLZ4、Content-Typeapplication/x-msgpackならMessagePack、レスポンスに関してはX-Accept-Encodinglz4なら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が消えてインスペクタをもう一回立ち上げ直すのが面倒臭すぎます。

参考

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?