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_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