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

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__