Cocoaはやっぱり!
インターネットにアクセスしよう
Web Kit #2 : Webサイトの更新日時を調べる

今回のテーマ

今回のテーマは、Webサイトの更新日時を調べるというものです。HTTPプロトコルをご存知なら分かると思いますが、サーバにHEADコマンドを使うと取得できます。Web Foundationを使って、HTTPプロトコルを操る方法を解説します。

推奨環境 この解説は、以下の環境を前提に作成し、動作確認等を行っています。ご確認ください。

改版履歴

サンプルの概要

今回作成するサンプルは「 Webサーバ上のコンテンツの更新日時を取得する 」というものです。URLを入力して、Accessボタンをクリックするとサーバにアクセスして、更新日時を調べて表示します。また、勉強の意味で、HTTPのヘッダーも表示します。下図は、その画面と実行例です。

NSURLConnectionの概要

前回は、NSURLDownloadクラスを使ってサーバにアクセスしました。ただし、NSURLDownloadを使うと、必ずファイルにダウンロードすることになりますので、今回のようなテーマには不都合です。メモリ上だけで処理を完結させたい場合は、NSURLConnectionを使うことになります。簡単に言うと、ファイルに保存をしないNSURLDownloadNSURLConnectionです。ということで、今回は、このNSURLConnectionの使い方の解説になります。

まずは、NSURLDownloadと同様に、NSURLConnectionの基本的な動きをシーケンス図を使って把握しておきます。なお、以下の説明は、話を簡単にするためにHTTPプロトコルを使っているときのものになっています。

右側のMyClassが自分でコーディングするクラスです。このクラス内でNSURLConnectionのインスタンスを生成します。すると、サーバへのアクセス処理が自動的に開始されます。生成時には、NSURLConnectionからの様々な通知を受けるインスタンス、つまりデリゲート ( delegate ) も指定するようになっています。今回は、MyClassをデリゲートに指定したときの図になっています。

NSURLConnectionはサーバと通信を行って、まず最初にHTTPのヘッダーを取得します。取得できたらその通知がデリゲートに届きます。HTTPのヘッダーには、取得しようとしているコンテンツの種類や最終更新日などの情報が入っています。ここで、コンテンツの最終更新日時であるLast-Modifiedを取得するなどの処理を行います。

NSURLDownloadでは、ファイルのダウンロードパスの決定を行う処理がありましたが、NSURLConnectionではファイルにダウンロードは行いませんので、そのやりとりはありません。

その後は、データを受信する度に通知が来ますので経過表示などを行います。NSURLDownloadではダウンロードしたデータ量のみの通知でしたが、データそのものが渡ってきます。もし、ファイルへの保存が必要な場合は、このメソッド内で書き出すなどの処理が必要になります。ダウンロードが完了したら、完了通知が来ます。

NSURLConnectionとしての流れはこうなりますが、今回作成するサンプルは、ヘッダーに含まれている更新日時を取得するだけですので、データのダウンロードの部分は実際には動きません ( HEADコマンドを送信しますので、サーバはコンテンツを送ってこないためです )。

インスタンス構成図

ではインスタンスの構成図をみておきましょう。MyClassというのがコーディングする部分で、vUrlがURLを入力するNSTextField、vLastModが最終更新日を表示するNSTextField、vResultが経過表示を行うNSTextFieldです。Accessボタンがクリックされたら、MyClassのaccess : メソッドが呼ばれるようにしています。

アクセスの開始

さて、コードを見ていきましょう。まずは、Accessボタンをクリックした時に起動されるaccess : メソッドです。

レスポンス取得 : MyClass.m
- (IBAction) access : (id) sender { // (1) サーバーへのリクエストの作成 NSMutableURLRequest *req; req = [ NSMutableURLRequest requestWithURL : [ NSURL URLWithString : [ vUrl stringValue ] ] ]; // (2) メソッドをHEADに変更 [ req setHTTPMethod : @"HEAD" ]; // (3) サーバーへ接続 NSURLConnection *con = [ NSURLConnection connectionWithRequest : req delegate : self ]; }

(1) サーバへのリクエストを作成するわけですが、前回使用したのNSURLRequestではなく、そのサブクラスであるNSMutableURLRequestを使用します。「 Mutable 」とは「 変更可能 」という意味ですので、自由にサーバへのリクエストの内容を変更することが出来ます。インスタンスの生成の方法は同じで、requestWithURL : を使用します。

(2) そして、このリクエストの中身を書き換えます。HTTPのメソッド ( コマンド ) をコンテンツまで取得するGET ( GETがデフォルト値になっています ) から、情報のみを取得するHEADに変えます。メソッドを変えるには setHTTPMethod : メソッドを使用します。メソッド名は文字列でそのまま指定できます。GETとHEAD以外にもPOSTが使えます。CGIでPOSTしか受け付けないようなものとか、画像のアップロードなどではPOSTを使うことになります。「 画像をウィンドウにドロップするだけでアップロードするようなソフト 」も作れるわけです。

NSMutableURLRequest : HTTPのメソッドを変更する
書式 : (void) setHTTPMethod : (NSString *) method 入力 : method - メソッド名の文字列 ( ex. "GET" "HEAD" "POST" )

(3) リクエスト情報が変更できたので、ここでサーバへのアクセスを行ってもらいます。NSURLConnectionのインスタンスを connectionWithRequest : delegate : メソッドで作成するだけです。アクセスも自動的に開始します。メソッド名のconnectionをdownloadに置き換えるとNSURLDownloadのメソッド名になることが多いですが、このように、メソッド名の命名規則もNSURLConnectionとNSURLDownloadでは類似しています。

NSURLConnection : 指定リクエストのインスタンス生成とアクセス処理の開始を行う
書式 : + (NSURLConnection *) connectionWithRequest : (NSURLRequest *) request    delegate : (id ) delegate 入力 : request - ダウンロードするためのリクエスト情報    : delegate - ダウンロード処理中の通知先 出力 : RETURN - 初期化されたインスタンス
サーバからのレスポンスの取得

connectionWithRequest : delegate : メソッドでアクセス処理が開始されます。サーバに対してリクエストを送信すると、それに対してのレスポンスが返ってきます。そうすると、デリゲートに指定したインスタンスの connection : didReceiveResponse : メソッドが呼び出されます。ここで、サーバからのレスポンスの内容を知ることが出来ます。

レスポンス取得通知 : MyClass.m
- (void) connection : (NSURLConnection *) connection didReceiveResponse : (NSURLResponse *) response { //----- 最終更新日の表示 -----// // (1) HTTPのヘッダー全体を辞書として取得 NSDictionary *dicHead = [ (NSHTTPURLResponse *) response allHeaderFields ]; // (2) Last-Modifiedのフィールドを取得 NSString *sLastMod = [ dicHead objectForKey : @"Last-Modified" ]; // (3) 文字列からNSDateに変換 NSCalendarDate *dateLastMod = [ NSCalendarDate dateWithString : sLastMod calendarFormat : @"%a, %d %b %Y %H:%M:%S %Z"]; // (4) ローカルタイムの文字列を生成 NSString *sLastModLocal = [ dateLastMod descriptionWithCalendarFormat : @"%Y.%m.%d %a %H:%M:%S %Z" timeZone : nil locale : nil ]; // vLastModに出力 [ vLastMod setStringValue : [ NSString stringWithFormat : @"%@ ( %@ )", sLastModLocal, sLastMod ] ]; //----- HTTPヘッダー全体の表示 -----// NSEnumerator *enumHead = [ dicHead keyEnumerator ]; NSMutableString *sHead = [ NSMutableString string ]; id key; while ( ( key = [ enumHead nextObject ] ) ) { NSString *sValue = [ dicHead objectForKey : key ]; [ sHead appendFormat : @"%@ = %@", key, sValue ]; [ sHead appendFormat : @"%C", 0x000D ]; // 改行 } [ vResponse setString : sHead ]; }

(1) このメソッドの第2パラメータには、サーバからのレスポンスのインスタンスが渡ってきますが、これをサブクラスのNSHTTPURLResposeにキャストして参照します ( 本来は、NSHTTPURLResposeクラスのインスタンスかどうかをチェックすべきです )。NSHTTPURLResponseは、サーバからのレスポンスを扱うクラスをHTTPに特化したものです。allHeaderFields メソッドを使うことで、サーバから受け取ったヘッダ全てを辞書形式で取得できます。HTTPのフィールド名をキーとした辞書になっています。

(2) ですので、この辞書からobjectForKey : で各フィールドの値を取得することが出来ます。HTTPヘッダーのフィールド名を文字列でキーとして渡すだけです。

(3) 一番最初の図を見ると分かりますが、HTTPヘッダーに入っているLast-Modifiedの日時の形式は「 Tue, 15 Jul 2003 14:32:25 GMT 」のようになっています。厳密には、形式にはいくつか種類がありますが、この形式が最もメジャーです。通常世界標準時のGMTで表記されていますが、分かりにくいのでローカルタイムに変換しましょう。日時を扱うクラスのNSCalendarDateのインスタンスを生成すれば、ローカルタイムの文字列を生成できます。

文字列からNSCalendarDateを生成するには、dateWithString : calendarFormat : が便利です。「 %aは曜日を表す文字列を切り出す 」指定といった感じで定義されていますので、切り出したいもの ( 年月日時分秒等 ) をパーセント記号と合わせて文字列を指定することで自動的に切り出してインスタンスを生成してくれます。

(4) NSCalendarDateが生成できたら、今度はローカルタイムの文字列を取得します。これも、descriptionWithCalendarFormat : timeZone : local : という出力形式が指定できるメソッドがありますので、これを使います。こちらも同様にパーセント記号を用いて形式を指定します。第2パラメータ以降にはnilをいれておけば、システムの設定に従ってくれますので、これでローカルの時刻になります。文字列が出来たら、vLastModにsetStringValue : して画面に表示します。

その後は、最所に辞書に取り出したヘッダーの内容をvResponseに出力して終わりです。ここは特に新しい内容はありません。

NSHTTPURLRespose : HTTPヘッダーの全内容を辞書形式で取得
書式 : - ( NSDictionary *) allHeaderFields 出力 : RETURN - サーバからの全レスポンス ( HTTPヘッダー ) をフィールド名をキーとして生成した辞書