Home > プログラミング > CFNetworkでHTTP GET

CFNetworkでHTTP GET

似たような記事はあちこちにあるけど、とりあえずまとめてみる。

とりあえずコード。単なるテストなのでごちゃごちゃ。

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // insert code here...
	CFURLRef theURL = CFURLCreateWithString(kCFAllocatorDefault, CFSTR("http://mixi.jp"), NULL);
	CFHTTPMessageRef theRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), theURL, kCFHTTPVersion1_1);
	CFReadStreamRef theStream = CFReadStreamCreateForHTTPRequest(NULL, theRequest);
	CFReadStreamOpen(theStream);
	CFIndex n;

	UInt8 theBuffer[8192];
	CFMutableDataRef theCFData = CFDataCreateMutable( NULL, 0 );

	while(true)
	{
		n = CFReadStreamRead(theStream, theBuffer, sizeof(theBuffer));
		if( n == 0 )
			break;

		CFDataAppendBytes( theCFData, theBuffer, n );
	}
	CFStringRef theContents = CFStringCreateFromExternalRepresentation( NULL, theCFData, kCFStringEncodingEUC_JP );
	CFRelease(theCFData);
//	CFShowStr(theContents);

//	CFIndex stringLength1 = CFStringGetMaximumSizeOfFileSystemRepresentation( theContents );
	CFIndex stringLength = CFStringGetMaximumSizeForEncoding( CFStringGetLength(theContents), kCFStringEncodingUTF8 );

	char *c = new char[stringLength];
	CFStringGetCString( theContents, c, stringLength, kCFStringEncodingUTF8 );
	puts(c);
	delete [] c;

	puts("----------------------");
	CFHTTPMessageRef responseHeader = (CFHTTPMessageRef)CFReadStreamCopyProperty(theStream, kCFStreamPropertyHTTPResponseHeader);
	CFDictionaryRef headers = CFHTTPMessageCopyAllHeaderFields(responseHeader);

	CFIndex count = CFDictionaryGetCount(headers);
	if( count > 0 )
	{
		CFStringRef *keys = new CFStringRef[count];
		CFStringRef *values = new CFStringRef[count];

		CFDictionaryGetKeysAndValues(headers, (const void **)keys, (const void **)values);

		for( int i = 0; i < count; i++ )
		{
			CFShow( keys[i] );
			CFShow( values[i] );
			puts("");
		}

		delete [] keys;
		delete [] values;
	}
	CFRelease(headers);

	puts("----------------------");

	CFRelease(theRequest);
	CFRelease(theURL);

	[pool drain];
    return 0;
}

解説

CFURLRef theURL = CFURLCreateWithString(kCFAllocatorDefault, CFSTR("http://mixi.jp"), NULL);

まずCFURLRefを作る。CFなんとかRefというのは実はポインタ型なので、*はつけなくていい。

CFSTR()は普通のC文字列(charの配列)から簡易CFStringRef型の文字列を作る。確か、内部で使い回しの領域に文字列オブジェクトを置くので、CFRelease()しなくてよいので使いやすい。その代わり、いつまでも内容が保証されるわけじゃないので、今回のようにすぐに渡してそれっきりにするのがよい。

CFURLCreateWithString()でCFURLRefインスタンスを作る。

一般にCFなんとかを生成する関数(コンストラクタ?)はアロケータを指定するけど、大抵はkCFAllocatorDefaultとかNULLを渡しておけばよいようだ

CFHTTPMessageRef theRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), theURL, kCFHTTPVersion1_1);

CFHTTPMessageRefのインスタンスを作る。GETリクエストをするわけだけど、使い回すわけでもないのでCFSTR()を使う。あとはさっき作ったURLインスタンスを渡す。HTTPのバージョンは1.1でいいのでこれも決めうちで。アロケータもデフォルト。

CFReadStreamRef theStream = CFReadStreamCreateForHTTPRequest(NULL, theRequest);
CFReadStreamOpen(theStream);

リクエストからストリームを作る。特に指定する物はないので簡単。ついでにストリームを開いちゃう。一緒にやってくれたらいいのに・・・ストリームを作って開かないってあるのかな。

ストリームから読み込む

	CFIndex n;
	UInt8 theBuffer[8192];
	CFMutableDataRef theCFData = CFDataCreateMutable( NULL, 0 );

	while(true)
	{
		n = CFReadStreamRead(theStream, theBuffer, sizeof(theBuffer));
		if( n == 0 )
			break;

		CFDataAppendBytes( theCFData, theBuffer, n );
	}

CFIndexはintらしい(調べてない)。UInt8はunsigned charで、バッファ用に適当な長さの配列を作っておく。最終的にCFStringにしたいので、CFDataに放り込む。可変長のものはCFMutableDataRef。一般にMutableが付いているのは中身を変更できる。

CFReadStreamRead()でストリームから読み込む。ネットワークはしばしばサイズ上限まで読まないことがあるのでnに読んだバイト数を記録する。Cの文字列のように’\0′終端じゃないから、長さを覚えておいてやる必要がある。読み込みサイズが0バイトなら終わり。そうでなければ、CFDataAppendBytes()で後ろにくっつける。

CFStringを作る

CFStringRef theContents = CFStringCreateFromExternalRepresentation( NULL, theCFData, kCFStringEncodingEUC_JP );
CFRelease(theCFData);

CFStringをCFDataから作る。その際にkCFStringEncodingEUC_JPを指定している。CFDataは単なるバイト列なのでエンコーディング情報がない(往々にしてアルファベット文化圏はその辺に疎いけど、CFStringの設計者は理解しているようだ)ので指定する。

CFDataはもう要らないので解放する。解放のタイミングは不要になったらすぐに解放するのか、しっぽでまとめて解放する方が見やすいのか。

//	CFShowStr(theContents);

CFShowStr()は文字列情報を出力するようなので不適。

//	CFIndex stringLength1 = CFStringGetMaximumSizeOfFileSystemRepresentation( theContents );
	CFIndex stringLength = CFStringGetMaximumSizeForEncoding( CFStringGetLength(theContents), kCFStringEncodingUTF8 );

	char *c = new char[stringLength];
	CFStringGetCString( theContents, c, stringLength, kCFStringEncodingUTF8 );
	puts(c);
	delete [] c;

仕方がないのでC文字列を作る。

CFStringは「文字数」で表すようだが、char配列はバイト数である。よく「2バイト文字」と言うが、1文字=1バイトあるいは2バイトと決め打ちするのはよくない。Unicodeはたしか1文字を21ビットで表すが、これはあまり使いやすくないので適当にエンコードして使う。UTF-8は基本8ビット(=1バイト)だが、収まらないときは2バイトとか3バイトになるようだ。日本語は一般に3バイトになるが、重箱の隅をつつくと1文字が15バイトになる(株式会社って文字とか)ものもあるそうだ。

そういうわけで、文字数とエンコーディングを指定してCFStringGetMaximumSizeForEncoding()すると必要なバイト数を返す。CFStringGetMaximumSizeOfFileSystemRepresentation()は大きすぎるようだ。

必要なバイト数を得てから、char配列を作りCFStringGetCString()で流し込む。puts()して終わったら忘れないうちに解放。

HTTPレスポンスヘッダを得る

	CFHTTPMessageRef responseHeader = (CFHTTPMessageRef)CFReadStreamCopyProperty(theStream, kCFStreamPropertyHTTPResponseHeader);

どういうわけか、HTTPの本文を読んでからじゃないとNULLが返るようなので、終わってから呼ぶ。CFReadStreamCopyProperty()は色々できるようだが、今回はkCFStreamPropertyHTTPResponseHeaderを指定する。

	CFDictionaryRef headers = CFHTTPMessageCopyAllHeaderFields(responseHeader);

CFDictionaryを得る。CFDictionaryは連想配列で、キーに対して値を問い合わせるコンテナ。C++のSTLだとstd::mapのようなもの。

	CFIndex count = CFDictionaryGetCount(headers);
	if( count > 0 )
	{
		(略)
	}
	CFRelease(headers);

連想配列にいくつキーと値のペアがあるか調べる。0より大きければ次のコードを実行する。終わったらheaderを解放する。

	CFStringRef *keys = new CFStringRef[count];
	CFStringRef *values = new CFStringRef[count];

	CFDictionaryGetKeysAndValues(headers, (const void **)keys, (const void **)values);

CFDictionaryGetKeysAndValues()でキーと値のペアを取得。CFStringRefの配列へのポインタを渡してやると、CFStringRefの配列が返される。

	for( int i = 0; i < count; i++ )
	{
		CFShow( keys[i] );
		CFShow( values[i] );
		puts("");
	}

	delete [] keys;
	delete [] values;

全てのペアを表示すればヘッダの内容がわかる。

BSD Socketがいいか、CFNetworkがいいか

正直なところ、こんなに面倒くさいことをするならBSD Socketを直接叩いた方がいいような気がする。URLのパース、DNSの問い合わせ、何かと古くさいSocketを直接叩くのは結構ダサいけど、面倒は少ない。必要ならBSD Socketをラップしちゃえばいいのだからね。

あとは、ヘッダもSocketから読むとゴチャゴチャ読むので、パースしてやる必要がある。これもそうたいした手間ではないし、CFなんとかを使っても面倒くさいから、どっちがいいかはよくわからない。

さらにPUTしてやろうとすると、SetBodyなんとかを使ったりするんだろう。Socketならwriteでがりがり書いてやるだけである。

Comments:0

Comment Form
Remember personal info

Trackback+Pingback:0

TrackBack URL for this entry
http://blog.neoneet.jp/2009/05/cfnetwork%e3%81%a7http-get/trackback/
Listed below are links to weblogs that reference
CFNetworkでHTTP GET from 週刊(月刊?)プレカリアート

Home > プログラミング > CFNetworkでHTTP GET

Search
Feeds
Meta

Page Top