HTTPモジュールはHTTPサーバを生成するだけでなく、HTTPクライアントを生成することもできる。
http.getとprocessオブジェクトを使って、外部のWebページをコマンドラインで取得する。

http.requestは、GET、POST、DELETE、PUT、OPTIONなど、任意の種類のHTTPリクエストを生成できる。GETリクエストについては、http.getという短縮系がある。

サーバを生成する訳ではないので、fetch.jsという名前でファイルをつくる。


fetch.js
var http = require('http');
var urlOpts = {host: 'www.nodejs.org', path: '/', port: '80'};

http.get(urlOpts, function(res){
res.on('data', function(chunk){
console.log(chunk.toString());
});
});

コンテンツを取得するだけであれば、これで終わり。

ターミナル
$ hotnode fetch.js 

このスクリプトを実行すると、コンソールにnodejs.orgのHTMLが出力される。
次に、エラーハンドリングを加えて、取得URLを設定できるようにしてみる。
太字追加

fetch.js
var http = require('http');
var url = require('url');
var urlOpts = {host: 'www.nodejs.org', path: '/', port: '80'};

if(process.argv[2]){
if(!process.argv[2].matcåh('http://')){
process.argv[2] = 'http://' + process.argv[2];
}
urlOpts = url.parse(process.argv[2]);
}

http.get(urlOpts, function(res){
console.log(urlOpts);
res.on('data', function(chunk){
console.log(chunk.toString());
}).on('error',function(e){
console.log('エラー:' + e.message);
});
});

ターミナル
$ hotnode fetch.js www.google.com

このように引数にURLを指定すると、そのURLにリクエストを送信し、コンテンツをコンソールに出力する。


http.getはリクエスト先の情報を定義したオブジェクトを引数にとる。(URL文字列をとることもできる)
ここでは、urlOpts変数に情報を格納してhttp.getに渡している。www.nodejs.orgにアクセスするよう、デフォルト値をurlOpts変数に設定している。
process.argvを取得して、取得先のURLがコマンドラインで入力されたかどうか確認する。processはconsoleと同じくNodeのグローバルオブジェクトで、requireで読み込む必要がなく、どこからでも呼び出すことができる。process.argv[2]はコマンドラインの3つ目の引数。[0]と[1]にはそれぞれnodeとfetch.jsが格納されている。
process.argv[2]が存在する場合(つまりアドレスが指定されている場合)、そのアドレスをurl.parseでオブジェクトに変換する。このとき、アドレスにhttp://が付与されていない場合は自動的に付与する。このオブジェクトでurlOptsのデフォルト値を上書きする。url.parseが返すオブジェクトは、http.getがとる引数のオブジェクトと同じプロパティを持っている。
サーバからレスポンスを受け取ると、そのレスポンスに対してHTTPクライアントとして処理を行う。http.getのコールバック関数で、resオブジェクトのdataイベントを待機する。resのデータストリームを受信すると、そのchunkをコンソールに出力する。




Nodeクックブック p.44
index.xml

<Alloy>

<Window>

<ImageView id="cake"></ImageView>

</Window>

</Alloy>


index.js

var cakeArray = new Array();

cakeArray[0] = "shortcake.png";

cakeArray[1] = "montblanc.png";

cakeArray[2] = "creampuff.png";

var cakeSelect = Math.floor(Math.random() * cakeArray.length);

$.cake.image = cakeArray[cakeSelect];

$.index.open();

public
すべてのクラスに公開
外から書き換えられる

private
非公開
外から書き換えられなし、見ることもできない



例えば下記の場合、counterで生成したインスタンスの数を数える。
counterは見えないが、getCounterでGETすることはできる。

Rectangle.java
public
class Rectangle {

  int width;

  int height;

  private static int counter = 0;

  int number;

  public Rectangle(){

    setSize(10,20);

    number = counter;

    counter++;

  }

  public Rectangle(int width,int height){

    setSize(width,height);

    number = counter;

    counter++;

  }

  public void setSize(int width, int height){

    this.width = width;

    this.height  = height;

  }

  public void printSize(){

    System.out.println("Rectangle(" + width + "," + height +")");

  }

  public static int getCounter(){

  return counter;

  }

}



なぜか・・・
まず、外から操作をさせない。できるべきものでもない、から。
それによってバグが起きてしまうかもしれないから。

もう1つの理由。
別に、publicでも動く。
でも、複数人で1つのものをつくっていたりすると、
例えば、
普通、書き換えることはないものでも、
publicになっていたら、そのcounterが外から書き換えることのあるものだと勘違いするかもしれない。
ある意味親切にするために、わかりやすくするために、privateはprivate、publicはpublicでかく。
POSTだけでなく、PUTリクエストを使ってファイルをアップロードすることもできる。

PUTリクエストでは1回のリクエストに1つのファイルしか送れないが、ファイルストリームを直接サーバに送るだけでことが済み、サーバサイドでリクエストをパースする必要がない。
ので、サーバサイドでの処理が楽になる。

HTMLファイル側でformのmethod属性をPOSTからPUTに変更するだけでこれが実現できれば良いのだが…残念ながらHTMLの基準のformではPUTメソッドを使用できない。ただし、現在策定中のXMLHttpRequest level2(xhr2)では、JavaScriptを使ってPUTメソッドでバイナリデータを送信することができる。

XMLHttpRequestを使ってPUTリクエストでファイルアップロードを実践してみる。
put_upload.htmlとserver.jsとアップロードファイルを保持するuploadsディレクトリを作成する。

put_upload.html
<html>
<body>
<form id="form">
<input type="file" id="userfile" name="userfile"><br>
<input type="submit">
</form>
<script>
(function(){
var userfile = document.getElementById('userfile'),
form = document.getElementById('form'),
file;

userfile.addEventListener('change',function(){
file = this.files[0];
});

form.addEventListener('submit',function(e){
e.preventDefault();
if(file){
var xhr = new XMLHttpRequest();
xhr.file = file;
xhr.open('put',window.location,true);
xhr.setRequestHeader('x-uploadedfilename',file.fileName || file.name);
xhr.send(file);
file = '';
form.reset();
}
});
})();
</script>
</body>
</html>

server.js
var http = require('http');
var fs = require('fs');
var form = fs.readFileSync('put_upload.html');

http.createServer(function (req,res){
if(req.method === 'GET'){
res.writeHead(200,{'Content-Type': 'text/html'});
res.end(form);
}
if(req.method === 'PUT'){
var fileData = new Buffer(+req.headers['content-length']);
var bufferOffset = 0;
req.on('data',function(chunk){
chunk.copy(fileData,bufferOffset);
bufferOffset += chunk.length;
}).on('end',function(){
var rand = (Math.random() * Math.random()).toString(16).replace('.','');
var to = 'uploads/' + rand + '-' +req.headers['x-uploadedfilename'];
fs.writeFile(to,fileData,function(err){
if(err){throw err;}
console.log('ファイルを' + to + 'に保存しました');
res.end();
});
});
}
}).listen(8080);

(put_upload.html)
input#userfileのchangeイベントを受け取ると、file変数でファイルポインタを取得する。そして、xhrでPUTリクエストを発行する。

この例では、1回のリクエストでファイルを1つだけ送信しているが、拡張して複数のファイルを非同期で一度にサーバに転送することも可能。

このコードではまず、ファイルを選択するinput要素にchangeイベントリスナを与えて、ユーザがファイルを選択した時点でファイルポインタを取得する。フォームがsubmitされると、デフォルトのフォームアクションを無効化して、XMLHttpRequestオブジェクトを生成しPUTリクエストをサーバに送るように設定する。また、サーバがファイル名を認識できるよう、カスタムヘッダを付与する。

(server.js)
このサーバはリクエストのdataイベントを待ち、送信されるchunkをまとめている。だたし、ここではデータ文字列を結合する方法はとっておらず、Bufferオブジェクトを生成してバッファ内でchunkを結合している。バイナリデータを取り扱う際に文字列タイプを使ってしまうと、データの結合時にデータ内容を破壊してしまうが、Bufferオブジェクトはバイナリを含めてどのようなデータタイプも取り扱うことができる。
リクエストのendイベントが発行されると、formidableと同様のランダムなファイル名を付与してuploadsフォルダに格納する。



Nodeクックブック p.41
multipartメッセージからデータとそれぞれのファイルを抽出する。

アップロードされたファイルを扱うだけでなく、一般的なPOSTデータも扱う。
fieldイベントを受け取ることでフォーム入力値を扱うことができる。
フォーム入力値とファイル両方を扱うこともできる。


formidableは自動的にフィールドデータサイズの制限を設定するため、手動で制限する必要はないが、incoming.maxFieldSizeを設定することでこの制限の変更が可能。
デフォルトは2MB。
この制限値は、アップロードファイル以外のすべてのフィールドデータの合計値に対する制限で、アップロードファイルのサイズには適用されない。


formidableがuploadsディレクトリにファイルを保存する際には、ファイル名はランダムな16進数の値となる。これは、ユニークなファイル名を生成することにより、同名のファイルを上書きしないようにするための処置。

ユニークなファイル名で既存ファイルに上書きしないようにした上で、ファイル名で内容を判別できるようにするためには、formidableからfileBeginイベント(multipartのファイルを受け取る度に発生)を受け取る。

server.js
var http = require('http');
var formidable = require('formidable');
var form = require('fs').readFileSync('form.html');

http.createServer(function (req,res){
if(req.method === 'GET'){
res.writeHead(200,{'Content-Type': 'text/html'});
res.end(form);
}
if(req.method === 'POST'){
var incoming = new formidable.IncomingForm();
incoming.uploadDir = 'uploads';
incoming.on('fileBegin',function(field,file){
if(file.name){
file.path += '-' + file.name;
}
}).on('file',function(field,file){
if(!file.size){return;}
res.write(file.name + 'を受け取りました\n')
}).on('field',function(field,value){
res.write(field + ':' + value + '\n');
}).on('end',function(){
res.end('すべてのファイルを受け取りました');
});
incoming.parse(req);
}
}).listen(8080);

formidableが生成したランダムなファイル名にオリジナルのファイル名を追記した。
ファイル名  ランダムな16進数-アップロードされたファイル名
ex)    53bf1b88b3f2fea77adda332cf0fa3bd-form.html
これで、ファイルの内容を判別することができる。
しかし、アプリケーションを開発する際は多くの場合、アップロードファイルの内容はデータベースに送られて、ランダムに生成されたファイル名と関連づけられるため、ファイル名自体を変更することはあまり多くないかもしれない。



Nodeクックブック p.37