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