- 2009-05-09 (土) 20:42
- プログラミング
似たような記事はあちこちにあるけど、とりあえずまとめてみる。
とりあえずコード。単なるテストなのでごちゃごちゃ。
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でがりがり書いてやるだけである。
- Newer: CFNetworkでHTTP POST
- Older: CocoaでHTMLを扱う
Comments:0
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 週刊(月刊?)プレカリアート