レスポンスヘッダのexpiresとリロードの関係
レスポンスヘッダのexpiresとリロードの関係あんまり把握してなくてはまったのでメモ
下ごしらえ
expireとcache-controlのmax-ageを適切に設定してレスポンス返すようなコードをかく。
(一応検証に使ったコードは下に記述してあるので面倒な人はそっちをみてください)
リクエストする
リクエストの仕方によって結果代わる
URL直打ち(リンクたどってきたりする自然な動きした時)
成功しましたね。
hoge.jsのリクエストが 200 from cache
になりました
サーバーに対してステータスコードも聞きにいかずにブラウザのキャッシュから取得した状態です(最も早い)
リロード
次はリロード(F5)します
hoge.jsのリクエストが 304 Not Modified
になりました
サーバーまでステータスコード聞きにいってます
サーバーまで聞きにいってるのでexpiresの値も更新されます
スーパーリロード
次はスーパーリロード(ctrl + F5)します
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(); } }; }
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の方のコードまとめ
コミットメッセージとかマークダウンに絵文字
githubでいろんなリポジトリ見て回ってたらコミットメッセージに絵文字がはいってるのあってあーつかえるんだーってなったんでメモ
使える絵文字一覧
ここにまとまってた
実際にやってみたらこんな感じ
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: とかの絵文字はよく見ますね
2013年の思い出
仕事とか (あまり詳しいことかけない)
1月頃〜
フロントエンドエンジニア(だいたいJS)に転向(それまではサーバサイドがメインだった)
スマホ系のJS書く仕事がメインになった
すべてのAndroidを生まれる前に壊したい
— なぞみかん (@nazomikan) 2013, 6月 21
なるほど pic.twitter.com/sngWSDOgW3
— なぞみかん (@nazomikan) 2013, 7月 1
二日に一回くらいandroid爆発しろとかいってたけど、パフォーマンスチューニングはandroidは伸びしろかなりあるので楽しかった(近いうちにどこかでまとめると思う)
最後の方はandroid2.1の糞端末に感情移入してた
8月頃〜
何故かサーバサイドのパフォーマンスチューニングとかしてた
12月頃〜
コーダーに転向(といっても2割程度 サーバサイド:3割, JS: 5割, コーディング:2割程度)
それまでもプライベートではちょくちょく書いてたりはしてたけど仕事で通用するレベルでなかったので@forty4_jpや@magneto_opticalにお世話になりながらマークアップとかCSSとか学ぶ
日常
1/22
モンダミンを化粧水と間違う
1/28
乳液と歯磨き粉を間違う
4/13
地震を予知する
なんか今日はきそうですね
— なぞみかん (@nazomikan) 2013, 4月 12
翌朝
■■緊急地震速報(最終報)■■ 淡路島付近で地震 最大震度 6弱(推定) [詳細] 2013/04/13 05:33:18発生 M6.2 深さ10km #緊急地震速報
— 緊急地震速報bot(α) (@zishin3255_2) 2013, 4月 12
5/21
地震を予知する
なんかうっすら地震きそうな感じする。
— なぞみかん (@nazomikan) 2013, 5月 21
10分後
■■緊急地震速報(第5報)■■ 栃木県北部で地震 最大震度 4(推定) [詳細] 2013/05/21 20:25:49発生 M4.9 深さ10km #緊急地震速報
— 緊急地震速報bot(α) (@zishin3255_2) 2013, 5月 21
6月〜9月
なんかまぶたが重く感じるようになって周りの人になんか変だから病院いけっていわれていったらやばい病気の可能性あるとかで大学病院を紹介される
軽い検査して可能性が十分にあるとかで入院の準備させられる
本検査は一ヶ月くらいかかって劇薬つかうのとかあって検査中失神したり注射で一日6本抜かれたりして肉体的にも精神的にも限界が近かった
検体価値のある病気だったみたいで検査の度に見学にくる医大生いっぱいいて教授さんノリノリだった
1ヶ月以上かけて検査した結果何事もなかったということであっけなく解放された(手のひら返しすごかった)
6/17
洗濯機と間違えて脱いだ服をゴミ箱にすてる
6/19
傘と靴べらを間違えて持って家を出る
7/14
無印の歯ブラシたてを買う
すごくよい
8/3
洗濯機にバスロマンをぶち込む
10/5
水たまり飛び越えようとしてすべて背中強打した
— なぞみかん (@nazomikan) 2013, 10月 2
背骨(第七胸椎)折れてた。
10/22
背骨やってから毎日牛乳のんでたら体脂肪率4%増えた・・・
— なぞみかん (@nazomikan) 2013, 10月 21
太る
11/9
Node Knock Out 2013に@simejiと参加
初日が社員旅行最終日と重なって24時間しかなかったけどなんとか完成した
企画はサイゼリアで15分くらいで決めてあとはジャストアイデアで機能追加していった(尚、あまり評価は高くなかった模様)
http://nodeknockout.com/teams/kusattamikan
12/20
痩せる
牛乳を辞めて豆乳にかえたり色々してたら体重元にもどった
※カルシウム得るために牛乳飲んでたのに豆乳に切り替えたらカルシウムとれないという矛盾に気がついたのもこの辺
感動
Froschのあおいちゃんかわいすぎ
羽生さんすごすぎ(フィギュアスケート)
substackのfalafelすごすぎ
cuckooもvmモジュール使わずにfalafel使えばよかったと思った
作ったもの
PubsubJS
色々おもうことがあってライブラリを作った
わりと今でも気に入ってる
Coil
connectライクにパイプライン処理がかけるようにしたやつ
並列処理とか書きやすいしパラメータの引き継ぎ地獄とかあまり意識しないでいいので今でも気に入って使ってる
Cuckoo
exportsされてないやつをテスト用にアクセスできるようにするライブラリ
テストの本質を見失ってるような気もしながらvmモジュール面白かったからつくったやつ
ちなみにvmモジュールでやるよりもnode-falafel(ast)でやったほうが健全だったなと思った
ほかにもいろいろ
まとめ
健康は大事
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); }());
で、しばらくしてからこんなレスが。
evalでいいようなvar e=(function(){var h=1;return function(c){return eval(c)}})();e('alert(h)') // withとProxyを使ってフロントJSで外部からロnazomikan.hateblo.jp/entry/2013/06/…
— kyo agoさん (@kyo_ago) 2013年6月1日
こ、これはまさか!!!
var e = (function () { var h = 1; return function (c) { return eval(c); }; })(); e('alert(h)'); // 1
きっったーったったたてgmdfsklbmdfbl;ds、が^^^^ーーー!!!!!
@nazomikan 私もこのへん見てて「そういえばそんな感じのことができるなー」とか思いついてました // evalとnew Functionはちょっとだけ意味が違う - 金利0無利息キャッシング – キャッシングできます -subtech.g.hatena.ne.jp/mala/20121012/…
— kyo agoさん (@kyo_ago) 2013年6月1日
ひどくかんどうしました!!!
@kyo_ago さんありがとうございます!!
Nodeのテストを簡単にするCuckooモジュール
untestableなコードを簡単にテスト可能にするモジュール Cuckoo を作りました。
以前書いた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();