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__