Node.js API (url) - URL 文字列の解析と生成

URL 「統一資源位置指定子(Uniform Resource Locator)」 というのは Web 上の html ファイルなどのリソースを指定するもので、 ブラウザーなどのアドレスに使われます。
Node.js では 標準モジュール の一つである URL モジュールで URL の文字列の解析、生成などの機能が提供されています。 今回はその URL モジュールおよびそれに関連する Query String モジュールについて紹介します。

URL モジュールは 'url' を require して使用します。
 var url = require('url');

URL の解析

URL の文字列は parse 関数で各要素に分解したオブジェクトを取得できます。
url.parse(urlStr[, parseQueryString][, slashesDenoteHost])

URL の要素

普段、ユーザー名の指定などは使いませんが、 URL では意外と多くの要素を書くことができます。
次の文字列を例に要素に分解してみます。
http://user:pass@host.com:8080/p/a/t/h?query=string#hash
要素 説明
protocol 'http:' プロトコル(スキーム)の名称。 https:, ftp:, file: など。
大文字でもすべて小文字に変換される。
slashes true protocol のコロン(:)の後に '//' があるかどうか
auth 'user:pass' 認証情報(ユーザー名:パスワード)
port '8080' ポート番号
hostname 'host.com' ホスト名
大文字でもすべて小文字に変換される。
host 'host.com:8080' ホスト名 と ポート番号 を合わせたもの
search '?q=string' 検索文字列。
検索などの文字列の指定に使われることが多いため、 Node.js では GET メソッドのパラメーターを検索、クエリーなどと呼んでいる。
query 'q=string' 検索文字列の ? 以降
第二引数の指定で、さらにオブジェクトに分解できる。
pathname '/p/a/t/h' ホストに要求するパス名
path '/p/a/t/h?q=string' パス名 と 検索文字列 を合わせたもの
hash '#elemid' ページ内の位置を指定するために使う要素 ID。
href URL の全体。
protocol と host の小文字の変換やエスケープなどがあるので、元の文字列と全く同じとは限らない。
var urlStr = 'http://user:pass@host.com:8080/p/a/t/h?q=string#elemid';
url.parse(url.parse(urlStr));
// Url {
//   protocol: 'http:',
//   slashes: true,
//   auth: 'user:pass',
//   host: 'host.com:8080',
//   port: '8080',
//   hostname: 'host.com',
//   hash: '#elemid',
//   search: '?q=string',
//   query: 'q=string',
//   pathname: '/p/a/t/h',
//   path: '/p/a/t/h?q=string',
//   href: 'http://user:pass@host.com:8080/p/a/t/h?q=string#elemid' }

query の解析

parse 関数の第 2 引数を true に設定すると query の部分がオブジェクト(連想配列)に分解されます。
url.parse(urlStr, true);
 // Url {
 //     : 
 //   query: { q: 'string' },
 //     :

ネットワークコンピューター(SMB)表記の解析

Windows ではネットワーク上のコンピューターには "\\コンピューター名\共有フォルダー" のようにアクセスできます。
この \ を / にした表記で '//' の後をホスト名として扱いたい場合には parse の第 3 引数に true を設定します。
 var smbStr = '//pcname/share/foo';
 url.parse(smbStr));
 // Url {
 //    :
 //   slashes: null,
 //    :
 //   host: null,
 //    :
 //   hostname: null,
 //    :
 //   pathname: '//pcname/share/foo',
 //   path: '//pcname/share/foo',
 //   href: '//pcname/share/foo' }
 
 url.parse(smbStr, false, true));
 // Url {
 //    :
 //   slashes: true,
 //    :
 //   host: 'pcname',
 //    :
 //   hostname: 'pcname',
 //    :
 //   pathname: '/share/foo',
 //   path: '/share/foo',
 //   href: '//pcname/share/foo' }

URL の生成

URL の文字列は format 関数で各要素に分解されたオブジェクトから生成できます。
url.format(urlObj)

URL の要素

href、path など一部使わない要素がありますが、基本的に使う要素は解析時と同じものです。
要素 説明
protocol 'http' コロン(":") はあってもなくても自動的に不可
http, https, ftp, gopher, file の場合は '//' が生成時に追加される。 mailto や foo などそれ以外の場合は追加されない。
slashes true これを true に設定すると mailto などの場合でも '//' は追加。
ただし、http などでは false にしたとしても追加。
auth 'user:pass' 指定されていれば使用
port '8080' host が指定されていない場合に使用
hostname 'host.com' host が指定されていない場合に使用
host 指定されていれば、それが使われ、そうでなければ port と hostname から作成
search 'q=string' 指定されていれば使用。 "?" はあってもなくても自動的に不可
query host が指定されいない場合に使用。文字列またはオブジェクトで指定
pathname 'p/a/t/h' 先頭の "/" はあってもなくても自動的に不可
path 生成時には使用しない
hash 'elemid' 指定されていれば使用。 先頭の "#" はあってもなくても自動的に不可
href 生成時には使用しない
 var urlObj = {
    protocol: 'http',
    auth: 'user:pass',
    port: '8080',
    hostname: 'host.com',
    hash: 'elemid',
    search: 'q=string',
    pathname: 'p/a/t/h' };
 url.format(urlObj);
 // http://user:pass@host.com:8080/p/a/t/h?q=string#elemid
protocol、 slashes によって、 "//" の付く、付かないのサンプルです。
 // http などではない → "//" は付かない
 urlObj = {
    protocol: 'foo',
    hostname: 'host.com',
    pathname: '/p/a/t/h' };
 url.format(urlObj);
 // foo:host.com/p/a/t/h
 
 // http などではないが、 slashes を true → "//" が付く
 urlObj = {
    protocol: 'foo',
    slashes: true,
    hostname: 'host.com',
    pathname: '/p/a/t/h' };
 url.format(urlObj);
 // foo://host.com/p/a/t/h
 
 
 // http だけど、 slashes を false → "//" は付く
 urlObj = {
    protocol: 'http',
    slashes: false,
    hostname: 'host.com',
    pathname: '/p/a/t/h' };
 url.format(urlObj);
 // http://host.com/p/a/t/h
また、 http などであってもホスト名がない場合は '//' は表示されません。 その場合も slashes を true にすると '//' が表示されるようになります。
 // file だけどホスト名がない
 urlObj = {
    protocol: 'file',
    pathname: '/C:/foo/bar' };
 url.format(urlObj);   // file:/C:/foo/bar
    
 // file 、ホスト名がなし、 slashes を true
 urlObj = {
    protocol: 'file',
    slashes: true,
    pathname: '/C:/foo/bar' };
 url.format(urlObj);   // file:///C:/foo/bar

相対 URL の展開

ある URL から相対的な URL を生成する場合には resolve メソッドを使用します。
url.resolve(from, to)
例えば、あるページ("http://host.com/foo/index.html")にリンク(<a href="../bar/baz.html">xxxx</a>)があったとして、 そのリンク先の URL はどんな値になるかというのを調べるのに使えます。
 url.resolve("http://host.com/foo/index.html", "../bar/baz.html");
 // http://host.com/bar/baz.html

クエリー文字列

クエリー文字列は URL の解析、生成と一緒に解析、生成できます。 querystring モジュールを require すると、それだけ使うこともできます。 parse で解析、 stringify で生成です。
var querystring = require('querystring');

querystring.parse('foo=bar&baz=qux&baz=quux&corge');
// { foo: 'bar', baz: [ 'qux', 'quux' ], corge: '' }

querystring.stringify({ foo: 'bar', baz: [ 'qux', 'quux' ], corge: '' });
// foo=bar&baz=qux&baz=quux&corge=

エスケープ

URL ではスペースなどの一部記号や日本語など使えない文字があり、それらは % を使った表記にエスケープする必要があります。

クエリー文字列

クエリー文字列のオブジェクトでは検索などに使うためか 生成、解析時に自動でエンコード、デコードをしてくれます。
 var escQstrObj = {
     protocol: 'http',
     hostname: 'host.com',
     pathname: 'search.html',
     query: {q: '日本語'}
 };
 url.format(escQstrObj);
 // http://host.com/search.html?q=%E6%97%A5%E6%9C%AC%E8%AA%9E
 var escQstrStr = 'http://host.com/search.html?q=%E6%97%A5%E6%9C%AC%E8%AA%9E';
 url.parse(escQstrStr, true);
 // Url {
 //     :
 //   search: '?q=%E6%97%A5%E6%9C%AC%E8%AA%9E',
 //   query: { q: '日本語' },
 //   pathname: '/search.html',
 //      : 
ちなみにエンコード、デコードには JavaScript の関数である encodeURIComponent, decodeURIComponent を使用しているようです。
変えることはないと思いますが querystring の関数を直接使う場合はオプションで関数を変えることもできます。

URL

パス名などにクエリー文字列以外の部分に関しては、 生成時にスペースなどの一部記号のみエスケープしてくれます。
 var escObj = {
     protocol: 'file',
     slashes: true,
     pathname: '/C:/Program%20Files/nodejs/index.html'
 };
 url.format(escObj);
 // file:///C:/Program%20Files/nodejs/index.html
ただ、解析時にエスケープした文字を戻すということはしません。 逆になぜか解析時にも、空白などのエスケープをします。
しかも、対応しているのは ASCII 文字だけで日本語などの変換はしてくれません。

Wikipedia や Weblio のように検索文字がアドレスのパス名に使われることもありますし、 クエリー文字列以外でもちゃんとエンコード、デコードして欲しい場合があります。
そういう場合は直接 encodeURIComponent, decodeURIComponent を使った方がいいと思います。
 var escUrlObj = {
        protocol: 'https',
        hostname: 'ja.wikipedia.org',
        pathname: '/wiki/' + encodeURIComponent('メインページ')
 };
 url.format(escUrlObj);
 // https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8
 var escUrlStr = 'https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8';
 var parsedObj = url.parse(escUrlStr);
 decodeURIComponent(parsedObj.pathname);
 // '/wiki/ メインページ'

サンプルコード

記事で使用したサンプルコードは以下のリンクから取得(名前をつけて保存)できます。 これを node コマンドの引数に渡して実行します。 なお、日本語の出力は UTF-8 で行われるため、UTF-8 対応のコンソール(Windows PowerShell など)に出力しないと文字化けします。
$ node url.js 
node コマンドの使い方について詳しくは以前の記事をご覧ください。

続きを読む

スポンサーサイト
 

Node.js API (process) - シグナルイベント

プログラムの外部から終了等の操作をしたい場合、 Unix 系ではシグナルを使用します。 Windows ではシグナルはないのですが、 Node.js ではある程度仮想的に対応してくれます。
今回は Node.js でのシグナル処理について説明します。

なお、Node.js ではシグナル処理は 標準モジュール の一つである Process モジュールで機能が提供されています。

シグナルの送信

まずは Node.js でシグナルを送信方法です。
送信はグローバルオブジェクトである processkill メソッドを呼び出します。
process.kill(pid[, signal])
 // ID が 8888 のプロセスに対して SIGINT を送信
 process.kill(8888, 'SIGINT');
このメソッド名は Unix にある kill コマンドから来ています。 signal の引数を省略時には、 kill コマンドのように SIGTERM を送信します。

シグナルの送信がなぜ kill コマンドなのかというと、おそらく外部からシグナルを送る場合、プロセスを終了させるためのものが多いからだと思います。
kill もそうですが、子プロセスの生成を spawn(産卵)、親プロセスが終了したのに終わらないプロセスを zombie(ゾンビ) と生き死にかかわる呼び名が多いです。


なお、 Windows ではシグナルの送信は対応していません。
一応、 SIGINT, SIGTERM, SIGKILL は送れるようになっていますが、 実際に送っているのではなく、単にプロセスを強制終了させます。 また、これら以外のシグナルを送信しようとするとエラーが発生します。

シグナル受信イベント

シグナルを受け取ると process オブジェクトはシグナル名をイベント名としたイベントを発行します。 シグナルのイベントにリスナーを登録することによって、デフォルトの動作を変更することができます。
以下の例では SIGINT にリスナーを登録しています。
 process.on('SIGINT', () => {
     console.log('Got SIGINT. ');
 });
SIGINT を受け取るとデフォルトでは終了しますが、リスナーを登録したことによって終了しなくなります。
ただし、後述するように登録できないシグナルや登録してもデフォルトの動作が変わらないシグナルもあります。

終了シグナル

シグナルでポイントになるのが、プロセスの終了のためのシグナルです。 それらのシグナルを以下の表にあげました。

シグナル 機能 説明
SIGHUP 1 端末切断(Hang UP) 端末エミュレーターやコンソールを閉じた場合に発生。
Windows でもコンソールを閉じると発生するが、リスナーを登録しても 10 秒程度で無条件に終了する。
SIGINT 2 割り込み(INTerapt) 一般的にキーボードの Ctrl+C で発生。 キーボードから発信は Windows も対応
SIGBREAK - 停止 Windows 用のシグナルで Ctrl+Break で発生
SIGKILL 9 強制終了 リスナーの登録不可
SIGTERM 15 終了(TERMinate) kill コマンドの省略時のデフォルト。 Windows 未対応


デフォルトでは Node.js はこれらのシグナルを受信するとプログラムをすぐに終了します。
これは SIGKILL を除いて、リスナーを登録して動作を変更することができます。 ただ、慣習的なものもあるので、対応を含めて個別に紹介していきます。

SIGTERM, SIGKILL

SIGTERM通常の終了で、 SIGKILL強制終了です。 kill コマンドのデフォルトは、名前と違って SIGKILL ではなく、 SIGTERM の方になっています。

終わる前に後処理をしたいというときにイベントをキャッチします。
 process.on('SIGTERM', () => {
     console.log('Got SIGTERM. ');
     // 後処理など
     // :
     process.exit(128+15);
 });
終了時の終了ステータスには慣習があります。それらについては以前の記事を見て下さい。 なお、SIGTERM を受け取っても終了させないというのはあまりよくありません。 外部から終了する手段をなくすのは危険ですし、そもそも SIGKILL の動作は変更できません。

SIGINT, SIGBREAK

SIGINT は割り込みと言いつつ、基本は終了します。 それも強制終了という扱いなどではなく、簡単なプログラムでは Ctrl+C を通常の終了方法としているものもあります。
ただ、 CUI で Ctrl+C を使いたいという時もあるので、 これで終了しないプログラムも結構あります。


SIGBREAK は SIGINT と同じような用途ですが、強制終了の意味合いが強い気がします。 Ctrl+Break はそんなに押すようなキーではないですし、そもそも知らない人も多いので、 そのままにしておいてもいいのではないかとは思います。
 process.on('SIGBREAK', () => {
     console.log('Got SIGBREAK. ');
     process.exit(1);
 });

SIGHUP

SIGHUP は端末エミュレーターやコンソールを閉じると発生するイベントです。 通常、端末を切る際にはそこで実行させていたコマンドも終了させます。

サーバーなどで端末切っても動かしたいということはありますが、そういうときは nohup コマンドを使うので、 終了しないようにする必要はありません。
使い勝手のために SIGHUP で終わらないようにしてもよいですが、 その場合はオプションを付けて起動した場合のみにしておきます。 デフォルトで終わらないのは、ユーザーが気付かずにプロセスを残す危険性が高くなります。
 process.on('SIGHUP', ()=> {
     console.log('Got SIGHUP. ');
     // -d オプションが付いていたら、終了しない
     if (process.argv[2] != '-d') {
        process.exit(128+1);
     }
 });
なお、 Windows ではリスナーを登録しても、しばらくすると無条件で終了します。 終了を止めることはできませんし、ここで後処理を入れても完了できる保証はありません。

また、 Unix 系ではデーモンなどのように動かし続け、ユーザーによる起動をあまり想定していないプログラムでは SIGHUP は終了ではなく、設定の再読み込みとして使われます。 ただ、 Node.js でデーモンを作るということはあまり無いかと思います。

その他のシグナル

シグナルというのは、まだまだあるのですが、終了シグナル以外で Node.js のドキュメントで紹介されていたのを紹介します。

シグナル 説明
SIGUSR1 SIGUSR1, SIGUSR2 はプログラム側で自由に使っていいシグナルとして用意されてる。
Node.js ではそのうちの SIGUSR1 をデバッガーが起動に充てている。 リスナーの登録はできるが、デバッガーの起動を止めることはできない
SIGPIPE 接続の切れたソケットに書き込みを行うと発生。デフォルトではプロセスの終了。
SIGWINCH コンソールのサイズが変更された場合に発生。 Windows でもカーソルの移動などで送信。
SIGSTOP 一時停止用シグナル。 Node.js では設定不可

サンプルコード

今回は先にサンプルコードを紹介します。各ファイルはリンクの [名前を付けて保存] で取得出来ます。


送信用のスクリプトです。 引数に プロセス ID とシグナル名を指定して、シグナルを送信します。
$ node process_signal_kill.js 8132 SIGINT


受信側のスクリプトです。 起動すると以下のことを行います。
$ node process_signal_rec.js 
 Process ID =  9432
ちなみに exit イベントが発生せず、終了ステータスが表示されない場合、 Unit 系では $?、 Windows では %ERRORLEVEL% で表示できます。


また、後述するシグナルイベントの取得例を追加したサンプルも用意しています。 node コマンドの使い方について詳しくは以前の記事をご覧ください。
 

Node.js API (process) - exit によるプログラムの終了

プログラミング言語ではたいていプログラムの終了用に exit が用意されています。 Node.js でも Node.js API として exit があります。
今回は exit 関連の機能を紹介します。

プログラムの終了

終了ステータス

通常、 exit を使わなくても、 Node.js の起動時に指定したスクリプトの最後まで達すれば、プログラムは終了します。 このときの終了ステータス(exit code) は 0 です。

JavaScript のコードのエラーなどで Node.js がエラー終了した場合にはエラーの内容に対応した終了ステータスで終了します。 Node.js のエラーでなくとも、プログラムとして正常終了ではない場合は 0 以外の終了ステータスで終わるべきです。
exit は途中で終わるためだけでなく、適切な終了ステータスを設定するために exit は必要です。

実際に終了ステータスの値をどうするか というのはプログラム側で決める必要があります。 ただ、特に細かく決めたりしない場合は、正常終了でなければ 1 にしておけばいいと思います。
終了ステータスの仕様等について詳しく知りたい方は以下の記事の「終了ステータス」の節をみてください。

process.exit

Node.js の exit はグローバルオブジェクトである process のメソッドとして用意されています。
引数に渡した値を終了ステータスとしてプログラムを終了します。
process.exit(1);
引数を省略した場合のデフォルト値は 0 です。

終了イベント

exit 関連のイベントについて説明します。 イベントシステムの基本については以前の記事を見てください。

exit イベント

プログラムの終了前に呼ばれるイベントです。 リスナーの関数には終了ステータスが引数として渡されます。
process.on('exit',  (code)=> {
    console.log('Program exit. code = ', code);
});
基本的に終了状態のチェックなどに使うもので、 ここから非同期処理を実行して、終了を回避させるといったことはできません。
例えば、以下の例ではログは出力されません。
process.on('exit',  (code)=> {
    // 非同期は実行されない
    setImmediate(console.log, 'This will not run');
});

beforeExit イベント

beforeExit イベントはすべてのイベントループやスケジュールされた処理が終わり、終了される前に発生します。 こちらはリスナーの関数から非同期処理を実行することもできます。
ただ、スクリプトが最後までいって終了するときに発生するため、 process.exit で終了した場合にはこのイベントは発生しません。
process.once('beforeExit', () => {
    console.log("before exit");
});
なお、上記の例で once() を使っているのは on() ではリスナーが繰り返し呼ばれて、終了しなくなるためです。

サンプルコード

記事で使用したサンプルコードは以下のリンクから取得(名前をつけて保存)できます。 これを node コマンドの引数に渡して実行します。
$ node process_exit.js 
node コマンドの使い方について詳しくは以前の記事をご覧ください。

 
このページをシェア
アクセスカウンター
カレンダー(アーカイブ)
プルダウン 降順 昇順 年別

03月 | 2016年04月 | 05月
- - - - - 1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30


はてな新着記事
はてな人気記事
ブロとも申請フォーム
プロフィール

yohshiy

Author:yohshiy
職業プログラマー。
仕事は主に C++ ですが、軽い言語マニアなので、色々使っています。

はてブ:yohshiy のブックマーク
Twitter:@yohshiy

サイト紹介
プログラミング好きのブログです。プログラミング関連の話題や公開ソフトの開発記などを雑多に書いてます。ただ、たまに英語やネット系の話になることも。