#include #include #include #include #include "jni.hpp" #include #include #include #include #include #include namespace mbgl { void JNICALL nativeOnFailure(JNIEnv *env, jobject obj, jlong nativePtr, jint type, jstring message); void JNICALL nativeOnResponse(JNIEnv *env, jobject obj, jlong nativePtr, jint code, jstring message, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body); class HTTPAndroidRequest; class HTTPAndroidContext : public HTTPContextBase { public: explicit HTTPAndroidContext(); ~HTTPAndroidContext(); HTTPRequestBase* createRequest(const std::string& url, HTTPRequestBase::Callback, std::shared_ptr) final; JavaVM *vm = nullptr; jobject obj = nullptr; }; class HTTPAndroidRequest : public HTTPRequestBase { public: HTTPAndroidRequest(HTTPAndroidContext*, const std::string& url, Callback, std::shared_ptr); ~HTTPAndroidRequest(); void cancel() final; void onFailure(JNIEnv*, int type, jstring message); void onResponse(JNIEnv*, int code, jstring message, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body); private: void finish(); HTTPAndroidContext *context = nullptr; bool cancelled = false; std::unique_ptr response; const std::shared_ptr existingResponse; jobject obj = nullptr; util::AsyncTask async; static const int connectionError = 0; static const int temporaryError = 1; static const int permanentError = 2; static const int canceledError = 3; }; // ------------------------------------------------------------------------------------------------- HTTPAndroidContext::HTTPAndroidContext() : vm(mbgl::android::theJVM) { JNIEnv *env = nullptr; bool detach = mbgl::android::attach_jni_thread(vm, &env, "HTTPAndroidContext::HTTPAndroidContext()"); const std::vector methods = { {"nativeOnFailure", "(JILjava/lang/String;)V", reinterpret_cast(&nativeOnFailure)}, {"nativeOnResponse", "(JILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[B)V", reinterpret_cast(&nativeOnResponse)} }; if (env->RegisterNatives(mbgl::android::httpRequestClass, methods.data(), methods.size()) < 0) { env->ExceptionDescribe(); } obj = env->CallStaticObjectMethod(mbgl::android::httpContextClass, mbgl::android::httpContextGetInstanceId); if (env->ExceptionCheck() || (obj == nullptr)) { env->ExceptionDescribe(); } obj = env->NewGlobalRef(obj); if (obj == nullptr) { env->ExceptionDescribe(); } mbgl::android::detach_jni_thread(vm, &env, detach); } HTTPAndroidContext::~HTTPAndroidContext() { JNIEnv *env = nullptr; bool detach = mbgl::android::attach_jni_thread(vm, &env, "HTTPAndroidContext::~HTTPAndroidContext()"); env->DeleteGlobalRef(obj); obj = nullptr; mbgl::android::detach_jni_thread(vm, &env, detach); vm = nullptr; } HTTPRequestBase* HTTPAndroidContext::createRequest(const std::string& url, HTTPRequestBase::Callback callback, std::shared_ptr response) { return new HTTPAndroidRequest(this, url, callback, response); } HTTPAndroidRequest::HTTPAndroidRequest(HTTPAndroidContext* context_, const std::string& url_, Callback callback_, std::shared_ptr response_) : HTTPRequestBase(url_, callback_), context(context_), existingResponse(response_), async([this] { finish(); }) { std::string etagStr; std::string modifiedStr; if (existingResponse) { if (existingResponse->etag) { etagStr = *existingResponse->etag; } else if (existingResponse->modified) { modifiedStr = util::rfc1123(SystemClock::to_time_t(*existingResponse->modified)); } } JNIEnv *env = nullptr; bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::HTTPAndroidRequest()"); jstring resourceUrl = mbgl::android::std_string_to_jstring(env, url); jstring userAgent = mbgl::android::std_string_to_jstring(env, "MapboxGL/1.0"); jstring etag = mbgl::android::std_string_to_jstring(env, etagStr); jstring modified = mbgl::android::std_string_to_jstring(env, modifiedStr); obj = env->CallObjectMethod(context->obj, mbgl::android::httpContextCreateRequestId, reinterpret_cast(this), resourceUrl, userAgent, etag, modified); if (env->ExceptionCheck() || (obj == nullptr)) { env->ExceptionDescribe(); } obj = env->NewGlobalRef(obj); if (obj == nullptr) { env->ExceptionDescribe(); } env->CallVoidMethod(obj, mbgl::android::httpRequestStartId); if (env->ExceptionCheck()) { env->ExceptionDescribe(); } mbgl::android::detach_jni_thread(context->vm, &env, detach); } HTTPAndroidRequest::~HTTPAndroidRequest() { JNIEnv *env = nullptr; bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::~HTTPAndroidRequest()"); env->DeleteGlobalRef(obj); obj = nullptr; mbgl::android::detach_jni_thread(context->vm, &env, detach); } void HTTPAndroidRequest::cancel() { cancelled = true; JNIEnv *env = nullptr; bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::cancel()"); env->CallVoidMethod(obj, mbgl::android::httpRequestCancelId); if (env->ExceptionCheck()) { env->ExceptionDescribe(); } mbgl::android::detach_jni_thread(context->vm, &env, detach); } void HTTPAndroidRequest::finish() { if (!cancelled) { notify(std::move(response)); } delete this; } void HTTPAndroidRequest::onResponse(JNIEnv* env, int code, jstring /* message */, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body) { response = std::make_unique(); using Error = Response::Error; if (etag != nullptr) { response->etag = mbgl::android::std_string_from_jstring(env, etag); } if (modified != nullptr) { response->modified = SystemClock::from_time_t(parse_date(mbgl::android::std_string_from_jstring(env, modified).c_str())); } if (cacheControl != nullptr) { response->expires = parseCacheControl(mbgl::android::std_string_from_jstring(env, cacheControl).c_str()); } if (expires != nullptr) { response->expires = SystemClock::from_time_t(parse_date(mbgl::android::std_string_from_jstring(env, expires).c_str())); } if (body != nullptr) { jbyte* bodyData = env->GetByteArrayElements(body, nullptr); response->data = std::make_shared(reinterpret_cast(bodyData), env->GetArrayLength(body)); env->ReleaseByteArrayElements(body, bodyData, JNI_ABORT); } if (code == 200) { // Nothing to do; this is what we want } else if (code == 304) { response->notModified = true; if (existingResponse) { response->data = existingResponse->data; if (!response->expires) { response->expires = existingResponse->expires; } if (!response->modified) { response->modified = existingResponse->modified; } if (!response->etag) { response->etag = existingResponse->etag; } } } else if (code == 404) { response->error = std::make_unique(Error::Reason::NotFound, "HTTP status code 404"); } else if (code >= 500 && code < 600) { response->error = std::make_unique(Error::Reason::Server, std::string{ "HTTP status code " } + std::to_string(code)); } else { response->error = std::make_unique(Error::Reason::Other, std::string{ "HTTP status code " } + std::to_string(code)); } async.send(); } void HTTPAndroidRequest::onFailure(JNIEnv* env, int type, jstring message) { std::string messageStr = mbgl::android::std_string_from_jstring(env, message); response = std::make_unique(); using Error = Response::Error; switch (type) { case connectionError: response->error = std::make_unique(Error::Reason::Connection, messageStr); break; case temporaryError: response->error = std::make_unique(Error::Reason::Server, messageStr); break; case canceledError: response->error = std::make_unique(Error::Reason::Canceled, "Request was cancelled"); break; default: response->error = std::make_unique(Error::Reason::Other, messageStr); } async.send(); } std::unique_ptr HTTPContextBase::createContext() { return std::make_unique(); } void JNICALL nativeOnFailure(JNIEnv* env, jobject, jlong nativePtr, jint type, jstring message) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnFailure"); assert(nativePtr != 0); return reinterpret_cast(nativePtr)->onFailure(env, type, message); } void JNICALL nativeOnResponse(JNIEnv* env, jobject, jlong nativePtr, jint code, jstring message, jstring etag, jstring modified, jstring cacheControl, jstring expires, jbyteArray body) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnResponse"); assert(nativePtr != 0); return reinterpret_cast(nativePtr)->onResponse(env, code, message, etag, modified, cacheControl, expires, body); } }