Skip to content

Instantly share code, notes, and snippets.

@geta6

geta6/node.md Secret

Last active September 26, 2022 18:21
Show Gist options
  • Save geta6/29f41e22a97380ea20fb to your computer and use it in GitHub Desktop.
Save geta6/29f41e22a97380ea20fb to your computer and use it in GitHub Desktop.
Node.js初級入門

Node.js初級入門

  • @geta6
  • 趣味: エロゲ

宣伝

  • glide.soというサービスを作りました
    • Gistからスライドを生成してくれます
    • h1とh2で区切ってくれる
    • 今のところそれだけ

Node.jsってなに?

  • みなさんjavascriptはご存知ですよね
  • そのjavascriptを使ってサーバで書ける言語
    • ブラウザいらない
    • ファイルとかにアクセスできる

Node.jsをインストール

  • 最新版を入れます (0.10.16)
    • 更新頻度が高いので追いかける
    • 0.10.16が8/16にリリースされました^^
  • 古いバージョンは今すぐrm -rf

インストール方法

  • OSX
    • brew install nodejs
  • Linux
    • ./configure && make && make install
  • Windows
    • node.exeをダウンロードする

実行のしかた

node app.js

バージョンの見方

  • 0 . 10 . 16
    • 10をメジャーバージョン
    • 16をマイナーバージョンと呼びます
  • 1.*は、技術的な問題とは切り離してリリースされるそうです

バージョンアップの見極め方

  • 奇数メジャーバージョンはインストールしない(7,9,11..)
    • Nightlyなので、深刻なバグに遭遇しない限り放置でよい
  • バージョンアップに強いコードを書く

バージョン間の互換性

  • マイナーバージョンはバグフィックス、気づいたら入れる
  • メジャーバージョンはAPIが変更される事もあるので要確認
  • どこかにまとめ記事があがるはず

バージョンマネージャは?

いりません

バージョンマネージャは?

  • rubyistはすぐにバージョンマネージャを入れたがる
  • もしあなたがNodeのコミッターで無ければ本当にいらない
    • 隣接メジャーバージョンの互換性が高い
    • マイナーバージョンではAPIが変更されない
  • モジュールが動かなくなったら作ればいいじゃん

え、めんどい..

  • 君からはNodeの匂いがしない

Nodeとは

  • 向いている分野からNodeの特徴を紐解く

何がNodeに向いているか

  • Nodeはネットワークプログラミングが得意です
    • 特に、プロトタイピングやリアルタイム性の高いアプリ
  • 非同期なプログラミングにとても強い
  • 軽い処理を大量に処理する性能に優れる
    • CPU的な意味で、重い処理には向いてない

なんで?

  • jsは元来I/Oを想定していない言語(ファイルアクセスとか)
    • イベントループが言語単位で実装されている
    • EventMachineのように食い合わせを気にしなくていい
  • nodeはシングルプロセスで動作する
  • ノンブロッキングI/Oを備えている
    • リソースを100%食べられる
    • 処理落ちが発生するが、処理待ちが発生しにくい

えっ

  • ???
    • イベントループ?ノンブロッキングI/O?
    • なんなん?

牛丼屋の店員で表す

牛丼屋店員のワークフロー

  1. 客が来る
  2. 半券を確認する
  3. 牛丼を作成する
  4. 牛丼を出す
  5. 次の客の半券を確認する
  6. 以下、ループ

客がおっぱい来たら?

  • 今までは店員数を増やして対応するのが通常手段だった
    • 人が増えたらフロアも調理場も増床するよね
  • 牛丼屋は払う給与や管理費が増えコストがかさむ
  • 店員数を増やしすぎれば牛丼屋は撤退してしまう
    • (ちなみにこれがC10K問題)

じゃあNodeは?

  • 店員は牛丼を準備している間にも他の作業をしてるよね
    • 他の客の半券を確認したり
    • 食べ終わった食器を下げたり
  • つまり一人の店員がフル稼働できる
    • 新たに客が来たらそっちの対応をする
    • 半券を確認したら調理場へ戻って作業を続ける
    • 牛丼の調理が遅れても次の客対応に向かえる

Nodeはいくつものワークフローを並列実行できる

  • と聞くと大仰だけど、つまり一人が頑張れる仕組みということ
  • 長い処理を噛ませてもブロッキングが発生しない
    • ちゃんと裏であくせく働ける
    • 重い処理を噛ませればまた別の話
  • Node is 社畜言語

実際にはどういうこと?

  • 例えばhttpのレスポンスで2秒待つと何が起こるか
  • そのサーバに並列性100で100回アクセスしてみると..?

普通はこうするよね

phpっぽい擬似コードで表す

$http.createServer(function() {
  header('Content-Type: text/plain');
  echo 'hello\n'; // hello
  sleep(2);       // 2秒待つ
  echo 'world\n'; // world
})->listen(3000);

実行してアクセスすると?

$ curl -i http://localhost:3000
HTTP/1.1 200 OK
Date: Mon, 19 Aug 2013 14:09:15 GMT
Transfer-Encoding: chunked

hello
world
  • 2秒待ってからヘッダとhelloとworldが表示される
  • 100回アクセスしたら終了までに2*100秒かかる

Nodeでhttpサーバを立てた

 var http = require('http');
 var s = http.createServer(function(req, res) {
   setTimeout(function(){  // 2秒待つ
     res.end('world\n');   // world
   }, 2000);
   res.writeHead({'content-type': 'text/plain'});
   res.write('hello\n');   // hello
 }).listen(3000);

実行してアクセスすると?

$ curl -i http://localhost:3000
HTTP/1.1 200 OK
Date: Mon, 19 Aug 2013 14:09:15 GMT
Connection: keep-alive
Transfer-Encoding: chunked

hello
world
  • ヘッダとhelloが表示される
  • 2秒待ってからworldが表示される
    • 処理完了前にレスポンスを送信可能

abを叩くと?

$ ab -n 100 -c 100 http://localhost:3000/
..中略..
Time taken for tests:   2.030 seconds
..中略..
  • 100回同時にアクセスしても約2秒で全ての処理が終わる
    • この2秒はsetTimeoutで待った分

シングルスレッドだけどシングルタスクじゃない

  • setTimeoutしてる間のCPU使用率は0
    • もし他の作業があればそちらの作業に戻る
      • 今回の場合はヘッダの送出とhelloの送出
    • 同時に他のコネクションがあっても同じように動作する
  • もしsleepのある言語だと、sleep中は反応しない
    • マルチスレッドの場合はスレッドを増やして反応する
  • nodeには排他ロックも実行停止もない、ただアイドルになる

Node is アイドル

  • 最高の夏をNodeと過ごそう

まとめると?

  • Nodeは軽い処理をいっぱいさせるのに向いてる
    • 分割可能なデータを処理させるのに向いてる
  • MySQLで排他制御しつつ〜みたいな書き方は向いてない
  • 他の言語と組み合わせる使い方が多い気がします
    • 特にORMが必要な場合
    • ruby + nodeとか、php + nodeとか

npm

  • そろそろNodeインストールできたと思います
  • nodeと一緒にインストールされるnpmについて説明します

npmとは

  • Node Package Manager
    • Node付属、モジュール管理用のコマンド
    • 依存関係の自動解決や起動管理ができる
  • モジュールを落としてきたり
    • 自分で作ったモジュールを公開したり
    • テストコマンドオプションを登録したり
  • package.jsonの記述内容を元に動作する

package.jsonとは

  • プロジェクトの名前や依存モジュール等を記述するJSON
    • npmは親向きに最近傍のpacakge.jsonを探します
    • ディレクトリを掘っていても正しく動作します
  • npm initで生成できる

npmの使い方1

  • ヘルプ表示
    • npm -h | npm -l
  • package.jsonの対話生成
    • npm init
  • 補完
    • npm completion >> ~/.zshrc

npmの使い方2

  • npm i | npm install | npm isntall
    • package.jsonに書かれた依存モジュールをインストール
  • npm i hoge
    • hogeをインストール
    • package.jsonと同じ場所のnode_modulesに設置される
    • node_modules/.binに実行ファイルがリンクされる
  • npm i hoge --save
    • hogeをインストールしてpackage.jsonに追記

npmの使い方3

  • npm i hoge -g
    • hogeをグローバルインストール
    • npmのパスと同じ場所に実行ファイルがリンクされる
    • package.jsonに実行ファイルの記載が無い場合はリンク無し

npmの使い方4

  • npm update
    • インストール済モジュールを最新版へアップデート
    • package.jsonにバージョン指定がある場合を除く
  • npm (start|stop|test)
    • 各スクリプトの実行

使えるnpm

[-g]はグローバルインストールしてねの意

  • express [-g]
    • 定番のWAF
  • coffee-script [-g]
    • CoffeeScriptが書ける
  • nodectl [-g]
    • 拙作、起動マネージャ

使えるnpm2

  • mkdirp
    • mkdir -pしてくれる
  • mktemp
    • mktempへのAPI
  • mime
    • ファイル名からMIME、MIMEから拡張子へ変換

使えるnpm3

  • async-cache
    • 非同期型のキャッシュ
  • request
    • http.getを便利にしてくれる

使えるnpm4

  • mongoose
    • MongoDBのODM(Object Document Mapper)
  • passport
    • oAuth周りの便利ライブラリ
    • SNSへの個別対応がすごく充実してる
  • moment
    • 時刻計算のライブラリ
    • a minute agoとかの文字列も生成可能

いいnpmの探し方

  • 基本的には手探り
  • 更新日付が最近のものを探す
    • 現在のメジャーバージョンリリース以前のものは回避
  • nodejsmodules.orgを見る
  • 単純な機能・シンプルな名前のものを探す

npmの調べ方

  • 基本的には手探り
  • npm infoしてgithubのリポジトリを見る
  • インストールしてみてREADMEを読む

npmに投稿しよう

  • 難しくない、審査もない
  • npm adduserする
  • package.jsonを詳しく書く
  • npm publishでリリースされる
    • .gitignoreに記述したファイルは無視される
    • .npmignoreに記述したファイルも無視される

モジュールを使う

  • require, exports, moduleを使います
    • CommonJSというサーバサイドjsの仕様に準拠
  • コードを外部化したりするのにも使います
  • 外部化したコードとモジュールの扱いに差はない

ロードする

requireを使う

some_module = require('some_module');
myscript = require('./myscript');

外部化する

exportsを使う

-- geta6.js --
exports.name = 'geta6';
exports.hobby = 'hentai game';
exports.dohobby = function () {
  console.log(this.hobby);
}

-- app.js --
console.log(require('./geta6'));

{
  name: "geta6",
  hobby: "hentai game"
  dohobby: [Function]
}

外部化する

module.exportsを使う

-- geta6.js --
module.exports = (function () {
  function geta6(){}
  geta6.prototype.name = 'geta6';
  geta6.prototype.hobby = 'hentai game';
  geta6.prototype.dohobby = function () {
    console.log(this.hobby);
  }
  return geta6;
});

-- app.js --
Geta6 = require('./geta6');
geta6 = new Geta6;
console.log(geta6);
geta6.dohobby();

{}
"hentai game"

exportsとmodule.exportsの違い

  • 別に違わない
  • けど望ましい使い方はある

exports

  • module.exportsが本体
    • prototypeをmoduleと捉える
    • require先ではmoduleのインスタンスになる
    • インスタンス化しなくても使えるモジュールに使う
  • exports.key = value、シンプル

module.exports

  • 特定のオブジェクト型に用いる
    • 独自のクラスなどを定義した場合に用いる
  • module.exports = [Function]

require, exports, module

  • 実はモジュールごとのローカル変数
    • deleteされても影響ありません
  • ここに載ってる

クライアントjsとの違い

  • グローバルオブジェクト
    • jsの場合、ブラウザウィンドウ
      • Nodeの場合、自身のプロセス
      • globalがwindow相当の予約語
  • あとはAPI(ファイルシステムなど)とか

表裏両面で同じ言語を使うメリット

  • DOMやAPIに依存しないライブラリを共有できる
    • momentやunderscoreなど
    • デフォルト値の流し込みが容易になる

イベント駆動のしくみ

  • process.nextTick
  • setImmediate

いつ使うのか

  • 主に再帰処理を行う場合
    • イベントループをブロックしない
  • 重いjsを実行するとブラウザは固まる
    • nodejsも重いjsを実行するとサーバが固まる
  • 他の処理が割込できる余地を残して実行する

http.createServer (req, res) ->
  list = ( (dir) ->
    list = []
    for f in fs.readdirSync dir
      if (fs.statSync f = path.join dir, f).isFile()
        list.push f
      else
        list = list.concat arguments.callee.call @, f
    return list
  )(path.resolve './', req.url)
  res.end list.join '\n'
.listen 3000

setImmediate

http.createServer (req, res) ->
  list = ( (dir) ->
    list = []
    for f in fs.readdirSync dir
      if (fs.statSync f = path.join dir, f).isFile()
        list.push f
      else
        callback = arguments.callee
        setImmediate ->
          list = list.concat callback.call @, f
    return list
  )(path.resolve './', req.url)
  res.end list.join '\n'
.listen 3000

nodeでフィボナッチ

いつ実行されるのか

  • 予約時に実行中のイベントループが終了した時点
    • 処理を次のイベントループに予約する

tickには限界がある

  • process.nextTickを使った再帰処理はダメ
    • 現在は警告+1000回の回数制限
    • 将来的に削除される予定

プロダクション環境

  • NODE_ENV=production node app.jsする
  • nodectl使う

Stream

  • 簡単に説明する
    • データを受信したらすぐ実行、狂気のStream
  • とても早いし便利
    • Streamの大元はただのインターフェース
    • なので、色々なStreamがある

2種類ある

  • ReadableStream
    • 読み込みStream
    • チャンクを読み出すごとにイベントが発生する
  • WritableStream
    • 書き込みStream

ReadableStreamのイベント

  • data
    • chunkが来た
  • end
    • おわた
  • error
    • あぼーんした

ファイルのコピー

file = fs.readFileSync 'copyfrom'
fs.writeFileSync 'copyto', file

copyfromの内容は全ていったんバッファされる

Streamだと?

read = fs.createReadStream 'copyfrom'
write = fs.createWriteStream 'copyto'
read.pipe write
  • チャンク毎にバッファ、都度破棄して書き込める
  • pipeの左にRS、右にWSを置く

だいたいStream

http.get 'www.google.com', (res) ->
  data = ''
  res.on 'data', (chunk) -> data += chunk
  res.on 'end', -> console.log data

このresはReadableStreamオブジェクト

こんなものも

http.createServer (req, res) ->
  fs.createReadStream('file').pipe res

このresはWritableStreamオブジェクト

pipeって?

  • Streamが読み書き不可になった時
    • イベントを補足、pauseしてresumeしないといけない
  • これを自動でやってくれるのがpipe

まとめると

  • Streamはインターフェースクラス
    • そのままextendしても機能しない
  • 自分で実装する必要がある
    • なので色々なStreamがある
    • そこら中に溢れている
  • 詳しいことは長くなるので割愛
    • 後日やるかもしれない

じつえん

  • 小さいデータをたくさん処理する事例見当たらない
  • アニメウィキでもスクレイピングしようか
var https = require('https');
var fs = require('fs');
var options = {
key: fs.readFileSync('test/fixtures/keys/agent2-key.pem'),
cert: fs.readFileSync('test/fixtures/keys/agent2-cert.pem')
};
https.createServer(options, function (req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment