naett.c

Tiny cross-platform HTTP / HTTPS client library in C.
git clone git://git.finwo.net/lib/naett.c
Log | Files | Refs | README | LICENSE

naett_osx.c (6914B)


      1 #include "naett_internal.h"
      2 
      3 #ifdef __APPLE__
      4 
      5 #include "naett_objc.h"
      6 #include <stdlib.h>
      7 #include <string.h>
      8 #include <stdio.h>
      9 
     10 #ifdef DEBUG
     11 static void _showPools(const char* context) {
     12     fprintf(stderr, "NSAutoreleasePool@%s:\n", context);
     13     objc_msgSend_void(class("NSAutoreleasePool"), sel("showPools"));
     14 }
     15 #define showPools(x) _showPools((x))
     16 #else
     17 #define showPools(x)
     18 #endif
     19 
     20 static id pool(void) {
     21     return objc_msgSend_id(objc_alloc("NSAutoreleasePool"), sel("init"));
     22 }
     23 
     24 static id sessionConfiguration = nil;
     25 
     26 void naettPlatformInit(naettInitData initData) {
     27     id NSThread = class("NSThread");
     28     SEL isMultiThreaded = sel("isMultiThreaded");
     29 
     30     if (!objc_msgSend_t(bool)(NSThread, isMultiThreaded)) {
     31         // Make a dummy call from a new thread to kick Cocoa into multi-threaded mode
     32         objc_msgSend_t(void, SEL, id, id)(
     33             NSThread, sel("detachNewThreadSelector:toTarget:withObject:"), isMultiThreaded, NSThread, nil);
     34     }
     35 
     36     sessionConfiguration = objc_msgSend_id(class("NSURLSessionConfiguration"), sel("ephemeralSessionConfiguration"));
     37     retain(sessionConfiguration);
     38 }
     39 
     40 id NSString(const char* string) {
     41     return objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), string);
     42 }
     43 
     44 int naettPlatformInitRequest(InternalRequest* req) {
     45     id p = pool();
     46 
     47     id urlString = NSString(req->url);
     48     id url = objc_msgSend_t(id, id)(class("NSURL"), sel("URLWithString:"), urlString);
     49 
     50     id request = objc_msgSend_t(id, id)(class("NSMutableURLRequest"), sel("requestWithURL:"), url);
     51 
     52     objc_msgSend_t(void, double)(request, sel("setTimeoutInterval:"), (double)(req->options.timeoutMS) / 1000.0);
     53     id methodString = NSString(req->options.method);
     54     objc_msgSend_t(void, id)(request, sel("setHTTPMethod:"), methodString);
     55 
     56     {
     57         id name = NSString("User-Agent");
     58         id value = NSString(req->options.userAgent ? req->options.userAgent : NAETT_UA);
     59         objc_msgSend_t(void, id, id)(request, sel("setValue:forHTTPHeaderField:"), value, name);
     60     }
     61 
     62     KVLink* header = req->options.headers;
     63     while (header != NULL) {
     64         id name = NSString(header->key);
     65         id value = NSString(header->value);
     66         objc_msgSend_t(void, id, id)(request, sel("setValue:forHTTPHeaderField:"), value, name);
     67         header = header->next;
     68     }
     69 
     70     char byteBuffer[10240];
     71     int bytesRead = 0;
     72 
     73     if (req->options.bodyReader != NULL) {
     74         id bodyData =
     75             objc_msgSend_t(id, NSUInteger)(class("NSMutableData"), sel("dataWithCapacity:"), sizeof(byteBuffer));
     76 
     77         int totalBytesRead = 0;
     78         do {
     79             bytesRead = req->options.bodyReader(byteBuffer, sizeof(byteBuffer), req->options.bodyReaderData);
     80             totalBytesRead += bytesRead;
     81             objc_msgSend_t(void, const void*, NSUInteger)(bodyData, sel("appendBytes:length:"), byteBuffer, bytesRead);
     82         } while (bytesRead > 0);
     83 
     84         if (totalBytesRead > 0) {
     85             objc_msgSend_t(void, id)(request, sel("setHTTPBody:"), bodyData);
     86         }
     87     }
     88 
     89     retain(request);
     90     req->urlRequest = request;
     91 
     92     release(p);
     93     return 1;
     94 }
     95 
     96 void didReceiveData(id self, SEL _sel, id session, id dataTask, id data) {
     97     InternalResponse* res = NULL;
     98     id p = pool();
     99 
    100     object_getInstanceVariable(self, "response", (void**)&res);
    101 
    102     if (res->headers == NULL) {
    103         id response = objc_msgSend_t(id)(dataTask, sel("response"));
    104         res->code = objc_msgSend_t(NSInteger)(response, sel("statusCode"));
    105         id allHeaders = objc_msgSend_t(id)(response, sel("allHeaderFields"));
    106 
    107         NSUInteger headerCount = objc_msgSend_t(NSUInteger)(allHeaders, sel("count"));
    108         id headerNames[headerCount];
    109         id headerValues[headerCount];
    110 
    111         objc_msgSend_t(NSInteger, id*, id*, NSUInteger)(
    112             allHeaders, sel("getObjects:andKeys:count:"), headerValues, headerNames, headerCount);
    113         KVLink* firstHeader = NULL;
    114         for (int i = 0; i < headerCount; i++) {
    115             naettAlloc(KVLink, node);
    116             node->key = strdup(objc_msgSend_t(const char*)(headerNames[i], sel("UTF8String")));
    117             node->value = strdup(objc_msgSend_t(const char*)(headerValues[i], sel("UTF8String")));
    118             node->next = firstHeader;
    119             firstHeader = node;
    120         }
    121         res->headers = firstHeader;
    122 
    123         const char* contentLength = naettGetHeader((naettRes*)res, "Content-Length");
    124         if (!contentLength || sscanf(contentLength, "%d", &res->contentLength) != 1) {
    125             res->contentLength = -1;
    126         }
    127     }
    128 
    129     const void* bytes = objc_msgSend_t(const void*)(data, sel("bytes"));
    130     NSUInteger length = objc_msgSend_t(NSUInteger)(data, sel("length"));
    131 
    132     res->request->options.bodyWriter(bytes, length, res->request->options.bodyWriterData);
    133     res->totalBytesRead += (int)length;
    134 
    135     release(p);
    136 }
    137 
    138 static void didComplete(id self, SEL _sel, id session, id dataTask, id error) {
    139     InternalResponse* res = NULL;
    140     object_getInstanceVariable(self, "response", (void**)&res);
    141     if (res != NULL) {
    142         if (error != nil) {
    143             res->code = naettConnectionError;
    144         }
    145         res->complete = 1;
    146     }
    147 }
    148 
    149 static id createDelegate(void) {
    150     static Class TaskDelegateClass = nil;
    151 
    152     if (!TaskDelegateClass) {
    153         TaskDelegateClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "naettTaskDelegate", 0);
    154         class_addProtocol(TaskDelegateClass, (Protocol* _Nonnull)objc_getProtocol("NSURLSessionDataDelegate"));
    155 
    156         addMethod(TaskDelegateClass, "URLSession:dataTask:didReceiveData:", didReceiveData, "v@:@@@");
    157         addMethod(TaskDelegateClass, "URLSession:task:didCompleteWithError:", didComplete, "v@:@@@");
    158         addIvar(TaskDelegateClass, "response", sizeof(void*), "^v");
    159     }
    160 
    161     id delegate = objc_msgSend_id((id)TaskDelegateClass, sel("alloc"));
    162     delegate = objc_msgSend_id(delegate, sel("init"));
    163 
    164     return delegate;
    165 }
    166 
    167 void naettPlatformMakeRequest(InternalResponse* res) {
    168     InternalRequest* req = res->request;
    169     id p = pool();
    170 
    171     id delegate = createDelegate();
    172     // delegate will be retained by session below
    173     autorelease(delegate);
    174     object_setInstanceVariable(delegate, "response", (void*)res);
    175 
    176     id session = objc_msgSend_t(id, id, id, id)(class("NSURLSession"),
    177         sel("sessionWithConfiguration:delegate:delegateQueue:"),
    178         sessionConfiguration,
    179         delegate,
    180         nil);
    181 
    182     res->session = session;
    183 
    184     id task = objc_msgSend_t(id, id)(session, sel("dataTaskWithRequest:"), req->urlRequest);
    185     objc_msgSend_void(task, sel("resume"));
    186 
    187     release(p);
    188 }
    189 
    190 void naettPlatformFreeRequest(InternalRequest* req) {
    191     release(req->urlRequest);
    192     req->urlRequest = nil;
    193 }
    194 
    195 void naettPlatformCloseResponse(InternalResponse* res) {
    196     objc_msgSend_void(res->session, sel("invalidateAndCancel"));
    197     res->session = nil;
    198 }
    199 
    200 #endif  // __APPLE__