Node.jsとWebSocketでchatサーバーをつくる

週末にみんなNode.jsいじっていて楽しそうだったので、写経してみたら

TypeError: Cannot call method 'set' of undefined

って言われてうごかなかったので、調べたらdeprecatedらしい。

なので、コメントアウトして動かしてみた。

var http = require('http'),
    fs = require('fs'),
    ws = require('websocket-server');

var httpServer = http.createServer(function (req, res) {
  var docroot = "public";
  var response = function(data) {
      res.writeHead(200, {'Content-Type': 'text/html'});
      res.end(data);
  };

  fs.readFile(docroot+req.url, function(err, data) {
     if(err)
     response(err.message);
     else
     response(data);
  });
});

var wsServer = ws.createServer({
  server: httpServer,
  origin: "http://localhost:8124"
});

wsServer.addListener("connection", function(conn) {
             var username = "user_"+conn.id;
//           conn.storage.set("username", username);
             conn.send("WebSocketサーバーに接続しました");
             conn.send("あなたの名前は " + username + " です");
             conn.broadcast(username+" が接続しました");

             conn.addListener("message", function(data) {
                          if(data == "ping")
                          conn.send("pong");
                          else
                          wsServer.broadcast(username+": "+data);
                      });
             });

wsServer.addListener("close", function(conn) {
            conn.broadcast(username+" が切断しました"); 
             });
wsServer.listen(8124);
console.log('Server runnning at http://127.0.0.1:8124/');

ProductName 徹底解説 HTML5 APIガイドブック コミュニケーション系API編
小松 健作
秀和システム / 2730円 ( 2010-12 )


JavaScriptで相対的な経過時間を出力する

nodeのチャットのサンプルから。

Date.prototype.toRelativeTime = function(now_threshold) {
  var delta = new Date() - this;

  now_threshold = parseInt(now_threshold, 10);

  if (isNaN(now_threshold)) {
    now_threshold = 0;
  }

  if (delta <= now_threshold) {
    return 'Just now';
  }

  var units = null;
  var conversions = {
    millisecond: 1, // ms -> ms
    second: 1000, // ms -> sec
    minute: 60, // sec -> min
    hour: 60, // min -> hour
    day: 24, // hour -> day
    month: 30, // day -> month (roughly)
    year: 12 // month -> year
  };

  for (var key in conversions) {
    if (delta < conversions[key]) {
      break;
    } else {
      units = key; // keeps track of the selected key over the iteration
      delta = delta / conversions[key];
    }
  }

  // pluralize a unit when the difference is greater than 1.
  delta = Math.floor(delta);
  if (delta !== 1) { units += "s"; }
  return [delta, units].join(" ");
};

便利

> now.toRelativeTime()
'30 seconds'
> now.toRelativeTime()
'58 seconds'
> now.toRelativeTime()
'1 minute'

node.jsのexportsって

node.jsのチャットのコード読んでいたら、

var fu = exports

と書いてあって、これなにかなぁと調べた。

requireされたときにエクスポートされるオブジェクトということかな。

var PI = 3.14;
var c = exports;

c.area = function (r) { return PI * r * r};
c.circumference = function (r) { return 2 * PI * r};

requireしてみる。

> var circle = require("./circle")
> circle.area(5)
78.5
> circle.circumference(5)
31.400000000000002

node.jsのweb server exampleをcoffee-scriptで書いてみた

今朝は寝坊したせいで朝コードを書く時間があまり取れなかった。

Node.jsのversionが上がっていたのでnvm installをしつつ、coffee-scriptをちょっとさわる

http = require('http');

http.createServer((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/')

ちなみに普通にjavascriptで書くとこうなる。

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/');

普段Pythonで書いてると文の最後の;とか忘れがちなのでcoffee-scriptは良かったりする

nvmを使って色々インストール

今朝試していた方法は実は古いやり方らしいので、今風のやり方で入れてみる。ずばりnvm

ただし、zshだとかなりハマリングなのでbashがオススメですね。zshのマシンでいれようとしたら超絶ハマったがbashなmacbookでは楽勝だったという。

git clone https://github.com/creationix/nvm.git ~/.nvm
source .nvm/nvm.sh 
nvm install v0.4.9
nvm use latest
nvm sync
nvm ls
npm install -g express
npm install -g ejs
npm install -g coffee-script

ついでに色々応用例を探しているのだけど、個人的に気に入ったもののリスト

scrumblr

このリアルタイム付箋ソフトでKPTやったら最高ですね。

node.jsとCSSSlide.jsでスライド共有するサンプルプログラムを作ったよ

これとskype使えばwebexplayerいらないんじゃないか?

node.js+socket.ioでライブコーディング的なものを作るメモ

これも中々楽しそう。

coffeescriptを触ってみている

ついでにnode.jsのアップデートをしつつ、npmを入れた。/usr/localのownerを変更するのは気持ち悪いが、自分しか使わないのでまぁいいかなと(参考)。

注)こっちのほうが良いやり方らしい

tar xvfz node-v0.4.9.tar.gz 
cd node-v0.4.9/
./configure 
make
sudo make install

sudo chown -R $USER /usr/local
curl http://npmjs.org/install.sh | sh

npm install -g coffee-script

さて、CoffeeScriptとはjavascriptにコンパイルできる小さな言語です。文法がPythonっぽいのでとっつきやすそう。

関数適用に括弧がいらない

引数が一つのときだけ?

coffee> add = (x,y) -> x+y
[Function]
coffee> add(1,2)
3
coffee> add 1 2
Error: In repl, Parse error on line 1: Unexpected 'NUMBER'
    at Object.parseError (/usr/local/lib/node_modules/coffee-script/lib/parser.js:472:11)
    at Object.parse (/usr/local/lib/node_modules/coffee-script/lib/parser.js:548:22)
    at /usr/local/lib/node_modules/coffee-script/lib/coffee-script.js:29:22
    at Object.eval (/usr/local/lib/node_modules/coffee-script/lib/coffee-script.js:88:10)
    at Interface.<anonymous> (/usr/local/lib/node_modules/coffee-script/lib/repl.js:39:28)
    at Interface.emit (events.js:64:17)
    at Interface._onLine (readline.js:153:10)
    at Interface._line (readline.js:408:8)
    at Interface._ttyWrite (readline.js:585:14)
    at ReadStream.<anonymous> (readline.js:73:12)

coffee> add1 = (x) -> x+1
[Function]
coffee> add1 3
4

内包標記が使える

coffee> numbers = [1,2,3,4,5]
[ 1, 2, 3, 4, 5 ]
coffee> odds = (n for n in numbers by 2)
[ 1, 3, 5 ]

なかなか楽しげ

ベターJavaScript!? CoffeeScriptが注目されるワケ

Ashkenas氏によれば、JavaScriptからCoffeeScriptに移植したあるライブラリは、元の1/3のコード量になったものがあるといいます。また、JavaScriptにある仕様上の欠陥や落とし穴の多くはCoffeeScriptによって避けられるとしています(欠点を知り、それらを避ける方法をまとめた「JavaScript: The Good Parts――「良いパーツ」によるベストプラクティス」という書籍もありますね)。

ProductName JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス
Douglas Crockford
オライリージャパン / 1890円 ( 2008-12-22 )


JavaScriptにある仕様上の欠陥や落とし穴を知っておくということも大切ですね。というわけで、次回の読書会は7/30に静岡でやる予定になっています。

JavaScript: The Good Parts読書会(6章,8章)

行ってきた@富士のコミュニティf

配列かどうかの判定(6.5)

タイプがobjectで且つコンストラクタがArrayであるかという判定法ではグローバルオブジェクトで作成された配列を識別できない。

そこで次のような判定の関数を定義している

var is_array = function (value) {
    return value &&
        typeof value === 'object' &&
        typeof value.length === 'number' &&
        typeof value.splice === 'function' &&
        !(value.propertyIsEnumerable('length'));
}

さて、この判定でtrueを返すような配列じゃないオブジェクトを作りたい。

読書会の時にはlengthプロパティを設定しつつ列挙不可能な状態にする方法がわからなかったが、朝プリキュアを見てたらふとプロトタイプチェーン上にlengthプロパティをおけばいいじゃんと気がついた。

var fake_ = {'length':3 };

var fake_array = {};
fake_array.__proto__ = fake_;
fake_array.splice = function() {
    return 'Hello World';
}

console.log(is_array(fake_array));

これはtrueを出力する

applyの分かりやすい適用事例(8)

apply,callはthisに値を自由にセット出来るので継承に頼らなくても自由にメソッドを実行出来るということだが、実例としてはどういう場合に使われているのかという話になった。自分は再帰でつかったくらいしかなかったなあとちょっと調べてみた。

他のライブラリなんかにある便利メソッドを自分のオブジェクトでちょっと利用したいって時か、(ライブラリ開発者が)コントロールできないユーザーのオブジェクトにメソッドを注入したいとかそういう時に使うのかな。

ProductName JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス
Douglas Crockford
オライリージャパン / 1890円 ( 2008-12-22 )


おやつと懇親会

おやつは杉山フルーツの生ゼリー

1309047478

懇親会はうお菜

1309047481

Javascriptの配列はトリッキーなオブジェクト

JGPを読んでいたら、配列リテラルはオブジェクトリテラルを使って書けると書いてあった。

ProductName JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス
Douglas Crockford
オライリージャパン / 1890円 ( 2008-12-22 )


var nums = ['one', 'two', 'three'];

これは

var obj_nums = {'0':'zero', '1':'one', '2':'two'};

ただし配列リテラルの方はArray.prototypeを継承するが、オブジェクトリテラルの場合にはObject.prototypeを継承する。

そこで継承関係をいじれば配列として使えるのかなぁと試してみた(Firebug)。

まずそのままだと

var obj_nums = {'0':'zero', '1':'one', '2':'two'};
console.log(obj_nums.toString()); // [object Object]

var nums = ['zero','one','two'];
console.log(nums.toString()); // zero,one,two

となる。続いて継承をいじってみる

var obj_nums = {'0':'zero', '1':'one', '2':'two'};
obj_nums.__proto__ = Array.prototype;
console.log(obj_nums.toString()); // (an empty string)

empty stringってなんやねんと思ったがlengthプロパティを付けてみる。

var obj_nums = {'0':'zero', '1':'one', '2':'two'};
obj_nums.__proto__ = Array.prototype;
obj_nums.length = 3;
console.log(obj_nums.toString()); // zero,one,two

Arrayリテラルと同じ挙動を示すようになった。内部でなにやってんだろうか?

ただし、オブジェクトを出力するとobj_numsのほうがプロパティが見えるんだよなぁ。これがちょっと気になる。

var obj_nums = {'0':'zero', '1':'one', '2':'two'};
obj_nums.__proto__ = Array.prototype;
obj_nums.length = 3;
console.log(obj_nums);

var nums = ['zero','one','two'];
console.log(nums);

array

というわけで、来週の読書会は配列のあたりをやりますので興味があれば参加すると楽しいと思います。ちなみに杉山フルーツの生ゼリーをおやつに迎える予定にしておりますよ。

もちろん、お昼につけナポを食べるのもありだと思います。

第4回JavaScript読書会は富士のコミュニティFでやります

ピーク(関数とオブジェクト)は過ぎたので、割と軽めの内容になってると思う。

ProductName JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス
Douglas Crockford
オライリージャパン / 1890円 ( 2008-12-22 )


WWW::Mechanize::FirefoxとWeb::QueryでAjaxのサイトをスクレイピングする

第1回 静岡ITPro勉強会 インフラ部で発表してきた。デモが動かなかったのは、名前がダサいという理由で電車の中でhtmlのファイル名を変えたせいでした、アホ過ぎる。

ま、こんな感じのファイルを用意します。AjaxでATNDのAPIにアクセスして今日の参加者を表示するっていうやつです。

<!DOCTYPE html>
<html>
<head>
<title>ajaxtest</title>
<script type="text/javascript" src="jquery-1.6.1.min.js"></script>
</head>

<body>
<button id=ajax name="atnd">クリックするとユーザー一覧を表示します</button>
<div class=users></div>
<script>

$("button#ajax").click(function(){
$.getJSON('http://api.atnd.org/events/users/?event_id=16076&format=jsonp&callback=?',
function(data){
 var items = [];

 $.each(data.events[0].users, function() {
   items.push('<li>' + this.nickname + '</li>');
 });

 $('<ul/>', {
   'class': 'userlist',
   html: items.join('')
 }).appendTo('div.users');
});
});
</script>
</body>
</html>

これをスクレイピングするにはWWW::Mechanize::Firefoxを使って

use WWW::Mechanize::Firefox;
use Web::Query;
use Encode;

my $mech = WWW::Mechanize::Firefox->new();
$mech->get('http://localhost:8000/');
$mech->click({ xpath => '//button[@name="atnd"]', synchronize => 0 });
sleep(2);
#print $mech->content;

my $q = Web::Query->new_from_html($mech->content);
$q->find('li')->each(sub {
                my $i = shift;
                printf "(%d) %s\n", $i+1, encode('utf-8', $_->text);
              });

実行するとこんな風に出力されるはずです。

(1) secondarykey
(2) ando_ando_ando
(3) となか
(4) yukio.47
(5) Kaz110
(6) tatsuya.ueda
(7) taji_314159265
(8) kzfm
(9) non

凡ミスでここまでは行かなかった。カナシス