nsIWebBrowserPersist の基本的な使い方 (5) ~ ダウンロード進捗状況
nsIWebBrowserPersist で HTTP によってダウンロードする際、ダウンロードの進捗状況を監視する。
サンプルコードは nsIWebBrowserPersist の基本的な使い方 (1) ~ 基本形 をベースとしており、一部省略しています。
saveURI の場合
nsIWebBrowserPersist の progressListener プロパティに nsIWebProgressListener インタフェースを実装したオブジェクトを自前で作成してセットすると、ダウンロードの状況の変化によって以下のメソッドが呼び出される。
メソッド名 | 呼び出されるタイミング |
---|---|
onStateChange | 引数 aStateFlags の値を調べ、 nsIWebProgressListener.STATE_START フラグが立っている場合はダウンロード開始、 nsIWebProgressListener.STATE_STOP フラグが立っている場合はダウンロード終了。 |
onProgressChange | ダウンロード進行中に呼び出される。引数 aCurSelfProgress, aMaxSelfProgress の値を調べることで、合計何バイト中の何バイトをダウンロードしたかがわかる。 |
onLocationChange | 呼び出し無し。(xul:tabbrowser 要素の持つ nsIWebProgress オブジェクト専用?) |
onStatusChange | |
onSecurityChange |
wbp.progressListener = { // implements nsIWebProgressListener onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) { if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) dump("started "); if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) dump("stopped "); }, onProgressChange: function (aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) { dump("downloading... " + aCurSelfProgress + "/" + aMaxSelfProgress + " "); }, onLocationChange: function (aWebProgress, aRequest, aLocation) {}, onStatusChange : function (aWebProgress, aRequest, aStatus, aMessage) {}, onSecurityChange: function (aWebProgress, aRequest, aState) {}, };
saveChannel の場合
saveURI の場合と異なり、なぜか nsIWebProgressListener の onProgressChange メソッドが呼び出されない。代わりに、 nsIChannel の notificationCallbacks プロパティに nsIProgressEventSink インタフェースを実装する。
参考: nsIWebBrowserPersist.saveChannel – やんばるもじら
channel.notificationCallbacks = { QueryInterface: function (aIID) { if (aIID.equals(Ci.nsIProgressEventSink)) return this; Components.returnCode = Cr.NS_ERROR_NO_INTERFACE; return null; }, // implements nsIInterfaceRequestor getInterface: function (aIID, aInstance) { return this.QueryInterface(aIID); }, // implements nsIProgressEventSink onProgress: function (aRequest, aContext, aProgress, aProgressMax) { dump("downloading... " + aProgress + "/" + aProgressMax + " "); }, onStatus: function (aRequest, aContext, aStatus, aStatusArg) {}, };
nsIChannel.notificationCallbacks プロパティは nsIInterfaceRequestor 型であり、 nsIChannel オブジェクトに発生する様々なイベントに応じて、まずはじめに getInterface メソッドが呼び出される。 getInterface メソッドは引数にて指定されたインタフェースへと QI して返すだけ。
なお、 nsIChannel.asyncOpen によって要求開始した場合、 notificationCallbacks に nsIRequestObserver インタフェースを実装することで、要求開始時と要求終了時に onStartRequest, onStopRequest メソッドが呼び出されるが、 nsIWebBrowserPersist.saveChannel によるダウンロードではこの呼び出しは発生しない。
別の方法として、 notificationCallbacks プロパティに nsIWebBrowserPersist オブジェクト自体をセットする手もあるようだ。
wbp.progressListener = { /* snip */ }; channel.notificationCallbacks = wbp;
参考
nsIWebProgressListener.idl
nsIInterfaceRequestor.idl
nsIProgressEventSink.idl
関連記事
nsIWebBrowserPersist の基本的な使い方 (1) ~ 基本形
nsIWebBrowserPersist の基本的な使い方 (2) ~ persistFlags
nsIWebBrowserPersist の基本的な使い方 (3) ~ 各種ヘッダの追加
nsIWebBrowserPersist の基本的な使い方 (4) ~ POST メソッド
nsIWebBrowserPersist の基本的な使い方 (5) ~ ダウンロード進捗状況
つづく…?
nsIWebBrowserPersist の基本的な使い方 (4) ~ POST メソッド
HTTP の POST メソッドで Web サーバへ要求し、その回答結果をファイルへ保存する。
ほとんど nsIHttpChannel の使用がメインですので、ファイル保存する場合以外にも応用可能です。
また、サンプルコードは nsIWebBrowserPersist の基本的な使い方 (1) ~ 基本形 をベースとしており、一部省略しています。
saveURI の場合
saveURI の第4引数 aPostData を使えばできるはずだが、現在調査中…
saveChannel の場合
例によって nsIURL オブジェクトから nsIHttpChannel オブジェクトを生成する。
// make nsIHttpChannel var ioSvc = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); var channel = ioSvc.newChannelFromURI(url).QueryInterface(Ci.nsIHttpChannel);
POST するデータは以下のような文字列とする。以下の場合は文字列全体を単純に encodeURI 関数で URL エンコードすれば良いが、キーや値に & や = を含む場合を考慮した、より一般的な URL エンコードの方法については後述する。
var postStr = encodeURI("foo=bar&baz=eek&名前=太郎");
POST する文字列から nsIStringInputStream を生成し、 nsIHttpChannel を nsIUploadChannel へ QI してからストリームをセットする。今回の例では Content-type は application/x-www-form-urlencoded だが、 XML データを POST する場合などは適宜 application/xml とかにする。
// make nsIStringInputStream to post var inputStream = Cc["@mozilla.org/io/string-input-stream;1"] .createInstance(Ci.nsIStringInputStream); inputStream.setData(postStr, postStr.length); // set nsIStringInputStream to nsIUploadChannel var uploadChannel = channel.QueryInterface(Ci.nsIUploadChannel); uploadChannel.setUploadStream(inputStream, "application/x-www-form-urlencoded", -1);
nsIWebBrowserPersist で送信する前に、 nsIHttpChannel オブジェクトの requestMethod プロパティを POST にするのを忘れずに。これをしないと、なぜか PUT メソッドでの要求になってしまう。
// must do this otherwise request method will be "PUT" channel.requestMethod = "POST"; // save channel to file var wbp = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] .createInstance(Ci.nsIWebBrowserPersist); wbp.saveChannel(channel, file);
POST する文字列の URL エンコード
POST する文字列がユーザからの入力である場合、 & や = を含む場合を考慮し、以下のように encodeURIComponent を使って URL エンコードする。
// make string to post var postObj = { "foo": "bar", "baz": "eek", "名前": "山田=太郎&花子", }; var pairs = []; for (var [key, val] in Iterator(postObj)) { pairs.push(encodeURIComponent(key) + "=" + encodeURIComponent(val)); } var postStr = pairs.join("&");
関連記事
nsIWebBrowserPersist の基本的な使い方 (1) ~ 基本形
nsIWebBrowserPersist の基本的な使い方 (2) ~ persistFlags
nsIWebBrowserPersist の基本的な使い方 (3) ~ 各種ヘッダの追加
nsIWebBrowserPersist の基本的な使い方 (4) ~ POST メソッド
nsIWebBrowserPersist の基本的な使い方 (5) ~ ダウンロード進捗状況
つづく…?
nsIWebBrowserPersist の基本的な使い方 (3) ~ 各種ヘッダの追加
リファラを指定する
事前準備として、リファラの URL 文字列から nsIURL オブジェクトを生成する。
var refURL = Cc["@mozilla.org/network/standard-url;1"].createInstance(Ci.nsIURL); refURL.spec = "http://www.itmedia.co.jp/";
saveURI の場合
saveURI メソッドの第3引数 aReferrer に nsIURI オブジェクトを指定することでリファラをセットできる。
Live HTTP Headers で要求時のヘッダを見ると、「Referer: http://www.itmedia.co.jp/」というヘッダが付いている。
wbp.saveURI(url, null, refURL, null, null, file);
saveChannel の場合
nsIHttpChannel の referrer プロパティに nsIURI オブジェクトを指定する。
channel.referrer = refURL;
任意のヘッダを指定する
saveURI の場合
saveURI メソッドの第5引数 aExtraHeaders にヘッダを文字列として指定すると、HTTP要求時にそのヘッダを追加できる。
ヘッダは「Foo: bar
」のような形式で指定する。ただし、既存ヘッダを上書きすることはできず、必ず追加される。
例えば以下のようにすると、「Accept-Encoding: gzip,deflate, xxx」となる。
wbp.saveURI(url, null, null, null, "Accept-Encoding: xxx ", file);
saveChannel の場合
nsIHttpChannel では、 setRequestHeader メソッドで自由にヘッダを追加できる。
第3引数 aMerge を true にすると既存ヘッダを上書きせずに追加し、 false にすると既存ヘッダを上書きする。
channel.setRequestHeader("User-Agent", "HTTP Downloader", false);
以下のようにすると、「Accept-Encoding」ヘッダ自体を送らなくすることができる。
channel.setRequestHeader("Accept-Encoding", null, false);
関連記事
nsIWebBrowserPersist の基本的な使い方 (1) ~ 基本形
nsIWebBrowserPersist の基本的な使い方 (2) ~ persistFlags
nsIWebBrowserPersist の基本的な使い方 (3) ~ 各種ヘッダの追加
nsIWebBrowserPersist の基本的な使い方 (4) ~ POST メソッド
nsIWebBrowserPersist の基本的な使い方 (5) ~ ダウンロード進捗状況
つづく…?
nsIWebBrowserPersist の基本的な使い方 (2) ~ persistFlags
saveURI / saveChannel を実行する前に nsIWebBrowserPersist オブジェクトの persistFlags プロパティに各種フラグをセットすることで、ダウンロードや保存の方式のオプションを指定できる。
PERSIST_FLAGS_BYPASS_CACHE
このフラグを立てると、キャッシュを無視して毎回Webサーバからダウンロードする。
厳密には、要求時に「Pragma: no-cache」「Cache-Control: no-cache」の2つのヘッダが付加される。
wbp.persistFlags |= wbp.PERSIST_FLAGS_BYPASS_CACHE;
PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION
このフラグを立てると、gzip圧縮転送された場合に非圧縮状態でファイルへ保存してくれる。
このフラグを立てないと、gzip圧縮転送されたデータは圧縮されたままファイルへと保存してしまうので注意。
あえて圧縮状態で保存する理由が思いつかないので、基本的にこのフラグは必須と考えてよさそう。
wbp.persistFlags |= wbp.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
PERSIST_FLAGS_APPEND_TO_FILE
Firefox 3 以降で対応。このフラグを立てると、ダウンロードしたデータを既存ファイルへ上書きせずに追記する。
wbp.persistFlags |= wbp.PERSIST_FLAGS_APPEND_TO_FILE;
参考
上記以外にも色々なフラグがある。詳しくは nsIWebBrowserPersist.idl を参照。
nsIWebBrowserPersist.idl
関連記事
nsIWebBrowserPersist の基本的な使い方 (1) ~ 基本形
nsIWebBrowserPersist の基本的な使い方 (2) ~ persistFlags
nsIWebBrowserPersist の基本的な使い方 (3) ~ 各種ヘッダの追加
nsIWebBrowserPersist の基本的な使い方 (4) ~ POST メソッド
nsIWebBrowserPersist の基本的な使い方 (5) ~ ダウンロード進捗状況
つづく…?
nsIWebBrowserPersist の基本的な使い方 (1) ~ 基本形
指定したURLからダウンロードしてファイルへ保存する際には nsIWebBrowserPersist が便利。
HTTPだけでなく、FTP、file:やchrome:プロトコルなどのURLからのファイル保存も可能ですが、ここではHTTPを前提とします。
nsIWebBrowserPersist の主要メソッド
メソッド | 概要 |
---|---|
saveURI() | 指定したURIからダウンロードしてファイルへ保存する。 |
saveChannel() | 指定したチャネル(nsIHttpChannel など)をファイルへ保存する。 |
saveDocument() | 指定したDOMドキュメントをファイルへ保存する。 |
基本形
指定したURLのデータをダウンロードし、指定したパスのファイルへと保存するだけの簡単なコードを作る。
まずは URL 文字列から nsIURL オブジェクトを、ファイルパスから nsILocalFile オブジェクトを生成する。
const URL_SPEC = "http://www.mozilla.com/img/firefox-title.png"; const FILE_PATH = "C:firefox-title.png"; // make nsIURL var url = Cc["@mozilla.org/network/standard-url;1"].createInstance(Ci.nsIURL); url.spec = URL_SPEC; // make nsILocalFile var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); file.initWithPath(FILE_PATH);
saveURI の場合
nsIWebBrowserPersist のインスタンスを生成し、先ほどの2つのオブジェクトを引数に saveURI を実行するだけ。
// save URL to file var wbp = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] .createInstance(Ci.nsIWebBrowserPersist); wbp.saveURI(url, null, null, null, null, file);
saveChannel の場合
色々なものを引数で指定できる便利屋的な saveURI メソッドに対して、 saveChannel メソッドは nsIChannel を引数とすることでより詳細な実装が可能。
nsIIOService を使って nsIURL オブジェクトから nsIHttpChannel オブジェクトを生成し、 nsIHttpChannel と nsILocalFile を引数に saveChannel メソッドを実行する。
// make nsIHttpChannel var ioSvc = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); var channel = ioSvc.newChannelFromURI(url).QueryInterface(Ci.nsIHttpChannel); // save channel to file var wbp = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] .createInstance(Ci.nsIWebBrowserPersist); wbp.saveChannel(channel, file);
なお、 nsIChannel を saveChannel に渡す前に open / asyncOpen してはならない。
参考
nsIWebBrowserPersist.idl
nsIIOService.idl
nsIChannel.idl
nsIHttpChannel.idl
関連記事
nsIWebBrowserPersist の基本的な使い方 (1) ~ 基本形
nsIWebBrowserPersist の基本的な使い方 (2) ~ persistFlags
nsIWebBrowserPersist の基本的な使い方 (3) ~ 各種ヘッダの追加
nsIWebBrowserPersist の基本的な使い方 (4) ~ POST メソッド
nsIWebBrowserPersist の基本的な使い方 (5) ~ ダウンロード進捗状況
つづく…?
nsIWebBrowserPersist で gzip 圧縮されたデータをダウンロードする
HTTPでダウンロードする際は nsIWebBrowserPersist::saveURI や saveChannel が便利だ。
しかし、gzip圧縮形式で転送されたデータ(つまりWebサーバが「Content-Encoding: gzip」ヘッダを付加した場合)をダウンロードすると、圧縮されたままの状態でファイルへと保存してしまう。
非圧縮状態でファイルへ保存するためには、 nsIWebBrowserPersist::persistFlags プロパティに PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION フラグを立てる。
あえて圧縮状態で保存する理由が思いつかないので、基本的にこのフラグは必須と考えてよさそう。
参考
余談
ScrapBook でgzip圧縮されたページを保存すると画像がすべて壊れて表示されなかったり、 Amazon や はてな のページを保存すると、ツリーの favicon が消滅してしまう、といった問題はすべて上記の問題によるものだった。さきほど修正して新しいバージョンをリリースした。
なぜこんな簡単なことに今まで気付かなかったのだろうか…。
しかも気付く前に Bugzilla にバグを立ててしまった。
Bug 416817 – nsIWebBrowserPersist should decompress data if server responses with "Content-Encoding: gzip" header