ぶれすとつーる

だいたいjavascript

レスポンスヘッダのexpiresとリロードの関係

レスポンスヘッダのexpiresとリロードの関係あんまり把握してなくてはまったのでメモ

下ごしらえ

expireとcache-controlのmax-ageを適切に設定してレスポンス返すようなコードをかく。

(一応検証に使ったコードは下に記述してあるので面倒な人はそっちをみてください)

リクエストする

リクエストの仕方によって結果代わる

URL直打ち(リンクたどってきたりする自然な動きした時)

f:id:nazomikan:20140114012036p:plain

成功しましたね。

hoge.jsのリクエストが 200 from cache になりました

サーバーに対してステータスコードも聞きにいかずにブラウザのキャッシュから取得した状態です(最も早い)

リロード

次はリロード(F5)します

f:id:nazomikan:20140114012400p:plain

hoge.jsのリクエストが 304 Not Modified になりました

サーバーまでステータスコード聞きにいってます

サーバーまで聞きにいってるのでexpiresの値も更新されます

スーパーリロード

次はスーパーリロード(ctrl + F5)します

f:id:nazomikan:20140114012540p:plain

200で実際にサーバーからレスポンス受け取ってますね(最も遅い状態)

まとめ

スーパーリロードすればexpire無視してサーバーに聞きにいくのは知ってたけどリロードでもステータスコードの確認しにいくんですね

しらずに効いてないと勘違いしてはまったナリ

検証につかったコード

var http = require('http')
  , connect = require('connect')
  , app
  ;
 
app = connect()
  .use(assetsExpire(1000 * 60 * 3))
  .use(connect.static('public'))
  .use(function(req, res){
    res.end(' \
<html> \
<head> \
<script type="text/javascript" src="/hoge.js"></script> \
</head> \
<body>hello world</body> \
</html> \
    ');
  });
 
http.createServer(app).listen(3000);
 
/**
 * 静的ファイル(js, css)のキャッシュ(expireの)設定
 *
 * @param {Number} expire expireの有効期間(単位ms)
 * @return {Function} middleware
 */
 function assetsExpire(expire) {
  var url = require('url')
    , path = require('path')
    ;
 
  // set default value
  expire = expire == null ? (1000 * 60 * 30) : expire;
 
  // connect-middlewareを返す
  return function (req, res, next) {
    var pathname = url.parse(req.url).pathname
      , ext
      , now
      ;
 
    // 拡張子取得
    ext = path.extname(pathname);
    if (!ext) {
      return next();
    }
 
    switch(ext) {
    case '.js':
    case '.css':
      now = (new Date(Date.now() + expire)).toUTCString();
      res.setHeader('Expires', now);
      res.setHeader('Cache-Control', "max-age=" + (expire / 1000));
    default:
      next();
    }
  };
}

gist

Nodeでホストごとに起動するアプリケーションを分ける

だいたいnodejs-jpのレスに書いたことと同じだけどメモ代わりに。

ピュアな感じで書く

とりあえずNodeのピュアなAPIだけでやる

まずはサーバをたてる

server.js

var http = require('http');

http.createServer(function(req, res) {

}).listen(3000);

ホストごとのアプリケーションのコードをかく

app1.js, app2.js

var http = require("http")
  , server
  ;
 
server = http.createServer(function (req, res) {
  res.writeHead(200);
  res.end("<html><body><p>hi i am nazomikan</p></body></html>");
});
 
module.exports = server;

ポイントとしてはここでサーバーをエクスポートするだけでlistenしないこと

server.js側でホストごとにサーバーわけてrequestを擬似的に発火させる

server.jsの続き

require('http').createServer(function(req, res) {
  var hostname
    , server1 = require('path/to/app1')
    , server2 = require('path/to/app2')
    ;
 
  // host無しならnot-found行き
  if(!req.headers.host) {
    return sendNotFound(res);
  }

  // ホスト名とポートを分けてホスト名だけ取得
  hostname = req.headers.host.split(":")[0];

  // ホスト名ごとのサーバーにrequestイベントを擬似的に発火
  switch (hostname) {
  case "hoge-iam-hidakaya.xxx.com":
    server1.emit('request', req, res); // requestイベントを擬似的に発火
    break;
  case "hoge-iam-nazomikan.xxx.com":
    server2.emit('request', req, res); // requestイベントを擬似的に発火
    break;
  default: // 指定したホストじゃない場合もnot-found行き
    sendNotFound(res);
    break;
  }
}).listen(3050);

/**
 * 404かえす
 */
function sendNotFound(res) {
  res.writeHead(404);
  res.end();
}

ピュアな方のコードまとめ

https://gist.github.com/nazomikan/8270802

適当でごめんなさい

expressとかconnectでやる

connect-middlewareにconnect-vhostという便利なやつがあります

var foo = express();
...

var bar = express();
...

var baz = express();
...

express()
  .use(express.vhost('foo.com', foo))
  .use(express.vhost('bar.com', bar))
  .use(express.vhost('baz.com', baz))
  .listen(80)

connectを生で使う場合は:%s/express/connect/gで。

たしかこっちはホスト名に正規表現もいけたはず(簡単なんだけど)

connectの方のコードまとめ

http://www.senchalabs.org/connect/vhost.html

コミットメッセージとかマークダウンに絵文字

githubでいろんなリポジトリ見て回ってたらコミットメッセージに絵文字がはいってるのあってあーつかえるんだーってなったんでメモ

使える絵文字一覧

ここにまとまってた

実際にやってみたらこんな感じ

commit message

f:id:nazomikan:20140103213413p:plain

commit 9bbf010f9833edf74a2f60066b1f065894a59039
Author: nazomikan <nazomikan@gmail.com>
Date:   Fri Jan 3 21:25:06 2014 +0900

    emoji test

    :bowtie: :smile: :laughing: :blush: :smiley: :relaxed: :smirk: 
    :heart_eyes: :kissing_heart: :kissing_closed_eyes: :flushed:
    :relieved: :satisfied: :grin: :wink: :stuck_out_tongue_winking_eye:
    :stuck_out_tongue_closed_eyes: :grinning: :kissing: :kissing_smiling_eyes:
    :stuck_out_tongue: :sleeping: :worried: :frowning: :anguished:
    :open_mouth: :grimacing: :confused: :hushed: :expressionless:
    :unamused: :sweat_smile: :sweat: :disappointed_relieved:
    :weary: :pensive: :disappointed: :confounded: :fearful:
    :cold_sweat: :persevere: :cry: :sob: :joy: :astonished: :scream:
    :neckbeard: :tired_face: :angry: :rage: :triumph: :sleepy:
    :yum: :mask: :sunglasses: :dizzy_face: :imp: :smiling_imp:
    :neutral_face: :no_mouth: :innocent: :alien: :yellow_heart:
    :blue_heart: :purple_heart: :heart: :green_heart:
    :broken_heart: :heartbeat::heartpulse: :two_hearts:
    :revolving_hearts: :cupid: :sparkling_heart: :sparkles: :star: :star2: 
    :dizzy: :boom: :collision: :anger: :exclamation: :question:
    :grey_exclamation: :grey_question: :zzz: :dash: :sweat_drops:
    :notes: :musical_note: :fire: :hankey: :poop: :shit: :+1: :thumbsup:
    :-1: :thumbsdown: :ok_hand: :punch: :facepunch: :fist: :v: :wave:
    :hand: :raised_hand: :open_hands: :point_up: :point_down:
    :point_left: :point_right: :raised_hands:
    :pray: :point_up_2: :clap: :muscle: :metal: :fu: :walking: :runner:
    :running: :couple: :family: :two_men_holding_hands:
    :two_women_holding_hands: :dancer: :dancers: :ok_woman: :no_good:
    :information_desk_person: :raising_hand: :bride_with_veil:
    :person_with_pouting_face: :person_frowning: :bow: :couplekiss:
    :couple_with_heart: :massage: :haircut: :nail_care: :boy: :girl: :woman:
    :man: :baby: :older_woman: :older_man: :person_with_blond_hair:
    :man_with_gua_pi_mao: :man_with_turban: :construction_worker: :cop:
    :angel: :princess: :smiley_cat: :smile_cat: :heart_eyes_cat: :kissing_cat:
    :smirk_cat: :scream_cat: :crying_cat_face: :joy_cat: :pouting_cat:
    :japanese_ogre: :japanese_goblin: :see_no_evil: :hear_no_evil:
    :speak_no_evil: :guardsman: :skull: :feet: :lips: :kiss: :droplet:
    :ear: :eyes: :nose: :tongue: :love_letter: :bust_in_silhouette:
    :busts_in_silhouette: :speech_balloon: :thought_balloon: :feelsgood:
    :finnadie: :goberserk: :godmode: :hurtrealbad: :rage1: :rage2: :rage3:
    :rage4: :suspect: :trollface:

この例では1行目には絵文字かかなかったけど別にどの行でもいれられる

GitHub Flavored Markdownでも使える模様

readme.mdでも普通に使えてた

そういえばissueとかで :+1: とかの絵文字はよく見ますね

f:id:nazomikan:20140103214716p:plain

2013年の思い出

仕事とか (あまり詳しいことかけない)

1月頃〜

フロントエンドエンジニア(だいたいJS)に転向(それまではサーバサイドがメインだった)

スマホ系のJS書く仕事がメインになった

二日に一回くらいandroid爆発しろとかいってたけど、パフォーマンスチューニングはandroidは伸びしろかなりあるので楽しかった(近いうちにどこかでまとめると思う)

最後の方はandroid2.1の糞端末に感情移入してた

8月頃〜

何故かサーバサイドのパフォーマンスチューニングとかしてた

12月頃〜

コーダーに転向(といっても2割程度 サーバサイド:3割, JS: 5割, コーディング:2割程度)

それまでもプライベートではちょくちょく書いてたりはしてたけど仕事で通用するレベルでなかったので@forty4_jpや@magneto_opticalにお世話になりながらマークアップとかCSSとか学ぶ

日常

1/22

モンダミンを化粧水と間違う

1/28

乳液と歯磨き粉を間違う

4/13

地震を予知する

翌朝

淡路で震度6弱 5府県で23人が重軽傷

5/21

地震を予知する

10分後

6月〜9月

なんかまぶたが重く感じるようになって周りの人になんか変だから病院いけっていわれていったらやばい病気の可能性あるとかで大学病院を紹介される

軽い検査して可能性が十分にあるとかで入院の準備させられる

本検査は一ヶ月くらいかかって劇薬つかうのとかあって検査中失神したり注射で一日6本抜かれたりして肉体的にも精神的にも限界が近かった

検体価値のある病気だったみたいで検査の度に見学にくる医大生いっぱいいて教授さんノリノリだった

1ヶ月以上かけて検査した結果何事もなかったということであっけなく解放された(手のひら返しすごかった)

6/17

洗濯機と間違えて脱いだ服をゴミ箱にすてる

6/19

傘と靴べらを間違えて持って家を出る

7/14

無印の歯ブラシたてを買う

item

すごくよい

8/3

洗濯機にバスロマンをぶち込む

10/5

背骨(第七胸椎)折れてた。

10/22

太る

11/9

Node Knock Out 2013に@simejiと参加

初日が社員旅行最終日と重なって24時間しかなかったけどなんとか完成した

企画はサイゼリアで15分くらいで決めてあとはジャストアイデアで機能追加していった(尚、あまり評価は高くなかった模様)

http://nodeknockout.com/teams/kusattamikan

12/20

痩せる

牛乳を辞めて豆乳にかえたり色々してたら体重元にもどった

※カルシウム得るために牛乳飲んでたのに豆乳に切り替えたらカルシウムとれないという矛盾に気がついたのもこの辺

感動

Froschのあおいちゃんかわいすぎ

羽生さんすごすぎ(フィギュアスケート)

羽生SP歴代最高得点

substackのfalafelすごすぎ

cuckooもvmモジュール使わずにfalafel使えばよかったと思った

substack/node-falafel

作ったもの

PubsubJS

色々おもうことがあってライブラリを作った

わりと今でも気に入ってる

PubsubJS

Coil

connectライクにパイプライン処理がかけるようにしたやつ

並列処理とか書きやすいしパラメータの引き継ぎ地獄とかあまり意識しないでいいので今でも気に入って使ってる

coil

Cuckoo

exportsされてないやつをテスト用にアクセスできるようにするライブラリ

テストの本質を見失ってるような気もしながらvmモジュール面白かったからつくったやつ

ちなみにvmモジュールでやるよりもnode-falafel(ast)でやったほうが健全だったなと思った

cuckoo

ほかにもいろいろ

まとめ

健康は大事

TJとsubstackすごすぎ

@mesoさんおつかれさまでした

あおいちゃんはかわいい

androidは爆発しろ

withとProxyを使ってフロントJSで外部からローカルスコープにアクセスする切り口

Proxyとwith使えばアクティベーションオブジェクト抜ける・・・?

とりあえずwithの動的スコープ内のアクティベーションオブジェクトはさし抜けた。

Function#toStringとwithとeval組み合わせればローカルメソッドにアクセスできるのでは・・・?

(function () {
  var obj = {}
    , activate
    ;

  activate = new Proxy(obj, {
    has: function (t, n) {
      return true;
    },
    set: function (t, n, v, r) {
      obj[n] = v;
    }
  });

  with (activate) {
    var a = 1;
    b = 2;
  }

  console.log(activate);
}());


で、しばらくしてからこんなレスが。



こ、これはまさか!!!

var e = (function () {
  var h = 1;
  return function (c) {
    return eval(c);
  };
})();
e('alert(h)'); // 1


きっったーったったたてgmdfsklbmdfbl;ds、が^^^^ーーー!!!!!




ひどくかんどうしました!!!

@kyo_ago さんありがとうございます!!

Nodeのテストを簡単にするCuckooモジュール

untestableなコードを簡単にテスト可能にするモジュール Cuckoo を作りました。

f:id:nazomikan:20130512050518j:plain

以前書いたNodeでプライベートな(exportsされてない)メソッドのテストをパス周りとかmodule周りとかきちんと細かいところまで作ったものです。

npmにインストールされてるのでこんな感じではいります。

npm install cuckoo 

たとえばこんな感じのファイルがあったとして

target.js

var util = require('util')
  ;

function untestableMethod() {
  util.isArray([1, 2, 3]);
}

exports.testableMethod = function () {
  return 1;
}

testableMethodの返り値テストとuntestableMethodのutil#isArrayの引数チェックとかのテストがしたいとします。

testableMethodはexportsされてるので問題ないんですのですがuntestableMethodはexportsされてないいわゆるprivateなメソッドなのでテストに難儀するわけですがcuckooを使えばこの辺が超楽にかけます。

var cuckoo = require('cuckoo')
  , assert = require('assert')
  ;

describe('#untestableMethod', function () {
  it('should have set the array to util#isArray', function () {
    var target
      , mock = {}
      ;

    mock.util = {
      isArray: function (ary) {
        assert.deepEqual([1, 2, 3], ary); // pass
      }
    };

    target = cuckoo.load('./target.js', mock);
    target.private.untestableMethod();
  });
});

describe('#testableMethod', function () {
  it('should get 1', function () {
    var target = cuckoo.load('./target.js')
      ;

    assert.equal(1, target.public.testableMethod());
  });
});

こんな感じ。

以前公開してた記事のコードではパス周りがloaderコードからの相対パスになってたりしたのですがmodule.parent.filenameとか駆使してこの辺をうまくテストコードからの相対パスになるようにしたりグローバル変数の追加だったりmodule周りのメソッドの拡充だったり色々しておきました。

テストで難儀してるかたは試してみると幸せになれるかしれません。

名前の由来はカッコーで、カッコーの特性の托卵(別の鳥の卵に自分の卵をまぎれさせて子育てさせる)がアクティベーションオブジェクトの差し替えやrequireのモックオブジェクト差し替えとかに似てるなーって感じでつけてます。

apiとかの細かい使用に関してはgithubのほうを参照ください。

timer#unrefで効率よくプロセスkillする

作ったアプリケーションが突然のエラーに見舞われることなんてざらにありますね。

で、エラーが起きたときはすみやかに、おだやかにそのワーカーをヌッコロしたいわけですがその終了作業中になんらかの処理がビジーな感じになって強制killしなきゃいけないこともあると思います。

インフラエンジニアな方々の貴重な休日を搾取しないためにもおだやかですみやかなプロセスkillは必須です。

これらを前提として適当にワーカーを殺す処理を書くとこんな感じになると思います。

var cluster = require('cluster')
  , domain = require('domain')
  ;

if (cluster.isMaster) {
  cluster.fork();
  cluster.fork();
} else {
  cluster.on('disconnect', function () {
    console.log('disconnect');
    cluster.fork();
  });

  doTask();
}

function doTask() {
  var d = domain.create()
    ;

  d.on('error', function () {
    var killtimer = setTimeout(function () {
        // 終了処理がなんらかの事情にビジー状態になってしまった時のために
        // 一律、30秒間待ってからプロセスを強制終了する
        process.exit(1);
    }, 30 * 1000);

    // --- 終了処理 ---

    // close some connections
    hoge.close();
    fuga.close();
    foo.close();

    // disconnect cluster
    cluster.worker.disconnect();

    // ----------------
  });

  d.run(function () {
    // メインななんらかの処理
    someTaskRun();
  });
}

これでserverやらなんらかの終了処理がビジーになったとしても30秒後には確実(終了処理で例外が発生しない限り)にworkerを殺すことができます。

ただ、無駄が多いですよね。

ビジー処理に見舞われずに正常に終了できた場合でも保険の30秒間はワーカーを維持することになります。

これはもったいないですね。

できることならば正常に処理が終了した後は30秒をまたずにworkerをヌッコロしたいです。


そんな時のために(?)、NodeのsetTimeoutはクライアントサイドのsetTimeoutと違い、timerオブジェクトを返すようになっており、timer#unrefメソッドを持っています。

unref()#

setTimeout() あるいは setInterval() が返す不透明な値は、 アクティブであるにもかかわらず、それがイベントループの最後の一つになっても プログラムの実行を継続しないタイマを作ることを可能にする、 timer.unref() メソッドを持っています。 既に unref されたタイマで再び unref が呼び出されても影響はありません。

setTimeout() が unref された場合、イベントループを起こすために独立した タイマが作成されるため、それらがあまりに多く作成されるとイベントループの パフォーマンスに悪影響を与えます -- 賢明に使ってください。

少し言葉が難しいですが、要するにtimer#unrefを実行すると、timerを返したsetTimeoutがイベントループの最後の処理になった時に、設定した時間をまたずにコールバックを実行して終わってくれるってことです。

こんな便利なものがあるのでさっきの処理はもっと効率よく書くことができる。

var cluster = require('cluster')
  , domain = require('domain')
  ;

if (cluster.isMaster) {
  cluster.fork();
  cluster.fork();
} else {
  cluster.on('disconnect', function () {
    console.log('disconnect');
    cluster.fork();
  });

  doTask();
}

function doTask() {
  var d = domain.create()
    ;

  d.on('error', function () {
    var killtimer = setTimeout(function () {
        // 終了処理がなんらかの事情にビジー状態になってしまった時のために
        // 一律、30秒間待ってからプロセスを強制終了する
        process.exit(1);
    }, 30 * 1000);

    // もし終了処理がうまくいってあとはタイマーを待つだけの状態に
    // なったらタイマーをまたずにsetTimeoutのコールバックを実行する。
    killtimer.unref();

    // --- 終了処理 ---

    // close some connections
    hoge.close();
    fuga.close();
    foo.close();

    // disconnect cluster
    cluster.worker.disconnect();

    // ----------------
  });

  d.run(function () {
    // メインななんらかの処理
    someTaskRun();
  });
}


無駄になることが多いであろうタイマー処理が無駄にならなくなって効率のよいコードになりました。

おしまい。


補足
ここでいうビジーっていうのはなんらか(closeとか)の非同期処理の向こう側で起きてるビジー状態なことをいってます。
これがもしNode側で無限ループとか発生してたらスレッドがブロックされるのでsetTimeoutのコールバックはいつまでたっても発火しないので検証の際はご注意ください




timer#unrefの挙動がよくわかんなかった人はこれを実行してみればなんとなくわかると思います。

var start = +new Date;

function randomElapsed() {
  var elapsed = (Math.random() * 10 + 1) << 0
    , timer
    ;

  console.log('timer is ', elapsed);

  timer = setTimeout(function () {
    console.log('done!', +new Date - start);
  }, elapsed * 1000);

  timer.unref();
  setTimeout(function lastTask() {
    console.log('last task is done', +new Date - start);
  }, 5000);
}

randomElapsed();