node-http-proxyで通信内容を変更するローカルプロキシサーバーを構築する
フロントエンドの開発をしていて、APIサーバーへのリクエスト、レスポンスをプロキシして、そのヘッダーを変更したいことがある。Charlesみたいなツールでも同じことはできるが、変更内容を複数人で共有するのが面倒だったり、integration環境で動かすのが難しい。ということで、Nodeでリクエスト・レスポンスのヘッダーを変更するちょっとしたscriptを書いてみた。 node-http-proxy(GitHub - nodejitsu/node-http-proxy: A full-featured http proxy for node.js)を使う。このモジュール自体はリバースプロキシとして使われることを想定していそうだが、今回のような開発用のローカルプロキシとしても使える。
var express = require('express'); var httpProxy = require('http-proxy'); var app = express(); var proxy = httpProxy.createProxyServer({}); proxy.on('proxyReq', function(proxyReq, req, res) { // append proxyReq.setHeader('X-REQUEST-FOO', 'BAR'); }); proxy.on('proxyRes', function(proxyRes, req, res) { var originalSetHeader = res.setHeader.bind(res); // append res.setHeader('X-RESPONSE-FOO', 'BAR'); res.setHeader = function(name, value) { // overwrite if (name === 'X-RESPONSE-FOO') { originalSetHeader('X-RESPONSE-FOO', 'BAR'); return; } originalSetHeader(name, value); }; }); proxy.on('error', function(err, req, res) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Something wrong.') }); app.get('*', function(req, res) { proxy.web(req, res, { target: 'https://api.example.dev', secure: false }); }); app.listen(8080);
後は、変更内容をyamlみたいな適当なフォーマットで記述して、起動時に読み込んで、リクエスト/レスポンスのヘッダーとして追加・上書きしてあげればよろしい。レスポンスのヘッダーの上書きがちょっとトリッキーで、proxyResイベントのイベントハンドラの後に元々のレスポンスのヘッダーに対して、setHeaderが呼ばれているようだったので、setHeaderをラップして、変更したいヘッダー名の場合は上書きするようにした。
これからの1日の過ごし方
下半期に入ったタイミングで、これまでの会社での1日の過ごし方を見直すことにした。
これまでの問題点として、自分の職務上、あちこちからメールやらチャットでアドホックに依頼や相談が来る。そういったあちこちから来たやつの対応が終わって、がっつり作業しようとしたら、また依頼や相談がきて… というのを繰り返して、お世辞にも効率的とは言えない時間の使い方をしていた。
そういった細切れになっている時間を種類ごとにまとめてバッチ化して、トータルでかかる時間を短くしたい、という気持ちが前からあって、今週から朝7時から16~17時を会社にいる時間と決め、さらにその時間を2分割することにした。(先週ぐらいから、とりあえず朝7時に来ていたけど、やはりバッチ化しないと意味がないことが分かった)
昼の13時まではがっつりコーディングや設計のような作業をする時間、昼休みから戻って17時までは、それ以外の細々した事務っぽい作業の時間にする。朝が早いのは、自分が農家の生まれで朝に強いのと(農家の倅は幼い頃から朝早くに叩き起こされ、親に山や田畑に連れ出されるので朝に強くなる)、最近 、急に暑くなってきたので、涼しいうちに出社したいから。
前半の朝の7時から13時までは、必死にコードを書く。TwitterやFacebookをログアウトして、HipChatやSlackみたいなチャットツールも開かない。(とか言いながら、トイレのために席を立つときにちゃっかりスマホで見ている)
出社してチャットツール開かなくて大丈夫なの?って思われそうだが、そもそも昼から出社するエンジニアがそれなりにいる会社だし、本当に緊急だったら自分にmentionが飛んでスマホに通知がくるので問題ないと思う。
一時間以上、作業し続けるとへばってしまうので、お茶とかコーヒーを飲みながら仕事して、都度、トイレのために席を立つようにしている。ここらへんポモドーロ法とかをやってみるのもいいかもしれない。
昼休みから戻って、後半の14時から17時は届いたメールをチェックしたり、Wikiやドキュメントを更新したり、企画者から相談を受けたり、企画書を読んでツッコんだりする。
その他、次の仕事で使いそうな技術の検証をしたり、午前中の作業で得られた知見をまとめたりする。
と、やっているとあっという間に16時半頃で、次の日のTODOをまとめて、明日出社したらすぐに仕事に取りかかれるようにしておく( 退社前に次の日のTODOリストを書く - kitak blog )。
気をつけないといけないのは下手に次の日にやればよい仕事に手を付けようとしないこと。手を付けたら、キリのいいところまでやろうとズルズル会社に残ってしまうので、明日やれば良いことは明日やれば良いと割り切る。
退勤したら、それなりにまとまった時間が取れるので、たまには手のこんだ夕食を作るなり、本を読むなり、好きなコード書くなり、Skype英会話をやるなり、ハッピーアワーで酒を飲むなり楽しみがある。
というのが、理想。で、今日1日やってみたかんじ結構イメージ通りに時間をマネージすることができた。ただ、17時に帰ろうとしたら、突然のゲリラ豪雨でこうやってブログを書いている。とほほ。
大奥 13 を読んだ
Vue.js 2.0+Spring Bootでサーバーサイドレンダリングを実現するサンプルを書いた
Vue.js 2.0+Spring Bootでサーバーサイドレンダリングを実現するサンプルを書いた
Vue.js 2.0がサーバーサイドレンダリングに対応した話は Vue.js 2.0のServer Side Renderingを試しつつ、軽くコードリーディングした - kitak blog に書いた通りで、その記事ではNode.jsでレンダリングをおこなった。ただ、個人的な理由で、Javaで出来るかどうか検証したかったのでGitHub - winterbe/spring-react-example: Isomorphic Spring Boot React.js ExampleをベースにReactをVue.jsに置き換える形で検証した。
サーバーサイドレンダリングの必要性
そもそも、なぜサーバーサイドレンダリングが必要か。Vue.js 2.0のServer Side Renderingを試しつつ、軽くコードリーディングした - kitak blog の記事では、利用者や検索エンジンでのクローラー視点で必要性を書いたが、加えて開発者の視点では、クライアントサイドのJavaScriptで扱うテンプレートとサーバーサイドのテンプレートを共通化できるというメリットがある。サーバーサイドとクライアントサイドでテンプレートが混在すると何が問題かというと、テンプレートの境界で脆弱性が発生したり( refs: AngularJSとサーバーサイドテンプレートの混在とngNonBindable ::ハブろぐ )、デリミタ({{ }}
)がクライアントサイドとサーバーサイドのテンプレートエンジンでダブってエラーになったり、無限スクロールを実装するときにサーバーサイドとクライアントサイドで同じ内容のテンプレートを記述することになり、メンテナンスが大変になったりする。
次からサンプルで重要そうな端々を紹介する。
Vue.js 2.0 の renderToString
Vue.js周辺のコードに関しては、先日書いた記事( Vue.js 2.0のServer Side Renderingを試しつつ、軽くコードリーディングした - kitak blog )の内容とほとんど同じなので説明を省く。テンプレートをVirtual DOMを返す関数に変換して、renderToStringを呼んでいるだけ。
Nashornを利用したレンダリング
NashornはJava8から搭載されているJavaScriptエンジン。以下のようにJavaScriptのコードをロードして、JavaScriptで定義された関数を呼び出すことができる。
NashornScriptEngine nashornScriptEngine = (NashornScriptEngine) new ScriptEngineManager().getEngineByName("nashorn"); nashornScriptEngine.eval(read("static/event-loop.js")); nashornScriptEngine.eval(read("static/nashorn-polyfill.js")); nashornScriptEngine.eval(read("static/server.js")); // ... Object html = nashornScriptEngine.invokeFunction("renderServer", comments);
読み込んでいるJavaScriptのevent-loop.jsやnashorn-polyfill.jsは、Vue.js 2.0のServer rendererがNode.js(v8)での実行をある程度想定しているので、以下の様な足りないconsoleやprocess、setTimeoutのpolyfillを用意する必要があるため、その定義。ここらへんちょっと微妙...
spring-vue-example/event-loop.js at 5c1f4734b0b0a0d4bdf21df3365ed2c560231da2 · kitak/spring-vue-example · GitHub
spring-vue-example/nashorn-polyfill.js at 5c1f4734b0b0a0d4bdf21df3365ed2c560231da2 · kitak/spring-vue-example · GitHub
var console = {}; console.debug = print; console.error = print; console.warn = print; console.log = print; var process = {}; process.env = {}; process.nextTick = function(fn) { global.setTimeout(fn, 0); };
サンプルのmasterブランチでは、NashornScriptEngineのインスタンスの管理、HTMLの生成を担うクラス VueRenderer を定義して、そのインスタンスが生成したHTMLをテンプレートに埋め込んでいる。最近のSpringだと、Script templatesという機能でNashorn, JRuby, Jythonから、各言語のテンプレートエンジンを利用することができるので、それを使ったサンプルもset-view-resolverブランチに用意した。
サーバーを起動して、アクセスしたところ、ページの表示やサーバー側とクライアント側のデータの同期処理( hydrationと呼ばれている )も意図通りおこなわれていることを確認した。この同期処理がうまくいかないと、サーバー側でレンダリングした内容がクライアント側のVue.jsが生成したDOM要素にまるっと置き換えられてしまって、サーバ側でレンダリングした意味がなくなる。
と、一見うまくいってそうに思えたのだけど、以下の問題がある。
既知の問題
spring-react-exampleでは、リクエスト毎にNashornScriptEngineのインスタンスを生成・スクリプトをロードしているが、自分が書いたサンプルでは、Browserifyで生成したファイルの読み込みに数秒かかってしまっているので(そもそもなんでそんなにかかっているのか、という気もするが... 時間があるときに調べる)、起動時に一度ロードしたものを再利用している。コードを読んだ限り、renderToString関数の呼び出し間で値の共有は無いので、並列に実行されることで、競合は起きないように思えるが、正直Javaに疎いので自信がない。一度読み込んでしまった後であれば、実行は体感的に早いように感じる。
どの程度パフォーマンスが出るか、abで負荷をかけてみたが、サーバー起動から17回目のリクエストでViewModelのオブジェクトを想定しているthisがundefinedになってしまった。これが面白くて何度起動し直してもぴったり17回目で問題が起きる。あれー...と思って、自分の書いたプログラムかVue.jsのどちらかの問題かなぁ、と疑ってかかっていたが、根気よく調べたら、Nashornのバグであることが発覚した。
再現コードこんなかんじかなぁ... https://t.co/luPmH0G4Pd
— kitak / Keisuke Kita (@kitak) 2016年6月21日
報告して、バグとして登録してもらった。Bug ID: JDK-8160034 The `this` value in the `with` is broken by the repetition of a function call
実現はできているが、上のNashornのバグもあるし、Vue.js 2.0もまだalphaなので、実際にproductionに採用するには、まだまだというところ。仮にNashornのバグが修正されて(あるいはNashornのバグを回避するために、Virtual DOMを返す関数を自分で書くという手もあるが、うーん...)、Vue.js 2.0の正式バージョンがリリースされたとしても、Node.js( V8 )をある程度前提にして書かれたコードをNashornで動かすことや、Nashornをレンダリングに利用して安定的に運用する、という課題を解消するためにそれなりにコストがかかりそう。そのコストを払ってでも、先に挙げたサーバーサイドレンダリングで得られるものが重要なのであれば、おこなう価値はあるのではなかろうか。
しばらくはVue.js 2.0 開発版のリリースに追従しつつ、寝かせるつもり。
Rx.jsのControlled observableを試した
ObservableのsubscribeOnNextに渡した関数(あるいはsubscribeの第一引数として渡した関数)でおこなう副作用の伴う処理、非同期処理が終わるまでの間、値の発行を止めたくなった。
受け取ったデータを蓄えつつ、受け取った順にひとつずつ一定時間表示するようなケース。(この記事を書いていて、わんこそばが頭に浮かんだ)
ひとつの非同期処理が終わるのを待って、次の非同期処理を実行するという点では、先日、書いた記事( async/awaitをつかった非同期処理の直列実行 - kitak.blog )と同じ。異なるのは、先の記事では、事前にトータルで何回非同期処理を行うか明らかになっているのに対して、今回は離散的に届くデータを扱うので、非同期処理をおこなう回数は未定という点。
色々調べたところ、Controlled observable( RxJS/backpressure.md at b0e20aab69b28ff0cbab68e536caff9533e50dfb · Reactive-Extensions/RxJS · GitHub )を使えば実現できそうだったのでメモしておく。
var Rx = require('rx-lite'); // 適当にtimerからObservableを生成しているが、WebSocketやUIイベントに置き換えること var source = Rx.Observable.timer(200).controlled(); source.subscribe((x) => { displayReceivedData(x) .then(() => { source.request(1); }); }); source.request(1);
Controlled observableはObservableのcontrolledメソッドを呼ぶことで生成でき、requestメソッドで受信する個数を指定できる。やりたいことに対して、Controlled observableを使うのはやりすぎな気もするので、もっと良いやり方があったら教えてください。
async/awaitをつかった非同期処理の直列実行
最近、非同期処理を直列に実行するプログラムを書く機会が多くあったのでメモ。
この間、自分が書いたのは、以下の様なもの。
- ユーザーは複数のデータを選択できる
- ユーザーが確定ボタンを押したら、選択した最初のデータについて、Web APIを呼び出して、そのレスポンスの内容によってはダイアログを表示する
- ユーザーがダイアログを閉じたら(これはダイアログを開くAPIにコールバックを渡すことでハンドリングできる)、次のデータについて上と同じ処理をおこなう。全てのデータにこれをおこなう
非同期処理を直列に実行する場合のイディオムとして知られているのが、「Promiseを返す」関数を返すクロージャを用意して、そのクロージャの配列でreduce操作をおこなうもの。コードにするとこんなかんじ。
const genWait = (n) => { return () => { return new Promise((resolve) => { console.log(`Waiting ${n}ms...`); setTimeout(() => { resolve(n); }, n); }); }; }; const genPromiseFns = [ genWait(3000), genWait(1000), genWait(2000) ]; const done = genPromiseFns.reduce((previous, fn) => { return previous.then((n) => { return fn(); }); }, Promise.resolve(0)) done.then((n) => { console.log('done all'); });
reduceのコールバックの第一引数に先の非同期処理のPromise(最初はreduceの第二引数)が渡されるので、その解決を待って、第二引数のクロージャを呼び出す。doneで配列の末尾の要素のクロージャの返したPromiseを参照できるので、それが解決されれば、全ての非同期処理が終わったことになる。
これはイディオムとしてよく知られたものではあるのだけど、知らない人からすれば非同期処理を直列に実行しているように理解しづらいのと、reduce周辺のコードを書くのがややこしい(個人の感想)。 ECMAScriptの仕様の策定途上にあるasync/await(Async Functions)を使えば、非同期処理があたかも同期処理のように書ける。
const genWait = (n) => { return () => { return new Promise((resolve) => { console.log(`Waiting ${n}ms...`); setTimeout(() => { resolve(n); }, n); }); }; }; const genPromiseFns = [ genWait(3000), genWait(1000), genWait(2000) ]; (async () => { for (let fn of genPromiseFns) { await fn(); } console.log('done all'); })();
策定プロセスのstage3(仕様は完成している状態)だから、Babelのpreset(babel-preset-stage-3)を使って、ぼちぼち実戦で使おうかな、と思いつつ、普段書くPromiseを扱う処理がそんなに複雑じゃなかったので(せいぜい2つか3つかの固定の数のPromiseをPromise.allで待ち受ける程度)、スルーしていた。モッタイナイ。