naett_android.c (9458B)
1 #include "naett_internal.h" 2 3 #ifdef __ANDROID__ 4 5 #ifdef __cplusplus 6 #error "Cannot build in C++ mode" 7 #endif 8 9 #include <stdlib.h> 10 #include <string.h> 11 #include <jni.h> 12 #include <android/log.h> 13 #include <pthread.h> 14 #include <stdarg.h> 15 16 #ifndef NDEBUG 17 #define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "naett", __VA_ARGS__)) 18 #else 19 #define LOGD(...) ((void)0) 20 #endif 21 #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "naett", __VA_ARGS__)) 22 #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "naett", __VA_ARGS__)) 23 24 static JavaVM* globalVM = NULL; 25 26 static JavaVM* getVM() { 27 if (globalVM == NULL) { 28 LOGE("Panic: No VM configured, exiting."); 29 exit(42); 30 } 31 return globalVM; 32 } 33 34 static JNIEnv* getEnv() { 35 JavaVM* vm = getVM(); 36 JNIEnv* env; 37 (*vm)->AttachCurrentThread(vm, &env, NULL); 38 return env; 39 } 40 41 static int catch (JNIEnv* env) { 42 int thrown = (*env)->ExceptionCheck(env); 43 if (thrown) { 44 (*env)->ExceptionDescribe(env); 45 } 46 return thrown; 47 } 48 49 static jmethodID getMethod(JNIEnv* env, jobject instance, const char* method, const char* sig) { 50 jclass clazz = (*env)->GetObjectClass(env, instance); 51 jmethodID id = (*env)->GetMethodID(env, clazz, method, sig); 52 (*env)->DeleteLocalRef(env, clazz); 53 return id; 54 } 55 56 static jobject call(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) { 57 jmethodID methodID = getMethod(env, instance, method, sig); 58 va_list args; 59 va_start(args, sig); 60 jobject result = (*env)->CallObjectMethodV(env, instance, methodID, args); 61 va_end(args); 62 return result; 63 } 64 65 static void voidCall(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) { 66 jmethodID methodID = getMethod(env, instance, method, sig); 67 va_list args; 68 va_start(args, sig); 69 (*env)->CallVoidMethodV(env, instance, methodID, args); 70 va_end(args); 71 } 72 73 static jint intCall(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) { 74 jmethodID methodID = getMethod(env, instance, method, sig); 75 va_list args; 76 va_start(args, sig); 77 jint result = (*env)->CallIntMethodV(env, instance, methodID, args); 78 va_end(args); 79 return result; 80 } 81 82 void naettPlatformInit(naettInitData initData) { 83 globalVM = initData; 84 } 85 86 int naettPlatformInitRequest(InternalRequest* req) { 87 JNIEnv* env = getEnv(); 88 (*env)->PushLocalFrame(env, 10); 89 jclass URL = (*env)->FindClass(env, "java/net/URL"); 90 jmethodID newURL = (*env)->GetMethodID(env, URL, "<init>", "(Ljava/lang/String;)V"); 91 jstring urlString = (*env)->NewStringUTF(env, req->url); 92 jobject url = (*env)->NewObject(env, URL, newURL, urlString); 93 if (catch (env)) { 94 (*env)->PopLocalFrame(env, NULL); 95 return 0; 96 } 97 req->urlObject = (*env)->NewGlobalRef(env, url); 98 (*env)->PopLocalFrame(env, NULL); 99 return 1; 100 } 101 102 static void* processRequest(void* data) { 103 const int bufSize = 10240; 104 char byteBuffer[bufSize]; 105 InternalResponse* res = (InternalResponse*)data; 106 InternalRequest* req = res->request; 107 108 JNIEnv* env = getEnv(); 109 (*env)->PushLocalFrame(env, 100); 110 111 jobject connection = call(env, req->urlObject, "openConnection", "()Ljava/net/URLConnection;"); 112 if (catch (env)) { 113 res->code = naettConnectionError; 114 goto finally; 115 } 116 117 { 118 jstring name = (*env)->NewStringUTF(env, "User-Agent"); 119 jstring value = (*env)->NewStringUTF(env, req->options.userAgent ? req->options.userAgent : NAETT_UA); 120 voidCall(env, connection, "addRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V", name, value); 121 (*env)->DeleteLocalRef(env, name); 122 (*env)->DeleteLocalRef(env, value); 123 } 124 125 KVLink* header = req->options.headers; 126 while (header != NULL) { 127 jstring name = (*env)->NewStringUTF(env, header->key); 128 jstring value = (*env)->NewStringUTF(env, header->value); 129 voidCall(env, connection, "addRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V", name, value); 130 (*env)->DeleteLocalRef(env, name); 131 (*env)->DeleteLocalRef(env, value); 132 header = header->next; 133 } 134 135 jobject outputStream = NULL; 136 if (strcmp(req->options.method, "POST") == 0 || strcmp(req->options.method, "PUT") == 0 || 137 strcmp(req->options.method, "PATCH") == 0 || strcmp(req->options.method, "DELETE") == 0) { 138 voidCall(env, connection, "setDoOutput", "(Z)V", 1); 139 outputStream = call(env, connection, "getOutputStream", "()Ljava/io/OutputStream;"); 140 } 141 jobject methodString = (*env)->NewStringUTF(env, req->options.method); 142 voidCall(env, connection, "setRequestMethod", "(Ljava/lang/String;)V", methodString); 143 voidCall(env, connection, "setConnectTimeout", "(I)V", req->options.timeoutMS); 144 voidCall(env, connection, "setInstanceFollowRedirects", "(Z)V", 1); 145 146 voidCall(env, connection, "connect", "()V"); 147 if (catch (env)) { 148 res->code = naettConnectionError; 149 goto finally; 150 } 151 152 jbyteArray buffer = (*env)->NewByteArray(env, bufSize); 153 154 if (outputStream != NULL) { 155 int bytesRead = 0; 156 if (req->options.bodyReader != NULL) 157 do { 158 bytesRead = req->options.bodyReader(byteBuffer, bufSize, req->options.bodyReaderData); 159 if (bytesRead > 0) { 160 (*env)->SetByteArrayRegion(env, buffer, 0, bytesRead, (const jbyte*) byteBuffer); 161 voidCall(env, outputStream, "write", "([BII)V", buffer, 0, bytesRead); 162 } else { 163 break; 164 } 165 } while (!res->closeRequested); 166 voidCall(env, outputStream, "close", "()V"); 167 } 168 169 jobject headerMap = call(env, connection, "getHeaderFields", "()Ljava/util/Map;"); 170 if (catch (env)) { 171 res->code = naettProtocolError; 172 goto finally; 173 } 174 175 jobject headerSet = call(env, headerMap, "keySet", "()Ljava/util/Set;"); 176 jarray headers = call(env, headerSet, "toArray", "()[Ljava/lang/Object;"); 177 jsize headerCount = (*env)->GetArrayLength(env, headers); 178 179 KVLink *firstHeader = NULL; 180 for (int i = 0; i < headerCount; i++) { 181 jstring name = (*env)->GetObjectArrayElement(env, headers, i); 182 if (name == NULL) { 183 continue; 184 } 185 const char* nameString = (*env)->GetStringUTFChars(env, name, NULL); 186 187 jobject values = call(env, headerMap, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", name); 188 jstring value = call(env, values, "get", "(I)Ljava/lang/Object;", 0); 189 const char* valueString = (*env)->GetStringUTFChars(env, value, NULL); 190 191 naettAlloc(KVLink, node); 192 node->key = strdup(nameString); 193 node->value = strdup(valueString); 194 node->next = firstHeader; 195 firstHeader = node; 196 197 (*env)->ReleaseStringUTFChars(env, name, nameString); 198 (*env)->ReleaseStringUTFChars(env, value, valueString); 199 200 (*env)->DeleteLocalRef(env, name); 201 (*env)->DeleteLocalRef(env, value); 202 (*env)->DeleteLocalRef(env, values); 203 } 204 res->headers = firstHeader; 205 206 const char *contentLength = naettGetHeader((naettRes *)res, "Content-Length"); 207 if (!contentLength || sscanf(contentLength, "%d", &res->contentLength) != 1) { 208 res->contentLength = -1; 209 } 210 211 int statusCode = intCall(env, connection, "getResponseCode", "()I"); 212 213 jobject inputStream = NULL; 214 215 if (statusCode >= 400) { 216 inputStream = call(env, connection, "getErrorStream", "()Ljava/io/InputStream;"); 217 } else { 218 inputStream = call(env, connection, "getInputStream", "()Ljava/io/InputStream;"); 219 } 220 if (catch (env)) { 221 res->code = naettProtocolError; 222 goto finally; 223 } 224 225 jint bytesRead = 0; 226 do { 227 bytesRead = intCall(env, inputStream, "read", "([B)I", buffer); 228 if (catch (env)) { 229 res->code = naettReadError; 230 goto finally; 231 } 232 if (bytesRead < 0) { 233 break; 234 } else if (bytesRead > 0) { 235 (*env)->GetByteArrayRegion(env, buffer, 0, bytesRead, (jbyte*) byteBuffer); 236 req->options.bodyWriter(byteBuffer, bytesRead, req->options.bodyWriterData); 237 res->totalBytesRead += bytesRead; 238 } 239 } while (!res->closeRequested); 240 241 voidCall(env, inputStream, "close", "()V"); 242 243 res->code = statusCode; 244 245 finally: 246 res->complete = 1; 247 (*env)->PopLocalFrame(env, NULL); 248 JavaVM* vm = getVM(); 249 (*env)->ExceptionClear(env); 250 (*vm)->DetachCurrentThread(vm); 251 return NULL; 252 } 253 254 static void startWorkerThread(InternalResponse* res) { 255 pthread_attr_t attr; 256 pthread_attr_init(&attr); 257 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); 258 pthread_create(&res->workerThread, &attr, processRequest, res); 259 pthread_setname_np(res->workerThread, "naett worker thread"); 260 } 261 262 void naettPlatformMakeRequest(InternalResponse* res) { 263 startWorkerThread(res); 264 } 265 266 void naettPlatformFreeRequest(InternalRequest* req) { 267 JNIEnv* env = getEnv(); 268 (*env)->DeleteGlobalRef(env, req->urlObject); 269 } 270 271 void naettPlatformCloseResponse(InternalResponse* res) { 272 res->closeRequested = 1; 273 if (res->workerThread != 0) { 274 int joinResult = pthread_join(res->workerThread, NULL); 275 if (joinResult != 0) { 276 LOGE("Failed to join: %s", strerror(joinResult)); 277 } 278 } 279 } 280 281 #endif // __ANDROID__