C/C++ 素人が http_parser を追ってみた
この記事は Node.js Advent Calendar 2013 - Adventar の15日目です。
Node.js については最新の安定版 v0.10.23 を元にしています。
http_parser とは
Node.js の初代ゲートキーパー Ryan Dahl が開発したC言語で書かれた http parser です。 このライブラリは Node.js の Addons と同様 v8 が C++ と JavaScript を繋ぎこむ仕組みを利用して読み込まれています。
Python の meinheld や RuBy の bossan といったサーバでも利用実績があるようです。
Node.js では http クラス内部で利用されています。
http_parser の基本的な使い方
Readme.md に書かれている通りですが
- http_parser_settings 構造体の変数を用意し、コールバックを登録
- http_parser 構造体の変数を用意し、HTTP_REQUEST か HTTP_RESPONSE(列挙型)を指定して、http_parser_init 関数で初期化
- http_parser 構造体と hbttp_parser_settings 構造体の参照、バッファ、バッファの長さを引数として http_parser_execute 関数を実行
- http_parser_execute 関数は parse した数を戻り値として戻し、http_parser 構造体のメンバに結果を格納、パースの過程で http_parser_settings 構造体に登録されたコールバック関数を利用します。
Node.js 内部での利用方法
前述の通り Addons の仕組みとほぼ同様です。 node_http_parser.cc に実装がありますが
NODE_MODULE(node_http_parser, node::InitHttpParser) で InitHttpParser 関数を node_http_parser という名前に紐付るので、初めて process.binding(’http_parser’) が呼び出された際に InitHttpParser 関数が実行されます。
InitHttpParser 内ではクラス初期化時の処理を C++ のクラス Parser の New と紐付けたり、クラス名 HTTPParser をセットしたり、クラス初期化時に http_parser_type 列挙型を受け取る準備をしたり、execute、finish、reinitialize、pause、resume といったメソッドと C++ で書かれた Parser クラスのメソッドを紐付けたり、コールバック関数の準備をしたりします。
こうした準備を経て、http.js 内から HTTPParser が JavaScript と C++ の間を渡って利用できるようになっています。
初歩的な例
Node.js 公式サイト にある簡単な例を見てみましょう。
var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World\n'); }).listen(1337, '127.0.0.1'); console.log('Server running at http://127.0.0.1:1337/'); To run the server, put the code into a file `example.js` and execute it with the `node` program from the command line: % node example.js Server running at http://127.0.0.1:1337/
このスクリプトを実行すると example.js が読み込まれ http クラスが利用されているので InitHttpParser が呼び出されます。 その後サーバが起動し、リクエストを待ち受けはじめます。
このサーバにアクセスすると connection イベントが発生し、connectionListener 内で parsers.alloc() が呼ばれ var parser = new HTTPParser(HTTPParser.REQUEST);が実行されます。 ここで Parser::New が呼び出されます。 Parser::New は引数のキャストとチェックを行った後、http_parser_type を引数に Parser のコンストラクタを呼び出します。 コンストラクタはそのまま引数を Parser::Init に委譲します。
Parser::Init では http_parser_init 関数の呼び出しと各種変数の初期化が実行されます。
こうして初期化された HTTPParser クラスにコールバック関数が設定され、リクエストを受け取る準備が出来ました。
リクエストは SocketOnData で受け取られ、この中で praser.execute(Parser::Execute) が呼び出されます。
http_parser_execute の実行
Parser::Execute は コメント にもありますが、buffer、開始位置、長さを引数として受け取ります。
この後おなじみの C++ への型変換等があっていよいよ http_parser_execute が呼び出されます。
さてどのようにパースが行われているのか見てみると大量の switch-case 文 が並んでいます。
parser->state を元に判定するこの switch-case 文ですが、初期値は http_parser_init 時に決定されています。 http_parser 初期化時の http_parser_type が HTTP_REQUEST なら s_start_req、HTTP_RESPONSE ならば s_start_res です。
こうして状態ごとに parser->state を切り替え、ていねいにパースしていき、パース結果は http_parser 構造体に格納され、最終的に JavaScript へと変換されます。
まとめ
最初に http.js について教えていただいたのは id:jovi0608 さん主催の Node.js 道場でした。 ここで学んだ際に http_parser が C のライブラリであることを認識し、興味を持ったと思います。 また、この時学んだ内部実装が基礎となり http_parser を理解できたと思います。
Node.js 道場は、こちらの本を元に進めていました。 一年前の出版なのでバージョンは進んでしまったとは思いますが基礎的な部分は変わっていないかと思います。 ご興味があったらぜひ読んでみてください。
※ アサマシリンクになっています。よろしければ。。。
※ 電子書籍は達人出版会で購入可能です。
Node.js 道場の課題については Qiita にも上がっていますので、こちらもご興味がありましたらどうぞ
http://qiita.com/mazzo46@github/items/1b1fac54d72110ebc508
先日の jovi0608 さんのエントリーにもあったように、最近 Node.js コミュニティでは、非常に精力的にコントリビュートしてきたメンバーが去るという悲しい出来事がありました。
ご当人は http_paser にも精力的にコミットしていたそうです。彼の素晴らしい貢献に対して感謝したいと思います。
以前聞いた話だと http_parser を JavaScript 化するという試みもあるようです。1.0 も間近となり、今後共目が離せないですね。
終わりに
自分は普段 LL 中心で C/C++ はほとんど触ったことがなく、コードを追うのに最初は Eclipse から始めて、途中で gdb を直接使うようになりました。 gdb は慣れてしまうと irb や perl -d などと似たところが多く(多分 gdb を手本にして作られたからですよね)だいぶ手に馴染んできた気がします。 ただ Mac ではうまくブレークポイントが設定出来なかったり、gdb のプロセスを信頼させる おまじない が必要なので、可能ならば Linux 環境を使うとはまりにくいイメージがあります。
この文章には間違いが含まれている可能性があります。ツッコミ歓迎ですので間違いなどありましたら教えていただけると嬉しいです。
YAPC::Asia Tokyo 2013 に参加してきた
見たセッション
Day 1
- オープニング
- Postcards from the Edge: The State of Perl 5 Development
- PSGI/Plack・Monocerosで学ぶハイパフォーマンスWebアプリケーションサーバの作り方
- BrowserStack を用いたクライアントサイドのテスト
- ランチセッション
- Inside amon2-livedoor-setup.pl with web application
- SPDY、HTTP/2.0の使い方
- Types and Perl LanguageTypes and Perl Language
- モダンPerlリファクタリング
- Lightning Talks Day 1
Day 2
- Perlで書く結合テスト
- これからのPerlプロダクトのかたち
- ランチセッション
- YAPC::Asia Tokyo 2013 特別座談会 「Rubyの良いところ語ってください 〜そんなPerlで大丈夫か?〜」
- 本当にあったレガシーな話本当にあったレガシーな話
- スマフォアプリ開発を支える認証認可アーキテクチャ
- PhantomJSによる多岐にわたる広告枠の確実な表示テスト
- フルテストも50msで終わらせたい 〜 FreakOutの取り組み 〜
- Lightning Talks Day 2
自分は元々 Perl をやっていたんですが、今は Rails 開発が主体なので今回は参加を見送ろうと思っっていました。
しかし、Ruby のセッションがある事、知人のトークがある事などから参加する事に決めました。
いざ参加してみると、聞きたいセッションが重なってしまい、どれに行こうか悩んだりすることもあって、YAPC はやっぱり懐の深いイベントだと思い
ました。
互換性を重視する Perl と変化を受け入れる Rails(Ruby) の文化
今回自分が見たセッションを通じて Perl の互換性を重視する文化は Perl コミュニティ自身を苦しめているのではないかと感じました。
初日のキーノートの中で言われていた「いつ妥協するか」という言葉などがそれを感じさせました。
Rails 界隈では変化を許容する事を前提にして、システムを常に改善する為の体制作り、開発手法について良く話されている気がします。
よくある話だと思いますが、プロダクト改修の予算を確保する事は難しく、後回しにされ、最終的に手の施しようがなくなった頃に大改修をする事になったりします。
テストが当たり前にあって、システムのバージョンアップが苦にならないような体制が、スーパーエンジニアのいない普通の開発現場にも当たり前にある未来が早く実現すれば良いなと思います。
その一歩としてこういったカンファレンスは指針を示し、エンジニアに刺激を与えてくれる素晴らしい機会だと思います。
イベントでの出会いについて
自分は昔から人が良く、人に合わせがちで自分の意見を押し殺しがちなので、一人で行動する事を好みます。
しかし、こういった機会に自分と同じ問題意識、課題を共有できる人と出会い、新しい環境に飛び込める人はとても幸運だと思います。
残念ながら自分はそういった幸運に恵まれませんが、幸運な人を横目に悔しさを貯めていつか幸運をつかむ為に今出来る事を探し続けます。
lestrrat さんと 941 さんの卒業
予定があって2日目は LT の後会場を離れてしまったのですが、twitter のハッシュタグは追っていたのでほぼリアルタイムで気付きました。
これだけの規模のイベントの準備や取りまとめはかなりの苦労があった事と思います。
心より感謝の意を評したいと思います。
お疲れ様でした!素晴らしいイベントをありがとうございました!!
自分もイベントのスタッフをしていますが、そのきっかけは、YAPC のスタッフ募集が締め切られた後にちょうどそのイベントのスタッフ募集があったからです。
要するに YAPC のスタッフをやろうかどうか迷っていて、応募し損ねたからだったのです(笑
今年もそのイベントがあります。
興味のある方は参加をご検討いただけると嬉しいです。
java-ja.ddd
始まったばかりだけど
グリーぱねぇ!
java-ja 怖そうって思ってたけど、結構居心地良かったよ!
yoshiori さん、yamashiro さん最高です。
ありがとうございました!
これ久しぶりのエントリーなんだよね。。。
Qiita に Play! 2.0 / Scala の小ネタを投稿した
ここ数ヶ月集中的にやっていたので軽い Tips を投稿してみた。
あんまりお役に立てる内容ではないかもしれないけどとりあえずという事で。
play console については頻繁に同じ事をするんであればテストを書いた方が良いと思います。
play console で model の動作などを確認したい場合
Play 2.0 / Scala の WS で同期リクエスト
あと一つ MultipartFormData でファイルを必須にする場合のエラーハンドリングについてのネタがあるけど良いサンプルが書けないので今度気が向いたら書くかも。今のところ基本的にはファイルアップロードのフォームは他のフォームと別に用意する必要がありそうです。
https://groups.google.com/d/msg/play-framework/2xmfDdv0xss/P3w6Fw6uBQUJ
なんとかして混ぜてやりたい場合リクエストのボディーをパースして独自にチェックしてエラーがあった場合 play.api.data.Form にエラーを詰めて返すとかすれば出来そうかも。
node-isaacs
この記事は 東京Node学園祭2012 アドベントカレンダー : ATND の 12 日目の記事です。
僕の記事で大分ハードルが下がったと思います。まだまだ参加者募集中です!
npm モジュールランキング No.2
node-isaacs は 東京Node学園祭2012 で来日される substack(James Halliday) さんのモジュールです。
npm モジュールランキングは同じく substack さんの npmtop で確認する事が可能です。
$ npm install npmtop -g $ npmtop rank percent packages author ---- ------- -------- ------ 1 1.25 % 221 tjholowaychuk 2 1.05 % 186 substack 3 0.77 % 136 raynos 4 0.70 % 123 dominictarr 5 0.66 % 117 coolaj86 6 0.54 % 95 architectd 7 0.49 % 87 jaredhanson 8 0.47 % 83 isaacs 9 0.46 % 82 damonoehlman 10 0.46 % 82 marak 11 0.44 % 78 fractal 12 0.43 % 76 gozala 13 0.40 % 70 indexzero 14 0.38 % 67 balupton 15 0.37 % 65 ryanflorence $ npmtop --who mikeal rank percent packages author ---- ------- -------- ------ 78 0.14 % 25 mikeal
今回来日される海外スピーカーの皆さんはこんな感じのランキングなんですね。
※ ブログ執筆時点(2012/10/26)のランキングです。
新しいゲートキーパー
このモジュール は言わずと知れた New gatekeeper Isaac Schlueter 氏の誕生日に彼の為に書かれたモジュールとされています。
使い方は README.md に書かれている通りですが、一番シンプルなのは
$ npm install isaacs -g $ isaacs -n 3 ok so i write the robots support them we should windows usually. allowed on are also forward slashes cygwin uses but if you specify a url? to a full absolute path, would that break npm? Error: ENOENT, directory '/usr/local/lib/node/.npm/connect/0.2.1-LINK-26633a47/package/bin/connect' No such file or $ isaacs > oh hello it (for and testing the sample working on of time a lot you are I do using env, what should > pretty much to go smart way probably a decided it's well. I parser and just studied ryan's http > you can do it, you just have to have belief in belief I'll just randomize it it is so people keep thinking
とグローバルインストールして、オプション n で行数を指定する、またはインタラクティブにキーワードを入力して会話を楽しみます(笑)
node-markov
isaacs コマンドの実体は bin/cli.js ですが、この中で最終的に izs.speak() しています。これは index.js 30行目から33行目 で定義されていて、その前の行で node-markov の markov オブジェクトが作成されています。 このオブジェクトはデフォルトの order が 2 に設定されていて、引数に設定された order で seed が実行された際に辞書の様な物を作ります。 仮に markov(2).seed('This is a test.') したとすると、内部の db という変数に以下の様に格納されます。
db: { this_is: { count: 1, words: { 'This is': 1 }, next: { a_test: 1 }, prev: { '': 1 } }, a_test: { count: 1, words: { 'a test.': 1 }, next: { '': 1 }, prev: { this_is: 1 } } }
isaacs コマンドが実際にやっている事は結構単純で、引数なしの時はコマンドラインから入力されたキーワードを元に respond を呼び出し、引数 n で行数指定された時は pick で適当な文字列を取り出し、それを respond に渡して結果をスペース区切りで join して出力しているだけです。
respond 内部では与えられたキーワードを search して、該当するキーワードがない場合は pick しています。 pick は node-deck というモジュールを使って先ほどの変数 db からランダムに key を返しています。
respond は最終的にキーワードか pick から帰ってくる key を元に fill を呼び出し、引数に合致する words から前後を順番に配列に unshift しています。結果的に以下の様な感じになります。
$node -e "var m = require('markov')(); m.seed('This is a test for markov.'); console.log(m.respond('This is'))" [ 'for markov.', 'a test', 'This is' ] $node -e "var m = require('markov')(); m.seed('This is a test for markov.'); console.log(m.respond('a test'))" [ 'for markov.', 'This is', 'a test' ] $node -e "var m = require('markov')(); m.seed('This is a test for markov.'); console.log(m.respond('for markov'))" [ 'This is', 'a test', 'for markov.' ] $node -e "var m = require('markov')(); m.seed('This is a test for markov.'); console.log(m.respond('is this'))" [ 'for markov.', 'a test', 'This is' ] $node -e "var m = require('markov')(); m.seed('This is a test for markov.'); console.log(m.respond('is this'))" [ 'for markov.', 'This is', 'a test' ]
Stream
ところで node-isaacs では seed に stream が使われています。node-markov は stream も seed として受け取れる様になっています。
このファイルはどうやって作られたかというと インストール時 に curl で読み込まれていました。
http://substack.net/data/isaacs.txt
package.json はこんな事も出来るんですね。でもセキュリティ的にどうなんだろう。
ちなみに substack さんは The Stream Handbook という本を書きはじめています。完成が楽しみな原稿です。
https://github.com/substack/stream-handbook
この原稿は日本Node.jsユーザー会の代表である meso さんが翻訳しているので英語が苦手な方でも大丈夫です。
https://github.com/meso/stream-handbook
まとめ
このモジュールを見つけたのは彼の同僚 pkrumins(Peteris Krumins) さんのこの記事を見たからでした。
http://www.catonmat.net/blog/browserling-open-sources-90-node-modules/
少し古い記事ですが、一つのプロダクトに 90 もの自社製でオープンソースなモジュールを使っているなんてすごいですね!
学園祭当日どんな話が聞けるのか楽しみです!!
ブログ移転しました && 実は転職してました;
転職について
2週間前から虎の門のとある企業で働いています。
とりあえず Perl と Node.js、Titanium mobile を使うお仕事になりました!
もしかしたら Scala もやる事になるかもしれません。わくわくしますね!!
残念ながら業務として Ruby に触れることがなくなってしまったので、お世話
になった方々と疎遠になってしまうかもしれません。。
ただし Rails はやっぱりフレームワークのお手本だと思うし、学ぶべき事は多
いと思うので出来る限り勉強したいと思います。
あー、そういえば Python にもあんまり触れてないなー。。
ブログ移転について
祝!はてなブログへのインポート機能提供開始という事で、区切りも良い事で
すし、早速ブログ移転してしまいました。今後ともよろしくお願いします!