Node.js

express向けconnect-ltsv-loggerを作った

ltsvのビッグ・ウェーブを眺めてたら最初にgem作ってたのが知人でびっくりして、俺もなんかやってみたいなーと思いつつ作ってみました。

connect-ltsv-logger

Node.js用のparserはもうあったし、それならstreamでって思ったけど Stream難しいので挫折してるうちに、ltsv-streamも登場していたので、 logを出力する側からアプローチしてみた。

こんな感じでexpressやconnectのMiddlewareとして使用します。

サンプル


              var express = require("express"),
                  ltsvlogger = require('connect-ltsv-logger');
              
              // define output WriteStream
              var out = fs.createWriteStream("ltsv-access.log",{flags: 'a+'}),
              
              // define tokens
              var ltsv = [];
              ltsv.push("host");
              ltsv.push("ident");
              ltsv.push("user");
              ltsv.push("time");
              ltsv.push("req");
              ltsv.push("status");
              ltsv.push("size");
              ltsv.push("referer");
              ltsv.push("ua");
              
              var app = express();
              app.configure(function(){
                // app.set(/*snip*/)
                // ...
              
                app.use(ltsvlogger({format:ltsv,stream:out}));
              
                // app.use(/*snip*/)
                // ...
              });
              

指定したTOKENをltsv形式でログ出力します。connect.loggerのラッパーなので元のオプションもそのまま使えます。

引数

  • format: String or 例のようにTokenのArrayが使えます。
  • stream :本家と同じ。ltsvにするのでstdoutじゃなくてファイルに書いた方が使いがいがあるかも。
  • buffer: 本家と同じ。
  • immediate: 本家と同じ。

使用可能なFormat

connect.logger'sのformatsをltsv形式にオーバーライドしているだけです。

  • default
    host:127.0.0.1ident:-user:-time:[Wed, 13 Feb 2013 10:00:55 GMT]req:GET / HTTP/1.1status:200size:110referer:-ua:-`
  • short
    host:127.0.0.1ident:-req:GET / HTTP/1.1status:200size:-response-time:1 ms`
  • tiny
    req:GET /status:200size:-response-time:1 ms
  • dev

    concise output colored by response status for development use (Not ltsv format).

使用可能なTokens

identとか良くわかんないけど、connectも”−”にしてたので...(汗)

  • time

      logger.token("time",function(){
                      return "[" + moment().format("DD/MMM/YYYY:HH:mm:ss Z") + "]" ;
                    });
  • host

      logger.token("host",function(req,res){
                      return req.connection.address().address || '-';
                    });
  • X-Forwarded-For

      logger.token("X-Forwarded-For",function(req,res){
                      return res.getHeader("X-Forwarded-For") || "-";
                    });
  • user

      logger.token("user",function(req,res){
                      return '-';
                    });
  • ident

      logger.token("ident",function(req,res){
                      return '-';
                    });
  • req

      logger.token("req",function(req,res){
                      var ret = [];
                      ret.push(req.method);
                      ret.push(req.url);
                      ret.push("HTTP/"+req.httpVersion);
                      return ret.join(" ");
                    });
  • method

      logger.token("method",function(req,res){
                      return req.method;
                    });
  • uri

      logger.token("uri",function(req,res){
                      return url.parse(req.url).href;
                    });
  • protocol

      logger.token("protocol",function(req,res){
                      return url.parse(req.url).protocol;
                    });
  • status

      logger.token("status",function(req,res){
                      return res.statusCode;
                    });
  • size

      logger.token("size",function(req,res){
                      return res.getHeader("content-length");
                    });
  • reqsize

      logger.token("reqsize",function(req,res){
                      if(req.body) return req.body.length;
                      return "-";
                    });
  • referer

      logger.token("referer",function(req,res){
                      return req.headers['referer'] || req.headers['referrer'];
                    });
  • ua

      logger.token("ua",function(req,res){
                      return req.headers['user-agent'];
                    });
  • vhost

      logger.token("vhost",function(req,res){
                      return req.headers["host"];
                    });
  • reqtime

      logger.token("reqtime",function(req,res){
                      return new Date - req._startTime;;
                    });
  • X-Cache

      logger.token("X-Cache",function(req,res){
                      return res.getHeader('X-Cache');
                    });
  • X-Runtime

      logger.token("X-Runtime",function(req,res){
                      return res.getHeader('X-Runtime');
                    });

            |i
                   \      |.|
                    ト\   /| ト
                    | トヽ   / | | ト
                    | | トヽ\/| | | ト    /
                    | | | ト\≧三ミゞ=イ/
                   ム彡''´ ̄ ̄    ̄ ヽ{__..
                  /             V´
                  ノ  __          ',
               ,. == y ̄, __、\_        )      世 界 的 で す も ん ね
               |i  }-| ゝ二 |/ ̄ ̄  /ニ,l
               ヽ__ノ/ヾ _ ノ       > }}
                / >≦'__        し /        乗 る し か な い
                 Vて二オカ       (_,/}
                 Yこ二ノ!!|          }         こ の ビッ グ ウ ェ ー ブ に
                  Y⌒ 从        ∠)
                  从从从トミ   _.ィニ二 ̄丶
                   ミ三三彡 ' ´      \ \
                      /           \ヽ
                    /            ミ;,. ', ',
                     |   _  _ __    \',.',
                    ノ!   | V7\ ´/
                   / l /_ゝ| ト >__/ /
                   |   ヽン ´  ヽー'
                  i|                l
                  |:! ヽ              |
                  | ト、 `ミ,            l
              

iPhone4Sで無料テザリング via WebSocket!

ws-tether

Yet Another Tethering Solution for iPhone4S.

ws-tether mechanism

使い方(Node.jsが動く環境が前提)

  1. ws-tetherをLocalのMacとCloud上のサーバ双方にcloneします。
    Cloud環境はHerokuがwebsocketサポートしています。
    
                  git clone http://github.com/georegeosddev/ws-tether.git
                  
  2. コンフィグファイルの編集
    
                  var config = {
                    localProxy : "ws://"+window.location.host
                    ,serverProxy : "ws://"+Cloudサーバのドメイン+":ポート"
                  };
                  

    websocketの接続先としてcloudサーバで稼働させるserver-ploxyを指定します。

  3. MacでAdhocネットワークを作成.
    create adhoc network
  4. 詳しいやり方

    ネットワークができたらifconfig等でIPアドレスを調べます。

    /sbin/ifconfig

    このへんはDHCPじゃなくて固定にしてもいいのかもしれないが詳しいことはよくわからないので、
    とりあえず割り振られたIPを調べる。

  5. ローカルプロキシの起動 (on Local Mac)

    cd ws-tether/local-proxy && npm start.
                  
  6. サーバプロキシの起動(on Cloud Server)

    ws-tether/server-proxy/bin/start.sh を編集してポートはCloudサーバで使用出来るポートにセットしたら、

    cd ws-tether/server-proxy && npm start.
                  
  7. iPhoneのsafariで先ほどMacで作成したネットワークにWifi接続して、bridge.htmlに接続.

    http://Mac'sIP-adress:3000/bridge.html
  8. Proxy Switchy!などのChrome拡張を使って、Chromeのプロキシをlocalhost:3000に設定

    ploxy setting

  9. Enjoy!

Phantom.jsとimageMagickで.html to .png to .pdf

年末のプレゼンに向けてこんなの作ってみた。 テスト一切書いてなかったり、Cakefileが微妙だったり、デザインがやっつけだったりまだまだ改善の余地ありですが、一旦完成。

  georgeOsdDev/SlideBase

express + socket.IOでスライドショーを共有するみたいな感じです。
  Jxck/SlideStream の劣化版といえばそれまでですが、 js,coffeescriptの勉強という意味で以下の分厚い本を読みつつ1から自分で作ってみた。


webサーバはexpressでadminログインしたプレゼンターの操作をsocket.IOでブロードキャストで基本的なアイデアや作りは冒頭申したとおり、  Jxck/SlideStream まんまなんですが、
以下の構成パッケージで、socket.IOとexpressでセッション共有する際にJxckさんのブログのままだと動かなかったのでcookieのとりかたは pxsta's Memo さんを参考にcookieのパースが必要みたい。

package.json


              "dependencies": {
                "express": "3.0.0",
                "ejs": ">= 0.0.1",
                "connect": "2.6.0",
                "socket.io": "0.9.10",
                "connect-redis": "~1.4.5",
                "cookie": "0.0.5",
                "underscore": "~1.4.2"
              }
              

expressサーバ側


              app.use util.parallel(
                express.methodOverride(), 
                express.session
                  store: sessionStore
                  secret: config.session.secret
                  maxAge: false
                  cookie:
                    httpOnly: false
                )
              

socket.IOサーバ側


              io.set 'authorization', (handshakeData, callback) ->
                if handshakeData.headers.cookie
                  singedCookie = slideBaseUtil.parseCookie decodeURIComponent(handshakeData.headers.cookie)
                  cookie = slideBaseUtil.parseSignedCookies singedCookie
                  sessionID = cookie['connect.sid']
                  store.get sessionID ,(err,session) ->
                    if err
                      log "err", err
                      callback err.message, false
                    else if !session
                      callback "session not found", false
                    else
                      log "session found"
                      handshakeData.cookie = cookie
                      handshakeData.sessionID = sessionID
                      handshakeData.session = session
                      callback null, true
                else
                  callback "cookie not found", true
              

slideBaseUtilの中身はこんな感じ


              connect = require 'connect'
              cookie  = require 'cookie'
              
              parseCookie: (target) ->
                cookie.parse target
              parseSignedCookies: (singedCookie) ->
                connect.utils.parseSignedCookies singedCookie, config.session.secret
              

クライアントサイドでプラグインみたいに別ファイルのいろんなところからsocketを使えるようにするには

              execEmit: (name,data) ->
                console.log "execEmit "+name
                obj =
                  name:'plugin'
                  plugin:
                    name:name
                    data:data
                $('body').trigger 'execEmit', obj
              socket?.on 'connect', ->
                $('body').on 'execEmit', (event,obj) ->
                  socket.emit obj.name, obj
              

みたいにbodyにイベント作ってそれを外から呼ぶみたいにしてみた。
あとはサーバ側にもpluginで受けたらそのままブロードキャストするようにかいて


              socket.on 'plugin', (data) ->
                socket.broadcast.volatile.emit "plugin", data
              

クライアントサイドはpluginを受けたらpluginとして登録してある関数を呼ぶみたいな感じにしておけば未定義のものが仮に送られてきても大丈夫みたいな感じ。


              socket.on 'plugin', (obj) ->
                if not obj.plugin or not obj.plugin.name then return false
                if not isEnableServerPush obj.plugin.name then return false
                func = sbClient.plugins[obj.plugin.name].get("callback") || {}
                func(obj.data)
              

さて、タイトルにあるとおり感動したのはPhantom.js。
このやり方もJxckさんの ブログ を参考にしたのですが、チョット違うのはPhantom.jsからクリックイベントを呼んでページ遷移させるとこと、sam2pとpdftkの代わりに imageMagick っていうツールを使いました。imageMagickを使うと、複数pngから一発で1枚のpdfに変換することができました。

Phantom.jsからクリックイベントを呼ぶには


              page.includeJs 'http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js',->
                offset = page.evaluate ->
                  $('#next').offset()
                page.sendEvent 'click', offset.left+1, offset.top+1
              
みたいな感じで、叩きたいボタンや場所のoffsetを取得してpage.sendEvent('イベント',top,left)を呼べばOK。これでページ送りができますが、それぞれ非同期でやってる?ようなので、この直後にpage.render(outout)を呼んじゃうとまだクリックされてなかったりするのでsetTimeoutで微妙な調整がいるかも。sendEventの完了タイミングとれたりすると便利なのに。
さて、imageMagickインストールはhomebrew経由でカンタン。
              brew install imagemagick
              
これでこんな感じでいくつかコマンドが使えるようになります。
              22:26:33:oshidatakeharu@george-on-mba.local:/usr/local/Cellar/imagemagick/6.7.7-6/bin:master$ ll
              total 304
              -r-xr-xr-x  1 oshidatakeharu  admin  1290 Nov 11 12:08 Magick++-config
              -r-xr-xr-x  1 oshidatakeharu  admin  1256 Nov 11 12:08 Magick-config
              -r-xr-xr-x  1 oshidatakeharu  admin  1264 Nov 11 12:08 MagickCore-config
              -r-xr-xr-x  1 oshidatakeharu  admin  1269 Nov 11 12:08 MagickWand-config
              -r-xr-xr-x  1 oshidatakeharu  admin  1251 Nov 11 12:08 Wand-config
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 animate
              -r-xr-xr-x  1 oshidatakeharu  admin  9260 Nov 11 12:08 compare
              -r-xr-xr-x  1 oshidatakeharu  admin  9204 Nov 11 12:08 composite
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 conjure
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 convert
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 display
              -r-xr-xr-x  1 oshidatakeharu  admin  9260 Nov 11 12:08 identify
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 import
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 mogrify
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 montage
              -r-xr-xr-x  1 oshidatakeharu  admin  9196 Nov 11 12:08 stream
              
このうちconvertっていうのがファイル変換してくれるやつで、
              convert src/*.png dest/output.pdf
              
とかあるいは
              convert -append 1.png 2.png output.pdf
              
みたいな感じでpngが複数ページ構成のpdfファイルに1つにまとまります。
カンタンでイイね。
あーなんか断片的でまとまりのないエントリーですみません。
  • Sep
  • 14
  • 2012

Nodejitsuにアプリをデプロイしてみた。

最近会社に手書きのホワイトボードが導入されて勤怠時刻を書いたり熱血風でなかなか嫌な感じなので
IT会社なのでオンライン化してみようと思いたち、作ったのがこちら。

whiteBoard

で、これを改良して作ったのがこちら。

webBoard

Demo

どちらもjQueryでイベントを拾ってSocket.IOでbroadcastする仕組みとなっています。

基本的にlocalネットワーク内で使うことを想定しているのですが、Demoをデプロイしようと思ってwebSocket対応Paasを探してたどり着いたのがNodejitsu. ナニコレNode専用なの?カッコイイ。jitsuって何?術?でよくわかんないけどやってみた。

まずはアカウント作ってcliを入れる

npm install jitsu -g
              

これで`jitsu`コマンドが使えるようになる。

$ jitsu
              info:    Welcome to Nodejitsu 
              info:    jitsu v0.9.8
              info:    It worked if it ends with Nodejitsu ok
              info:    Executing command 
              help:              ___  __
              help:        /  /  /   /_  /  /
              help:     __/  /  /   __/ /__/
              help:    
              help:    Flawless deployment of Node.js apps to the cloud
              help:    open-source and fully customizable.
              help:    https://github.com/nodejitsu/jitsu
              help:    
              help:    Usage:
              help:    
              help:      jitsu <resource> <action> <param1> <param2> ...
              

チュートリアル通りにjitsu initとかjitsu deployとかやってくととりあえずnodejitsu側にモジュールは配置されるようだけど何やらエラーがでるわでるわ。

で細かいことはあんまり覚えていないのだけど以下のようにいろいろやったら動くとこまでできた。

  • package.jsonのdependencyでバージョン指定の’*‘をやめて特定のバージョンを指定する。

アプリがスタートするタイミングでnodejitsu側でnpm installしてるみたいなんだが、どうもそこでこけてしまう。(この時のログは流れてしまってもうないけど)ログにmodule versionがうんたらかんたらって怒られるのでちゃんと指定するとnpm installは正常終了したっぽい。socket.IOのコンパイルに結構時間かかってたけど。

  • expressのポート指定は8080にする。

最初はなんとなく3000にしてたけど、portがうんたらかんたらって怒られるのでチュートリアルにあるように8080にしたらOKだった。

  • jitsu deploy じゃなくて jitsu app appname start を使ってみる

こんなログ

error:   Error running command apps deploy
              error:   Nodejitsu Error (500): Internal Server Error
              warn:    Error returned from Nodejitsu
              error:   Error: connect ECONNREFUSED
              

とかこんなログとか

error:   Error running command apps deploy
              error:   socket hang up
              

よくわかんなかってけど、dashboardで見るとアプリモジュールは最新化されてたっぽいのでdeployはやめてstop startにしたら動いた。めでたしめでたし。

Docpadでスライドジェネレータを作ったの巻

最近ハマっているDocpadを使ってスライドジェネレータを公開しました。

Demo:Slidepad
Repositry:georgeOsdDev/slidepad

markdownで書いたファイルをコンバートしてイケてるHTML5プレゼンテーションFWに出力します。LTとかTech系のプレゼンで使ってもらえると嬉しいです。
詳しくはデモGetting Startedを見ていただくとして一つ嬉しいことが。


それがこれ

幸運なことにDocpad@6.5.5が今日リリースされて、Skeletonの一つとして組み込まれることに。

実際にDocpad@6.5.5を入れて試してみると
              $ docpad run
              info: Welcome to DocPad v6.5.5
              info: Environment: development
              info: Plugins: 
              You are about to create your new project inside your current directory. Below is a list of skeletons to bootstrap your new project:
              
              	HTML5 Boilerplate
              	HTML5 Boilerplate skeleton for DocPad. Bare essentials for building a modern website with best practices.
              
              	Twitter Bootstrap
              	Simple and flexible HTML, CSS, and Javascript for popular user interface components and interactions.
              
              	Kitchensink
              	A DocPad Skeleton useful for showcasing different features and functionality of DocPad. Implemented using Twitter Bootstrap.
              
              	Website
              	Website skeleton for DocPad. Useful for personal websites and blogs. Pulls in social streams and github information.
              
              	Hogan
              	A DocPad Skeleton based off Twitter's Hogan.js Project Page.
              
              	SlidePad
              	Create HTML5 Presentations easily with DocPad
              
              	No Skeleton
              	Prefer to start from scratch? You can get started without any skeleton if you wish
              
              Which skeleton will you use?
                1) HTML5 Boilerplate
                2) Twitter Bootstrap
                3) Kitchensink
                4) Website
                5) Hogan
                6) SlidePad
                7) No Skeleton
                : 6
              info: Installing the SlidePad skeleton into /Users/oshidatakeharu/Projects/Nodejs/docpad/6.5.5/skeleton
              Initialized empty Git repository in /Users/oshidatakeharu/Projects/Nodejs/docpad/6.5.5/skeleton/.git/
              From git://github.com/georgeOsdDev/slidepad
               * [new branch]      master     -> skeleton/master
              From git://github.com/georgeOsdDev/slidepad
               * branch            master     -> FETCH_HEAD
              npm http GET https://registry.npmjs.org/docpad/6.5.3-dev
              n
              
              ...
              
              

おぉぉぉぉ。出ました。僕のリポジトリからFetchしてる! しかもHTML5 BoilerplateとかTwitter Bootstrapとかド定番のテンプレと並んで表示されるなんて!

typoだらけの英語が恥ずかしいし、テストもほとんどやっていないし...(汗)だけど自分が作って公開したものに反応があるととても嬉しいですね。もっとがんばるぞ!