commit fb7cb8e69ad82109925fa7479583ffc427f89c82
parent 185c071d51ccb4635a9348e29adc6be4a7511da0
Author: Erik Agsjö <erik.agsjo@gmail.com>
Date: Mon, 15 Nov 2021 22:52:37 +0100
iOS, consistency fixes
Diffstat:
8 files changed, 259 insertions(+), 14 deletions(-)
diff --git a/example/Makefile b/example/Makefile
@@ -8,7 +8,7 @@ else
ifeq ($(UNAME_S),Darwin)
LDFLAGS = -framework Cocoa
else ifeq ($(UNAME_S),Linux)
- #LDFLAGS = -s -lGLU -lGL -lX11
+ LDFLAGS = -lcurl
endif
endif
diff --git a/naett.c b/naett.c
@@ -13,6 +13,7 @@
#if __linux__ && !__ANDROID__
#define __LINUX__ 1
+#include <curl/curl.h>
#endif
#if __ANDROID__
@@ -80,6 +81,9 @@ typedef struct {
pthread_t workerThread;
int closeRequested;
#endif
+#if __LINUX__
+ struct curl_slist* headerList;
+#endif
} InternalResponse;
void naettPlatformInit(naettInitData initData);
@@ -95,6 +99,7 @@ void naettPlatformCloseResponse(InternalResponse* res);
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
+#include <assert.h>
typedef struct InternalParam* InternalParamPtr;
typedef void (*ParamSetter)(InternalParamPtr param, InternalRequest* req);
@@ -185,7 +190,10 @@ static int defaultBodyWriter(const void* source, int bytes, void* userData) {
return bytes;
}
+static int initialized = 0;
+
static void initRequest(InternalRequest* req, const char* url) {
+ assert(initialized);
req->options.method = strdup("GET");
req->options.timeoutMS = 5000;
req->url = strdup(url);
@@ -201,7 +209,9 @@ static void applyOptionParams(InternalRequest* req, InternalOption* option) {
// Public API
void naettInit(naettInitData initData) {
+ assert(!initialized);
naettPlatformInit(initData);
+ initialized = 1;
}
naettOption* naettMethod(const char* method) {
@@ -336,6 +346,7 @@ naettReq* naettRequestWithOptions(const char* url, int numOptions, const naettOp
}
naettRes* naettMake(naettReq* request) {
+ assert(initialized);
InternalRequest* req = (InternalRequest*)request;
naettAlloc(InternalResponse, res);
res->request = req;
@@ -423,7 +434,7 @@ void naettClose(naettRes* response) {
// Inlined naett_osx.c: //
//#include "naett_internal.h"
-#ifdef __MACOS__
+#ifdef __APPLE__
// Inlined naett_objc.h: //
#ifndef NAETT_OBJC_H
@@ -522,7 +533,8 @@ int naettPlatformInitRequest(InternalRequest* req) {
int bytesRead = 0;
if (req->options.bodyReader != NULL) {
- id bodyData = objc_msgSend_t(id, NSUInteger)(class("NSMutableData"), sel("dataWithCapacity:"), sizeof(byteBuffer));
+ id bodyData =
+ objc_msgSend_t(id, NSUInteger)(class("NSMutableData"), sel("dataWithCapacity:"), sizeof(byteBuffer));
do {
bytesRead = req->options.bodyReader(byteBuffer, sizeof(byteBuffer), req->options.bodyReaderData);
@@ -615,9 +627,230 @@ void naettPlatformCloseResponse(InternalResponse* res) {
release(res->session);
}
-#endif // __MACOS__
+#endif // __APPLE__
// End of inlined naett_osx.c //
+// Inlined naett_linux.c: //
+//#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, "%sn", 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
+// End of inlined naett_linux.c //
+
// Inlined naett_win.c: //
//#include "naett_internal.h"
@@ -626,6 +859,9 @@ void naettPlatformCloseResponse(InternalResponse* res) {
#include <stdlib.h>
#include <string.h>
+void naettPlatformInit(naettInitData initData) {
+}
+
int naettPlatformInitRequest(InternalRequest* req) {
}
@@ -776,7 +1012,6 @@ static void* processRequest(void* data) {
jobject methodString = (*env)->NewStringUTF(env, req->options.method);
voidCall(env, connection, "setRequestMethod", "(Ljava/lang/String;)V", methodString);
voidCall(env, connection, "setConnectTimeout", "(I)V", req->options.timeoutMS);
- voidCall(env, connection, "setReadTimeout", "(I)V", req->options.timeoutMS);
voidCall(env, connection, "setInstanceFollowRedirects", "(Z)V", 1);
voidCall(env, connection, "connect", "()V");
diff --git a/naett.h b/naett.h
@@ -7,9 +7,6 @@ extern "C" {
#if __ANDROID__
#include <jni.h>
-#endif
-
-#if __ANDROID__
typedef JavaVM* naettInitData;
#else
typedef void* naettInitData;
@@ -23,10 +20,12 @@ void naettInit(naettInitData initThing);
typedef struct naettReq naettReq;
typedef struct naettRes naettRes;
-typedef struct naettOption naettOption;
typedef int (*naettReadFunc)(void* dest, int bufferSize, void* userData);
typedef int (*naettWriteFunc)(const void* source, int bytes, void* userData);
+// Option to `naettRequest`
+typedef struct naettOption naettOption;
+
// Sets request method. Defaults to "GET".
naettOption* naettMethod(const char* method);
// Adds a request header.
@@ -37,7 +36,7 @@ naettOption* naettBody(const char* body, int size);
naettOption* naettBodyReader(naettReadFunc reader, void* userData);
// Sets a response body writer.
naettOption* naettBodyWriter(naettWriteFunc writer, void* userData);
-// Sets timeout, used both for connection and read.
+// Sets connection timeout in milliseconds.
naettOption* naettTimeout(int milliSeconds);
/**
diff --git a/src/amalgam.h b/src/amalgam.h
@@ -1,5 +1,6 @@
#include "naett.h"
#include "naett_core.c"
#include "naett_osx.c"
+#include "naett_linux.c"
#include "naett_win.c"
#include "naett_android.c"
diff --git a/src/naett_android.c b/src/naett_android.c
@@ -132,7 +132,6 @@ static void* processRequest(void* data) {
jobject methodString = (*env)->NewStringUTF(env, req->options.method);
voidCall(env, connection, "setRequestMethod", "(Ljava/lang/String;)V", methodString);
voidCall(env, connection, "setConnectTimeout", "(I)V", req->options.timeoutMS);
- voidCall(env, connection, "setReadTimeout", "(I)V", req->options.timeoutMS);
voidCall(env, connection, "setInstanceFollowRedirects", "(Z)V", 1);
voidCall(env, connection, "connect", "()V");
diff --git a/src/naett_core.c b/src/naett_core.c
@@ -3,6 +3,7 @@
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
+#include <assert.h>
typedef struct InternalParam* InternalParamPtr;
typedef void (*ParamSetter)(InternalParamPtr param, InternalRequest* req);
@@ -93,7 +94,10 @@ static int defaultBodyWriter(const void* source, int bytes, void* userData) {
return bytes;
}
+static int initialized = 0;
+
static void initRequest(InternalRequest* req, const char* url) {
+ assert(initialized);
req->options.method = strdup("GET");
req->options.timeoutMS = 5000;
req->url = strdup(url);
@@ -109,7 +113,9 @@ static void applyOptionParams(InternalRequest* req, InternalOption* option) {
// Public API
void naettInit(naettInitData initData) {
+ assert(!initialized);
naettPlatformInit(initData);
+ initialized = 1;
}
naettOption* naettMethod(const char* method) {
@@ -244,6 +250,7 @@ naettReq* naettRequestWithOptions(const char* url, int numOptions, const naettOp
}
naettRes* naettMake(naettReq* request) {
+ assert(initialized);
InternalRequest* req = (InternalRequest*)request;
naettAlloc(InternalResponse, res);
res->request = req;
diff --git a/src/naett_osx.c b/src/naett_osx.c
@@ -1,6 +1,6 @@
#include "naett_internal.h"
-#ifdef __MACOS__
+#ifdef __APPLE__
#include "naett_objc.h"
#include <stdlib.h>
@@ -37,7 +37,8 @@ int naettPlatformInitRequest(InternalRequest* req) {
int bytesRead = 0;
if (req->options.bodyReader != NULL) {
- id bodyData = objc_msgSend_t(id, NSUInteger)(class("NSMutableData"), sel("dataWithCapacity:"), sizeof(byteBuffer));
+ id bodyData =
+ objc_msgSend_t(id, NSUInteger)(class("NSMutableData"), sel("dataWithCapacity:"), sizeof(byteBuffer));
do {
bytesRead = req->options.bodyReader(byteBuffer, sizeof(byteBuffer), req->options.bodyReaderData);
@@ -130,4 +131,4 @@ void naettPlatformCloseResponse(InternalResponse* res) {
release(res->session);
}
-#endif // __MACOS__
+#endif // __APPLE__
diff --git a/src/naett_win.c b/src/naett_win.c
@@ -5,6 +5,9 @@
#include <stdlib.h>
#include <string.h>
+void naettPlatformInit(naettInitData initData) {
+}
+
int naettPlatformInitRequest(InternalRequest* req) {
}