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

commit 185c071d51ccb4635a9348e29adc6be4a7511da0
parent 622b4d340820a604757bf66d6dd4c0a4d38788f2
Author: Erik Agsjö <erik.agsjo@gmail.com>
Date:   Mon, 15 Nov 2021 22:50:34 +0100

CURL - based linux impl

Diffstat:
Msrc/naett_internal.h | 4++++
Asrc/naett_linux.c | 218+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 222 insertions(+), 0 deletions(-)

diff --git a/src/naett_internal.h b/src/naett_internal.h @@ -9,6 +9,7 @@ #if __linux__ && !__ANDROID__ #define __LINUX__ 1 +#include <curl/curl.h> #endif #if __ANDROID__ @@ -76,6 +77,9 @@ typedef struct { pthread_t workerThread; int closeRequested; #endif +#if __LINUX__ + struct curl_slist* headerList; +#endif } InternalResponse; void naettPlatformInit(naettInitData initData); diff --git a/src/naett_linux.c b/src/naett_linux.c @@ -0,0 +1,218 @@ +#include "naett_internal.h" + +#if __linux__ && !__ANDROID__ + +#include <curl/curl.h> +#include <assert.h> +#include <pthread.h> +#include <unistd.h> +#include <fcntl.h> +#include <string.h> + +static pthread_t workerThread; +static int handleReadFD = 0; +static int handleWriteFD = 0; + +static void panic(const char* message) { + fprintf(stderr, "%s\n", message); + exit(1); +} + +static void* curlWorker(void* data) { + CURLM* mc = (CURLM*)data; + int activeHandles = 0; + int messagesLeft = 1; + + struct curl_waitfd readFd = { handleReadFD, CURL_WAIT_POLLIN }; + + union { + CURL* handle; + char buf[sizeof(CURL*)]; + } newHandle; + + int newHandlePos = 0; + + while (1) { + int status = curl_multi_perform(mc, &activeHandles); + if (status != CURLM_OK) { + panic("CURL processing failure"); + } + + int readyFDs = 0; + curl_multi_wait(mc, &readFd, 1, 1000, &readyFDs); + + if (readyFDs == 0) { + usleep(100 * 1000); + } + + int bytesRead = read(handleReadFD, newHandle.buf, sizeof(newHandle.buf) - newHandlePos); + if (bytesRead > 0) { + newHandlePos += bytesRead; + } + if (newHandlePos == sizeof(newHandle.buf)) { + curl_multi_add_handle(mc, newHandle.handle); + newHandlePos = 0; + } + + struct CURLMsg* message = curl_multi_info_read(mc, &messagesLeft); + if (message && message->msg == CURLMSG_DONE) { + CURL* handle = message->easy_handle; + InternalResponse* res = NULL; + curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char**)&res); + curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &res->code); + res->complete = 1; + curl_easy_cleanup(handle); + } + } +} + +void naettPlatformInit(naettInitData initData) { + curl_global_init(CURL_GLOBAL_ALL); + CURLM* mc = curl_multi_init(); + int fds[2]; + if (pipe(fds) != 0) { + panic("Failed to open pipe"); + } + handleReadFD = fds[0]; + handleWriteFD = fds[1]; + + int flags = fcntl(handleReadFD, F_GETFL, 0); + fcntl(handleReadFD, F_SETFL, flags | O_NONBLOCK); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&workerThread, &attr, curlWorker, mc); +} + +int naettPlatformInitRequest(InternalRequest* req) { + return 1; +} + +static size_t readCallback(char* buffer, size_t size, size_t numItems, void* userData) { + InternalResponse* res = (InternalResponse*)userData; + InternalRequest* req = res->request; + return req->options.bodyReader(buffer, size, req->options.bodyReaderData); +} + +static size_t writeCallback(char* ptr, size_t size, size_t numItems, void* userData) { + InternalResponse* res = (InternalResponse*)userData; + InternalRequest* req = res->request; + return req->options.bodyWriter(ptr, size * numItems, req->options.bodyWriterData); +} + +#define METHOD(A, B, C) (((A) << 16) | ((B) << 8) | (C)) + +static void setupMethod(CURL* curl, const char* method) { + if (strlen(method) < 3) { + return; + } + + int methodCode = (method[0] << 16) | (method[1] << 8) | method[2]; + + switch (methodCode) { + case METHOD('G', 'E', 'T'): + case METHOD('C', 'O', 'N'): + case METHOD('O', 'P', 'T'): + curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); + break; + case METHOD('P', 'O', 'S'): + case METHOD('P', 'A', 'T'): + case METHOD('D', 'E', 'L'): + curl_easy_setopt(curl, CURLOPT_POST, 1); + break; + case METHOD('P', 'U', 'T'): + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1); + break; + case METHOD('H', 'E', 'A'): + case METHOD('T', 'R', 'A'): + curl_easy_setopt(curl, CURLOPT_NOBODY, 1); + break; + } + + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method); +} + +static size_t headerCallback(char* buffer, size_t size, size_t nitems, void* userData) { + InternalResponse* res = (InternalResponse*) userData; + size_t headerSize = size * nitems; + + char* headerName = strndup(buffer, headerSize); + char* split = strchr(headerName, ':'); + if (split) { + *split = 0; + split++; + while (*split == ' ') { + split++; + } + char* headerValue = strdup(split); + + char* cr = strchr(headerValue, 13); + if (cr) { + *cr = 0; + } + + char* lf = strchr(headerValue, 10); + if (lf) { + *lf = 0; + } + + naettAlloc(KVLink, node); + node->next = res->headers; + node->key = headerName; + node->value = headerValue; + res->headers = node; + } + + return headerSize; +} + +void naettPlatformMakeRequest(InternalResponse* res) { + InternalRequest* req = res->request; + + CURL* c = curl_easy_init(); + curl_easy_setopt(c, CURLOPT_URL, req->url); + curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT_MS, req->options.timeoutMS); + + curl_easy_setopt(c, CURLOPT_READFUNCTION, readCallback); + curl_easy_setopt(c, CURLOPT_READDATA, res); + + curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, writeCallback); + curl_easy_setopt(c, CURLOPT_WRITEDATA, res); + + curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, headerCallback); + curl_easy_setopt(c, CURLOPT_HEADERDATA, res); + + setupMethod(c, req->options.method); + + struct curl_slist* headerList = NULL; + KVLink* header = req->options.headers; + size_t bufferSize = 0; + char* buffer = NULL; + while (header) { + size_t headerLength = strlen(header->key) + strlen(header->value) + 1 + 1; // colon + null + if (headerLength > bufferSize) { + bufferSize = headerLength; + buffer = (char*)realloc(buffer, bufferSize); + } + snprintf(buffer, bufferSize, "%s:%s", header->key, header->value); + headerList = curl_slist_append(headerList, buffer); + header = header->next; + } + curl_easy_setopt(c, CURLOPT_HTTPHEADER, headerList); + free(buffer); + res->headerList = headerList; + + curl_easy_setopt(c, CURLOPT_PRIVATE, res); + + write(handleWriteFD, &c, sizeof(c)); +} + +void naettPlatformFreeRequest(InternalRequest* req) { +} + +void naettPlatformCloseResponse(InternalResponse* res) { + curl_slist_free_all(res->headerList); +} + +#endif