objective-c で 繋ぎっぱなしな http 的なやつで通信したりする場合

airshow-ios では appletv と websocket の用に tcp を繋ぎっぱなしにしつつ、http の用な感じでやりとりをするので、 robbiehanson/CocoaAsyncSocket · GitHub で socket の面倒を見つつ、 CFHTTPMessage* な関数で http のパースなどをするようにした。

CocoaAsyncSocket だと

[GCDAsyncSocket readDataToData: withTimeout: tag:]

という関数があり、これを使って例えば、 http の header 全部読むこんで欲しい場合は

NSData *toData = [@"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
[socket readDataToData:toData withTimeout:-1 tag:TAG_HEADER];

とした。 tag をつけておくと読み込んだ後に呼ばれるメソッドでどの読み込みの為に呼ばれたのかが分かるから、header と body の読み込んだ後の判別等に使った。

相手に書き込みレスポンスを受け取る場合、上のメソッドを呼んだ後に

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    if (tag == TAG_HEADER) {
        CFHTTPMessageRef messageRef = CFHTTPMessageCreateEmpty(NULL, NO);
        CFHTTPMessageAppendBytes(messageRef, (const UInt8 *)data.bytes, data.length);

        if (!CFHTTPMessageIsHeaderComplete(messageRef)) {
                // failed
        }

        NSDictionary *header = (NSDictionary *)CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(messageRef));
        NSString *contentLength = header[@"Content-Length"];
        //....

        CFRelease(messageRef)
    }
}

の用な感じで CFHTTPMessageCreateEmpty でもとを作って、 CFHTTPMessageAppendBytes でデータを追加していく。CFHTTPMessageCopyAllHeaderFields を使う事で CFHTTPMessageRef から CFDIcitionaryRef のかたちで header の内容を受け取ることが出来る。

ちなみにこれは、response を待っている場合なので

CFHTTPMessageCreateEmpty(NULL, NO);

としてるが、 request を待つ場合は

CFHTTPMessageCreateEmpty(NULL, YES);

としないと、CFHTTPMessageCopyRequestURL の用な関数が正しく値を返さないので気をつける必用がある。何も考えずに全部 NO にしていて、嵌った。

ちなみに body も読む場合は上の後に

[sock readDataToLength:contentLength.integerValue withTimeout:-1 tag:TAG_BODY];

の用なかたちで読み込む。

まとめ

最初は c の http-parser を使わないといけないと思ったが core fundation に関数があったので助かった。