naett_linux.c (6862B)
1 #include "naett_internal.h" 2 3 #if __linux__ && !__ANDROID__ 4 5 #include <curl/curl.h> 6 #include <assert.h> 7 #include <pthread.h> 8 #include <unistd.h> 9 #include <fcntl.h> 10 #include <string.h> 11 12 static pthread_t workerThread; 13 static int handleReadFD = 0; 14 static int handleWriteFD = 0; 15 16 static void panic(const char* message) { 17 fprintf(stderr, "%s\n", message); 18 exit(1); 19 } 20 21 static void* curlWorker(void* data) { 22 CURLM* mc = (CURLM*)data; 23 int activeHandles = 0; 24 int messagesLeft = 1; 25 26 struct curl_waitfd readFd = { handleReadFD, CURL_WAIT_POLLIN }; 27 28 union { 29 CURL* handle; 30 char buf[sizeof(CURL*)]; 31 } newHandle; 32 33 int newHandlePos = 0; 34 35 while (1) { 36 int status = curl_multi_perform(mc, &activeHandles); 37 if (status != CURLM_OK) { 38 panic("CURL processing failure"); 39 } 40 41 struct CURLMsg* message = curl_multi_info_read(mc, &messagesLeft); 42 if (message && message->msg == CURLMSG_DONE) { 43 CURL* handle = message->easy_handle; 44 InternalResponse* res = NULL; 45 curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char**)&res); 46 curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &res->code); 47 res->complete = 1; 48 curl_easy_cleanup(handle); 49 } 50 51 52 int readyFDs = 0; 53 curl_multi_wait(mc, &readFd, 1, 1, &readyFDs); 54 55 if (readyFDs == 0 && activeHandles == 0 && messagesLeft == 0) { 56 usleep(100 * 1000); 57 } 58 59 int bytesRead = read(handleReadFD, newHandle.buf, sizeof(newHandle.buf) - newHandlePos); 60 if (bytesRead > 0) { 61 newHandlePos += bytesRead; 62 } 63 if (newHandlePos == sizeof(newHandle.buf)) { 64 curl_multi_add_handle(mc, newHandle.handle); 65 newHandlePos = 0; 66 } 67 } 68 69 return NULL; 70 } 71 72 void naettPlatformInit(naettInitData initData) { 73 curl_global_init(CURL_GLOBAL_ALL); 74 CURLM* mc = curl_multi_init(); 75 int fds[2]; 76 if (pipe(fds) != 0) { 77 panic("Failed to open pipe"); 78 } 79 handleReadFD = fds[0]; 80 handleWriteFD = fds[1]; 81 82 int flags = fcntl(handleReadFD, F_GETFL, 0); 83 fcntl(handleReadFD, F_SETFL, flags | O_NONBLOCK); 84 85 pthread_attr_t attr; 86 pthread_attr_init(&attr); 87 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 88 pthread_create(&workerThread, &attr, curlWorker, mc); 89 } 90 91 int naettPlatformInitRequest(InternalRequest* req) { 92 return 1; 93 } 94 95 static size_t readCallback(char* buffer, size_t size, size_t numItems, void* userData) { 96 InternalResponse* res = (InternalResponse*)userData; 97 InternalRequest* req = res->request; 98 return req->options.bodyReader(buffer, size * numItems, req->options.bodyReaderData); 99 } 100 101 static size_t writeCallback(char* ptr, size_t size, size_t numItems, void* userData) { 102 InternalResponse* res = (InternalResponse*)userData; 103 InternalRequest* req = res->request; 104 size_t bytesWritten = req->options.bodyWriter(ptr, size * numItems, req->options.bodyWriterData); 105 res->totalBytesRead += bytesWritten; 106 return bytesWritten; 107 } 108 109 #define METHOD(A, B, C) (((A) << 16) | ((B) << 8) | (C)) 110 111 static void setupMethod(CURL* curl, const char* method) { 112 if (strlen(method) < 3) { 113 return; 114 } 115 116 int methodCode = (method[0] << 16) | (method[1] << 8) | method[2]; 117 118 switch (methodCode) { 119 case METHOD('G', 'E', 'T'): 120 case METHOD('C', 'O', 'N'): 121 case METHOD('O', 'P', 'T'): 122 curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); 123 break; 124 case METHOD('P', 'O', 'S'): 125 case METHOD('P', 'A', 'T'): 126 case METHOD('D', 'E', 'L'): 127 curl_easy_setopt(curl, CURLOPT_POST, 1); 128 break; 129 case METHOD('P', 'U', 'T'): 130 curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); 131 break; 132 case METHOD('H', 'E', 'A'): 133 case METHOD('T', 'R', 'A'): 134 curl_easy_setopt(curl, CURLOPT_NOBODY, 1); 135 break; 136 } 137 138 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method); 139 } 140 141 static size_t headerCallback(char* buffer, size_t size, size_t nitems, void* userData) { 142 InternalResponse* res = (InternalResponse*) userData; 143 size_t headerSize = size * nitems; 144 145 char* headerName = strndup(buffer, headerSize); 146 char* split = strchr(headerName, ':'); 147 if (split) { 148 *split = 0; 149 split++; 150 while (*split == ' ') { 151 split++; 152 } 153 char* headerValue = strdup(split); 154 155 char* cr = strchr(headerValue, 13); 156 if (cr) { 157 *cr = 0; 158 } 159 160 char* lf = strchr(headerValue, 10); 161 if (lf) { 162 *lf = 0; 163 } 164 165 naettAlloc(KVLink, node); 166 node->next = res->headers; 167 node->key = headerName; 168 node->value = headerValue; 169 res->headers = node; 170 } 171 172 return headerSize; 173 } 174 175 void naettPlatformMakeRequest(InternalResponse* res) { 176 InternalRequest* req = res->request; 177 178 CURL* c = curl_easy_init(); 179 curl_easy_setopt(c, CURLOPT_URL, req->url); 180 curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT_MS, req->options.timeoutMS); 181 182 curl_easy_setopt(c, CURLOPT_READFUNCTION, readCallback); 183 curl_easy_setopt(c, CURLOPT_READDATA, res); 184 185 curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, writeCallback); 186 curl_easy_setopt(c, CURLOPT_WRITEDATA, res); 187 188 curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, headerCallback); 189 curl_easy_setopt(c, CURLOPT_HEADERDATA, res); 190 191 curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1); 192 193 int bodySize = res->request->options.bodyReader(NULL, 0, res->request->options.bodyReaderData); 194 curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, bodySize); 195 196 setupMethod(c, req->options.method); 197 198 struct curl_slist* headerList = NULL; 199 char uaBuf[512]; 200 snprintf(uaBuf, sizeof(uaBuf), "User-Agent: %s", req->options.userAgent ? req->options.userAgent : NAETT_UA); 201 headerList = curl_slist_append(headerList, uaBuf); 202 203 KVLink* header = req->options.headers; 204 size_t bufferSize = 0; 205 char* buffer = NULL; 206 while (header) { 207 size_t headerLength = strlen(header->key) + strlen(header->value) + 1 + 1; // colon + null 208 if (headerLength > bufferSize) { 209 bufferSize = headerLength; 210 buffer = (char*)realloc(buffer, bufferSize); 211 } 212 snprintf(buffer, bufferSize, "%s:%s", header->key, header->value); 213 headerList = curl_slist_append(headerList, buffer); 214 header = header->next; 215 } 216 curl_easy_setopt(c, CURLOPT_HTTPHEADER, headerList); 217 free(buffer); 218 res->headerList = headerList; 219 220 curl_easy_setopt(c, CURLOPT_PRIVATE, res); 221 222 write(handleWriteFD, &c, sizeof(c)); 223 } 224 225 void naettPlatformFreeRequest(InternalRequest* req) { 226 } 227 228 void naettPlatformCloseResponse(InternalResponse* res) { 229 curl_slist_free_all(res->headerList); 230 } 231 232 #endif