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 b298892985d59c713a4593847c367d902232304f
parent 4a29a444306dedb59eab2b0510a6bc2e66520c7b
Author: Erik Agsjö <erik.agsjo@gmail.com>
Date:   Sun, 14 Nov 2021 16:20:40 +0100

Android implementation

Diffstat:
Mexample/main.c | 4+++-
Mnaett.c | 342++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mnaett.h | 17+++++++++++++++++
Msrc/Makefile | 3++-
Msrc/amalgam.h | 2++
Asrc/naett_android.c | 265+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/naett_core.c | 31+++++++++++++++++++++++++------
Msrc/naett_internal.h | 18+++++++++++++++---
Msrc/naett_osx.c | 3++-
Asrc/naett_win.c | 20++++++++++++++++++++
10 files changed, 683 insertions(+), 22 deletions(-)

diff --git a/example/main.c b/example/main.c @@ -3,8 +3,10 @@ #include <stdio.h> int main(int argc, char** argv) { + naettInit(NULL); + naettReq* req = - naettRequest("http://www.dn.se", naettMethod("GET"), naettHeader("content-type", "application/json")); + naettRequest("https://www.dn.se", naettMethod("GET"), naettHeader("accept", "application/json")); naettRes* res = naettMake(req); while (!naettComplete(res)) { usleep(100 * 1000); diff --git a/naett.c b/naett.c @@ -15,6 +15,11 @@ #define __LINUX__ 1 #endif +#if __ANDROID__ +#include <jni.h> +#include <pthread.h> +#endif + #ifdef __APPLE__ #include "TargetConditionals.h" #include <objc/objc.h> @@ -56,21 +61,28 @@ typedef struct { #if __APPLE__ id urlRequest; #endif +#if __ANDROID__ + jobject urlObject; +#endif } InternalRequest; typedef struct { InternalRequest* request; - int complete; int code; KVLink* headers; Buffer body; #if __APPLE__ id session; #endif +#if __ANDROID__ + pthread_t workerThread; + int closeRequested; +#endif } InternalResponse; -void naettPlatformInitRequest(InternalRequest* req); -void naettPlatformMakeRequest(InternalRequest* req, InternalResponse* res); +void naettPlatformInit(void* initThing); +int naettPlatformInitRequest(InternalRequest* req); +void naettPlatformMakeRequest(InternalResponse* res); void naettPlatformFreeRequest(InternalRequest* req); void naettPlatformCloseResponse(InternalResponse* res); @@ -160,6 +172,7 @@ static int defaultBodyWriter(const void* source, int bytes, void* userData) { static void initRequest(InternalRequest* req, const char* url) { req->options.method = strdup("GET"); + req->options.timeoutMS = 5000; req->url = strdup(url); } @@ -172,6 +185,10 @@ static void applyOptionParams(InternalRequest* req, InternalOption* option) { // Public API +void naettInit(void* initThing) { + naettPlatformInit(initThing); +} + naettOption* naettMethod(const char* method) { naettAlloc(InternalOption, option); option->numParams = 1; @@ -262,6 +279,7 @@ naettOption* naettBodyWriter(naettWriteFunc writer, void* userData) { return (naettOption*)option; } + naettReq* naettRequest_va(const char* url, int numArgs, ...) { va_list args; InternalOption* option; @@ -276,8 +294,12 @@ naettReq* naettRequest_va(const char* url, int numArgs, ...) { } va_end(args); - naettPlatformInitRequest(req); - return (naettReq*)req; + if (naettPlatformInitRequest(req)) { + return (naettReq*)req; + } + + naettFree(req); + return NULL; } naettReq* naettRequestWithOptions(const char* url, int numOptions, const naettOption** options) { @@ -290,8 +312,12 @@ naettReq* naettRequestWithOptions(const char* url, int numOptions, const naettOp free(option); } - naettPlatformInitRequest(req); - return (naettReq*)req; + if (naettPlatformInitRequest(req)) { + return (naettReq*)req; + } + + naettFree(req); + return NULL; } naettRes* naettMake(naettReq* request) { @@ -302,7 +328,7 @@ naettRes* naettMake(naettReq* request) { req->options.bodyWriter = defaultBodyWriter; req->options.bodyWriterData = (void*) &res->body; } - naettPlatformMakeRequest(req, res); + naettPlatformMakeRequest(res); return (naettRes*) res; } @@ -326,7 +352,12 @@ const char* naettGetHeader(naettRes* response, const char* name) { int naettComplete(const naettRes* response) { InternalResponse* res = (InternalResponse*)response; - return res->complete; + return res->code != 0; +} + +int naettGetStatus(const naettRes* response) { + InternalResponse* res = (InternalResponse*)response; + return res->code; } void naettFree(naettReq* request) { @@ -414,7 +445,7 @@ void naettClose(naettRes* response) { #include <stdlib.h> #include <string.h> -void naettPlatformInitRequest(InternalRequest* req) { +int naettPlatformInitRequest(InternalRequest* req) { id urlString = objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), req->url); id url = objc_msgSend_t(id, id)(class("NSURL"), sel("URLWithString:"), urlString); release(urlString); @@ -528,4 +559,295 @@ void naettPlatformCloseResponse(InternalResponse* res) { #endif // __MACOS__ // End of inlined naett_osx.c // +// Inlined naett_win.c: // +//#include "naett_internal.h" + +#ifdef __WINDOWS__ + +#include <stdlib.h> +#include <string.h> + +int naettPlatformInitRequest(InternalRequest* req) { +} + +void naettPlatformMakeRequest(InternalRequest* req, InternalResponse* res) { +} + +void naettPlatformFreeRequest(InternalRequest* req) { +} + +void naettPlatformCloseResponse(InternalResponse* res) { +} + +#endif // __WINDOWS__ +// End of inlined naett_win.c // + +// Inlined naett_android.c: // +//#include "naett_internal.h" + +#ifdef __ANDROID__ + +#ifdef __cplusplus +#error "Cannot build in C++ mode" +#endif + +#include <stdlib.h> +#include <string.h> +#include <jni.h> +#include <android/log.h> +#include <pthread.h> +#include <stdarg.h> + +#ifndef NDEBUG +#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "naett", __VA_ARGS__)) +#else +#define LOGD(...) ((void)0) +#endif +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "naett", __VA_ARGS__)) +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "naett", __VA_ARGS__)) + +static JavaVM* globalVM = NULL; + +JNIEXPORT +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + globalVM = vm; + return JNI_VERSION_1_2; +} + +static JavaVM* getVM() { + if (globalVM == NULL) { + LOGE("Panic: No VM configured, exiting."); + exit(42); + } + return globalVM; +} + +static JNIEnv* getEnv() { + JavaVM* vm = getVM(); + JNIEnv* env; + (*vm)->AttachCurrentThread(vm, &env, NULL); + return env; +} + +static int catch (JNIEnv* env) { + int thrown = (*env)->ExceptionCheck(env); + if (thrown) { + (*env)->ExceptionDescribe(env); + } + return thrown; +} + +static jmethodID getMethod(JNIEnv* env, jobject instance, const char* method, const char* sig) { + jclass clazz = (*env)->GetObjectClass(env, instance); + return (*env)->GetMethodID(env, clazz, method, sig); +} + +static jobject call(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) { + jmethodID methodID = getMethod(env, instance, method, sig); + va_list args; + va_start(args, sig); + jobject result = (*env)->CallObjectMethodV(env, instance, methodID, args); + va_end(args); + return result; +} + +static void voidCall(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) { + jmethodID methodID = getMethod(env, instance, method, sig); + va_list args; + va_start(args, sig); + (*env)->CallVoidMethodV(env, instance, methodID, args); + va_end(args); +} + +static jint intCall(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) { + jmethodID methodID = getMethod(env, instance, method, sig); + va_list args; + va_start(args, sig); + jint result = (*env)->CallIntMethodV(env, instance, methodID, args); + va_end(args); + return result; +} + +void naettPlatformInit(void* initThing) { + globalVM = (JavaVM*)initThing; +} + +int naettPlatformInitRequest(InternalRequest* req) { + JNIEnv* env = getEnv(); + jclass URL = (*env)->FindClass(env, "java/net/URL"); + jmethodID newURL = (*env)->GetMethodID(env, URL, "<init>", "(Ljava/lang/String;)V"); + jstring urlString = (*env)->NewStringUTF(env, req->url); + jobject url = (*env)->NewObject(env, URL, newURL, urlString); + if (catch (env)) { + return 0; + } + req->urlObject = (*env)->NewGlobalRef(env, url); + return 1; +} + +static void* processRequest(void* data) { + InternalResponse* res = (InternalResponse*)data; + InternalRequest* req = res->request; + + JNIEnv* env = getEnv(); + (*env)->PushLocalFrame(env, 10); + + jobject connection = call(env, req->urlObject, "openConnection", "()Ljava/net/URLConnection;"); + if (catch (env)) { + res->code = naettConnectionError; + goto finally; + } + + KVLink* header = req->options.headers; + while (header != NULL) { + jstring name = (*env)->NewStringUTF(env, header->key); + jstring value = (*env)->NewStringUTF(env, header->value); + voidCall(env, connection, "addRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V", name, value); + (*env)->DeleteLocalRef(env, name); + (*env)->DeleteLocalRef(env, value); + header = header->next; + } + + jobject outputStream = NULL; + if (strcmp(req->options.method, "POST") == 0 || strcmp(req->options.method, "PUT") == 0 || + strcmp(req->options.method, "PATCH") == 0 || strcmp(req->options.method, "DELETE") == 0) { + voidCall(env, connection, "setDoOutput", "(Z)V", 1); + outputStream = call(env, connection, "getOutputStream", "()Ljava/io/OutputStream;"); + } + 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"); + if (catch (env)) { + res->code = naettConnectionError; + goto finally; + } + + const int bufSize = 10240; + jbyteArray buffer = (*env)->NewByteArray(env, bufSize); + char byteBuffer[bufSize]; + + if (outputStream != NULL) { + int bytesRead = 0; + if (req->options.bodyReader != NULL) + do { + bytesRead = req->options.bodyReader(byteBuffer, bufSize, req->options.bodyReaderData); + if (bytesRead > 0) { + voidCall(env, outputStream, "write", "([B,I,I)V", buffer, 0, bytesRead); + } else { + break; + } + } while (!res->closeRequested); + voidCall(env, outputStream, "close", "()V"); + } + + jobject headerMap = call(env, connection, "getHeaderFields", "()Ljava/util/Map;"); + if (catch (env)) { + res->code = naettProtocolError; + goto finally; + } + + jobject headerSet = call(env, headerMap, "keySet", "()Ljava/util/Set;"); + jarray headers = call(env, headerSet, "toArray", "()[Ljava/lang/Object;"); + jsize headerCount = (*env)->GetArrayLength(env, headers); + + for (int i = 0; i < headerCount; i++) { + jstring name = (*env)->GetObjectArrayElement(env, headers, i); + if (name == NULL) { + continue; + } + const char* nameString = (*env)->GetStringUTFChars(env, name, NULL); + + jobject values = call(env, headerMap, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", name); + jstring value = call(env, values, "get", "(I)Ljava/lang/Object;", 0); + const char* valueString = (*env)->GetStringUTFChars(env, value, NULL); + + naettAlloc(KVLink, node); + node->key = strdup(nameString); + node->value = strdup(valueString); + node->next = res->headers; + res->headers = node; + + (*env)->ReleaseStringUTFChars(env, name, nameString); + (*env)->ReleaseStringUTFChars(env, value, valueString); + + (*env)->DeleteLocalRef(env, name); + (*env)->DeleteLocalRef(env, value); + (*env)->DeleteLocalRef(env, header); + } + + int statusCode = intCall(env, connection, "getResponseCode", "()I"); + + jobject inputStream = NULL; + + if (statusCode >= 400) { + inputStream = call(env, connection, "getErrorStream", "()Ljava/io/InputStream;"); + } else { + inputStream = call(env, connection, "getInputStream", "()Ljava/io/InputStream;"); + } + if (catch (env)) { + res->code = naettProtocolError; + goto finally; + } + + jint bytesRead = 0; + do { + bytesRead = intCall(env, inputStream, "read", "([B)I", buffer); + if (catch (env)) { + res->code = naettReadError; + goto finally; + } + if (bytesRead < 0) { + break; + } + (*env)->GetByteArrayRegion(env, buffer, 0, bytesRead, byteBuffer); + req->options.bodyWriter(byteBuffer, bytesRead, req->options.bodyWriterData); + } while (!res->closeRequested); + + voidCall(env, inputStream, "close", "()V"); + + res->code = statusCode; + +finally: + (*env)->PopLocalFrame(env, NULL); + JavaVM* vm = getVM(); + (*env)->ExceptionClear(env); + (*vm)->DetachCurrentThread(vm); + res->workerThread = NULL; + return NULL; +} + +static void startWorkerThread(InternalResponse* res) { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + pthread_create(&res->workerThread, &attr, processRequest, res); + pthread_setname_np(res->workerThread, "naett worker thread"); +} + +void naettPlatformMakeRequest(InternalResponse* res) { + startWorkerThread(res); +} + +void naettPlatformFreeRequest(InternalRequest* req) { + JNIEnv* env = getEnv(); + (*env)->DeleteGlobalRef(env, req->urlObject); +} + +void naettPlatformCloseResponse(InternalResponse* res) { + res->closeRequested = 1; + if (res->workerThread != NULL) { + int joinResult = pthread_join(res->workerThread, NULL); + if (joinResult != 0) { + LOGE("Failed to join: %s", strerror(res)); + } + } +} + +#endif // __ANDROID__ +// End of inlined naett_android.c // + // End of inlined amalgam.h // diff --git a/naett.h b/naett.h @@ -1,6 +1,12 @@ #ifndef LIBNAETT_H #define LIBNAETT_H +#ifdef __cplusplus +extern "C" { +#endif + +void naettInit(void* initThing); + typedef struct naettReq naettReq; typedef struct naettRes naettRes; typedef struct naettOption naettOption; @@ -20,6 +26,13 @@ void naettFree(naettReq* request); naettRes* naettMake(naettReq* request); int naettComplete(const naettRes* response); +enum { + naettConnectionError = -1, + naettProtocolError = -2, + naettReadError = -3, + naettConnecting = 0, +}; +int naettGetStatus(const naettRes* response); const void* naettGetBody(naettRes* response, int* size); const char* naettGetHeader(naettRes* response, const char* name); void naettClose(naettRes* response); @@ -27,4 +40,8 @@ void naettClose(naettRes* response); naettReq* naettRequest_va(const char* url, int numOptions, ...); #define countOptions(...) ((sizeof((void*[]){ __VA_ARGS__ }) / sizeof(void*))) +#ifdef __cplusplus +} +#endif + #endif // LIBNAETT_H diff --git a/src/Makefile b/src/Makefile @@ -1,4 +1,5 @@ all: ../naett.c +.PHONY : ../naett.c -../naett.c: +../naett.c: ./bundle.sh $@ amalgam.h diff --git a/src/amalgam.h b/src/amalgam.h @@ -1,3 +1,5 @@ #include "naett.h" #include "naett_core.c" #include "naett_osx.c" +#include "naett_win.c" +#include "naett_android.c" diff --git a/src/naett_android.c b/src/naett_android.c @@ -0,0 +1,265 @@ +#include "naett_internal.h" + +#ifdef __ANDROID__ + +#ifdef __cplusplus +#error "Cannot build in C++ mode" +#endif + +#include <stdlib.h> +#include <string.h> +#include <jni.h> +#include <android/log.h> +#include <pthread.h> +#include <stdarg.h> + +#ifndef NDEBUG +#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "naett", __VA_ARGS__)) +#else +#define LOGD(...) ((void)0) +#endif +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "naett", __VA_ARGS__)) +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "naett", __VA_ARGS__)) + +static JavaVM* globalVM = NULL; + +JNIEXPORT +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + globalVM = vm; + return JNI_VERSION_1_2; +} + +static JavaVM* getVM() { + if (globalVM == NULL) { + LOGE("Panic: No VM configured, exiting."); + exit(42); + } + return globalVM; +} + +static JNIEnv* getEnv() { + JavaVM* vm = getVM(); + JNIEnv* env; + (*vm)->AttachCurrentThread(vm, &env, NULL); + return env; +} + +static int catch (JNIEnv* env) { + int thrown = (*env)->ExceptionCheck(env); + if (thrown) { + (*env)->ExceptionDescribe(env); + } + return thrown; +} + +static jmethodID getMethod(JNIEnv* env, jobject instance, const char* method, const char* sig) { + jclass clazz = (*env)->GetObjectClass(env, instance); + return (*env)->GetMethodID(env, clazz, method, sig); +} + +static jobject call(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) { + jmethodID methodID = getMethod(env, instance, method, sig); + va_list args; + va_start(args, sig); + jobject result = (*env)->CallObjectMethodV(env, instance, methodID, args); + va_end(args); + return result; +} + +static void voidCall(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) { + jmethodID methodID = getMethod(env, instance, method, sig); + va_list args; + va_start(args, sig); + (*env)->CallVoidMethodV(env, instance, methodID, args); + va_end(args); +} + +static jint intCall(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) { + jmethodID methodID = getMethod(env, instance, method, sig); + va_list args; + va_start(args, sig); + jint result = (*env)->CallIntMethodV(env, instance, methodID, args); + va_end(args); + return result; +} + +void naettPlatformInit(void* initThing) { + globalVM = (JavaVM*)initThing; +} + +int naettPlatformInitRequest(InternalRequest* req) { + JNIEnv* env = getEnv(); + jclass URL = (*env)->FindClass(env, "java/net/URL"); + jmethodID newURL = (*env)->GetMethodID(env, URL, "<init>", "(Ljava/lang/String;)V"); + jstring urlString = (*env)->NewStringUTF(env, req->url); + jobject url = (*env)->NewObject(env, URL, newURL, urlString); + if (catch (env)) { + return 0; + } + req->urlObject = (*env)->NewGlobalRef(env, url); + return 1; +} + +static void* processRequest(void* data) { + InternalResponse* res = (InternalResponse*)data; + InternalRequest* req = res->request; + + JNIEnv* env = getEnv(); + (*env)->PushLocalFrame(env, 10); + + jobject connection = call(env, req->urlObject, "openConnection", "()Ljava/net/URLConnection;"); + if (catch (env)) { + res->code = naettConnectionError; + goto finally; + } + + KVLink* header = req->options.headers; + while (header != NULL) { + jstring name = (*env)->NewStringUTF(env, header->key); + jstring value = (*env)->NewStringUTF(env, header->value); + voidCall(env, connection, "addRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V", name, value); + (*env)->DeleteLocalRef(env, name); + (*env)->DeleteLocalRef(env, value); + header = header->next; + } + + jobject outputStream = NULL; + if (strcmp(req->options.method, "POST") == 0 || strcmp(req->options.method, "PUT") == 0 || + strcmp(req->options.method, "PATCH") == 0 || strcmp(req->options.method, "DELETE") == 0) { + voidCall(env, connection, "setDoOutput", "(Z)V", 1); + outputStream = call(env, connection, "getOutputStream", "()Ljava/io/OutputStream;"); + } + 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"); + if (catch (env)) { + res->code = naettConnectionError; + goto finally; + } + + const int bufSize = 10240; + jbyteArray buffer = (*env)->NewByteArray(env, bufSize); + char byteBuffer[bufSize]; + + if (outputStream != NULL) { + int bytesRead = 0; + if (req->options.bodyReader != NULL) + do { + bytesRead = req->options.bodyReader(byteBuffer, bufSize, req->options.bodyReaderData); + if (bytesRead > 0) { + voidCall(env, outputStream, "write", "([B,I,I)V", buffer, 0, bytesRead); + } else { + break; + } + } while (!res->closeRequested); + voidCall(env, outputStream, "close", "()V"); + } + + jobject headerMap = call(env, connection, "getHeaderFields", "()Ljava/util/Map;"); + if (catch (env)) { + res->code = naettProtocolError; + goto finally; + } + + jobject headerSet = call(env, headerMap, "keySet", "()Ljava/util/Set;"); + jarray headers = call(env, headerSet, "toArray", "()[Ljava/lang/Object;"); + jsize headerCount = (*env)->GetArrayLength(env, headers); + + for (int i = 0; i < headerCount; i++) { + jstring name = (*env)->GetObjectArrayElement(env, headers, i); + if (name == NULL) { + continue; + } + const char* nameString = (*env)->GetStringUTFChars(env, name, NULL); + + jobject values = call(env, headerMap, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", name); + jstring value = call(env, values, "get", "(I)Ljava/lang/Object;", 0); + const char* valueString = (*env)->GetStringUTFChars(env, value, NULL); + + naettAlloc(KVLink, node); + node->key = strdup(nameString); + node->value = strdup(valueString); + node->next = res->headers; + res->headers = node; + + (*env)->ReleaseStringUTFChars(env, name, nameString); + (*env)->ReleaseStringUTFChars(env, value, valueString); + + (*env)->DeleteLocalRef(env, name); + (*env)->DeleteLocalRef(env, value); + (*env)->DeleteLocalRef(env, header); + } + + int statusCode = intCall(env, connection, "getResponseCode", "()I"); + + jobject inputStream = NULL; + + if (statusCode >= 400) { + inputStream = call(env, connection, "getErrorStream", "()Ljava/io/InputStream;"); + } else { + inputStream = call(env, connection, "getInputStream", "()Ljava/io/InputStream;"); + } + if (catch (env)) { + res->code = naettProtocolError; + goto finally; + } + + jint bytesRead = 0; + do { + bytesRead = intCall(env, inputStream, "read", "([B)I", buffer); + if (catch (env)) { + res->code = naettReadError; + goto finally; + } + if (bytesRead < 0) { + break; + } + (*env)->GetByteArrayRegion(env, buffer, 0, bytesRead, byteBuffer); + req->options.bodyWriter(byteBuffer, bytesRead, req->options.bodyWriterData); + } while (!res->closeRequested); + + voidCall(env, inputStream, "close", "()V"); + + res->code = statusCode; + +finally: + (*env)->PopLocalFrame(env, NULL); + JavaVM* vm = getVM(); + (*env)->ExceptionClear(env); + (*vm)->DetachCurrentThread(vm); + res->workerThread = NULL; + return NULL; +} + +static void startWorkerThread(InternalResponse* res) { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + pthread_create(&res->workerThread, &attr, processRequest, res); + pthread_setname_np(res->workerThread, "naett worker thread"); +} + +void naettPlatformMakeRequest(InternalResponse* res) { + startWorkerThread(res); +} + +void naettPlatformFreeRequest(InternalRequest* req) { + JNIEnv* env = getEnv(); + (*env)->DeleteGlobalRef(env, req->urlObject); +} + +void naettPlatformCloseResponse(InternalResponse* res) { + res->closeRequested = 1; + if (res->workerThread != NULL) { + int joinResult = pthread_join(res->workerThread, NULL); + if (joinResult != 0) { + LOGE("Failed to join: %s", strerror(res)); + } + } +} + +#endif // __ANDROID__ diff --git a/src/naett_core.c b/src/naett_core.c @@ -82,6 +82,7 @@ static int defaultBodyWriter(const void* source, int bytes, void* userData) { static void initRequest(InternalRequest* req, const char* url) { req->options.method = strdup("GET"); + req->options.timeoutMS = 5000; req->url = strdup(url); } @@ -94,6 +95,10 @@ static void applyOptionParams(InternalRequest* req, InternalOption* option) { // Public API +void naettInit(void* initThing) { + naettPlatformInit(initThing); +} + naettOption* naettMethod(const char* method) { naettAlloc(InternalOption, option); option->numParams = 1; @@ -184,6 +189,7 @@ naettOption* naettBodyWriter(naettWriteFunc writer, void* userData) { return (naettOption*)option; } + naettReq* naettRequest_va(const char* url, int numArgs, ...) { va_list args; InternalOption* option; @@ -198,8 +204,12 @@ naettReq* naettRequest_va(const char* url, int numArgs, ...) { } va_end(args); - naettPlatformInitRequest(req); - return (naettReq*)req; + if (naettPlatformInitRequest(req)) { + return (naettReq*)req; + } + + naettFree(req); + return NULL; } naettReq* naettRequestWithOptions(const char* url, int numOptions, const naettOption** options) { @@ -212,8 +222,12 @@ naettReq* naettRequestWithOptions(const char* url, int numOptions, const naettOp free(option); } - naettPlatformInitRequest(req); - return (naettReq*)req; + if (naettPlatformInitRequest(req)) { + return (naettReq*)req; + } + + naettFree(req); + return NULL; } naettRes* naettMake(naettReq* request) { @@ -224,7 +238,7 @@ naettRes* naettMake(naettReq* request) { req->options.bodyWriter = defaultBodyWriter; req->options.bodyWriterData = (void*) &res->body; } - naettPlatformMakeRequest(req, res); + naettPlatformMakeRequest(res); return (naettRes*) res; } @@ -248,7 +262,12 @@ const char* naettGetHeader(naettRes* response, const char* name) { int naettComplete(const naettRes* response) { InternalResponse* res = (InternalResponse*)response; - return res->complete; + return res->code != 0; +} + +int naettGetStatus(const naettRes* response) { + InternalResponse* res = (InternalResponse*)response; + return res->code; } void naettFree(naettReq* request) { diff --git a/src/naett_internal.h b/src/naett_internal.h @@ -11,6 +11,11 @@ #define __LINUX__ 1 #endif +#if __ANDROID__ +#include <jni.h> +#include <pthread.h> +#endif + #ifdef __APPLE__ #include "TargetConditionals.h" #include <objc/objc.h> @@ -52,21 +57,28 @@ typedef struct { #if __APPLE__ id urlRequest; #endif +#if __ANDROID__ + jobject urlObject; +#endif } InternalRequest; typedef struct { InternalRequest* request; - int complete; int code; KVLink* headers; Buffer body; #if __APPLE__ id session; #endif +#if __ANDROID__ + pthread_t workerThread; + int closeRequested; +#endif } InternalResponse; -void naettPlatformInitRequest(InternalRequest* req); -void naettPlatformMakeRequest(InternalRequest* req, InternalResponse* res); +void naettPlatformInit(void* initThing); +int naettPlatformInitRequest(InternalRequest* req); +void naettPlatformMakeRequest(InternalResponse* res); void naettPlatformFreeRequest(InternalRequest* req); void naettPlatformCloseResponse(InternalResponse* res); diff --git a/src/naett_osx.c b/src/naett_osx.c @@ -6,7 +6,7 @@ #include <stdlib.h> #include <string.h> -void naettPlatformInitRequest(InternalRequest* req) { +int naettPlatformInitRequest(InternalRequest* req) { id urlString = objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), req->url); id url = objc_msgSend_t(id, id)(class("NSURL"), sel("URLWithString:"), urlString); release(urlString); @@ -40,6 +40,7 @@ void naettPlatformInitRequest(InternalRequest* req) { } req->urlRequest = request; + return 1; } void didReceiveData(id self, SEL _sel, id session, id dataTask, id data) { diff --git a/src/naett_win.c b/src/naett_win.c @@ -0,0 +1,20 @@ +#include "naett_internal.h" + +#ifdef __WINDOWS__ + +#include <stdlib.h> +#include <string.h> + +int naettPlatformInitRequest(InternalRequest* req) { +} + +void naettPlatformMakeRequest(InternalRequest* req, InternalResponse* res) { +} + +void naettPlatformFreeRequest(InternalRequest* req) { +} + +void naettPlatformCloseResponse(InternalResponse* res) { +} + +#endif // __WINDOWS__