summaryrefslogtreecommitdiff
path: root/platform/android
diff options
context:
space:
mode:
authorLeith Bade <leith@mapbox.com>2015-07-31 13:00:52 +1000
committerLeith Bade <leith@mapbox.com>2015-08-11 11:59:08 +1000
commit0cbe7e07f5ada444de45b3936f7378dd4f3b9d6b (patch)
tree72e2e56f37659d9b55b2815ed9aa8a1e363c1184 /platform/android
parentdc11b8132ba7ccc58c280e580526425e7389df86 (diff)
downloadqtlocation-mapboxgl-0cbe7e07f5ada444de45b3936f7378dd4f3b9d6b.tar.gz
Use OkHTTP to implement HTTPContext on Android.
Closes #823
Diffstat (limited to 'platform/android')
-rw-r--r--platform/android/http_request_android.cpp399
1 files changed, 399 insertions, 0 deletions
diff --git a/platform/android/http_request_android.cpp b/platform/android/http_request_android.cpp
new file mode 100644
index 0000000000..044f772628
--- /dev/null
+++ b/platform/android/http_request_android.cpp
@@ -0,0 +1,399 @@
+#include <mbgl/storage/http_context_base.hpp>
+#include <mbgl/storage/http_request_base.hpp>
+#include <mbgl/storage/resource.hpp>
+#include <mbgl/storage/response.hpp>
+#include <mbgl/util/chrono.hpp>
+#include <mbgl/platform/log.hpp>
+#include <mbgl/android/jni.hpp>
+
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/util.hpp>
+#include <mbgl/util/string.hpp>
+#include <mbgl/util/parsedate.h>
+
+#include <jni.h>
+
+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(uv_loop_t *loop);
+ ~HTTPAndroidContext();
+
+ HTTPRequestBase* createRequest(const Resource&,
+ RequestBase::Callback,
+ uv_loop_t*,
+ std::shared_ptr<const Response>) final;
+
+ uv_loop_t *loop = nullptr;
+
+ JavaVM *vm = nullptr;
+ jobject obj = nullptr;
+};
+
+class HTTPAndroidRequest : public HTTPRequestBase {
+public:
+ HTTPAndroidRequest(HTTPAndroidContext*,
+ const Resource&,
+ Callback,
+ uv_loop_t*,
+ std::shared_ptr<const Response>);
+ ~HTTPAndroidRequest();
+
+ void cancel() final;
+ void retry() final;
+
+ void onFailure(int type, std::string message);
+ void onResponse(int code, std::string message, std::string etag, std::string modified, std::string cacheControl, std::string expires, std::string body);
+
+private:
+ void retry(uint64_t timeout) final;
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ static void restart(uv_timer_t *timer, int);
+#else
+ static void restart(uv_timer_t *timer);
+#endif
+ void finish(ResponseStatus status);
+ void start();
+
+ HTTPAndroidContext *context = nullptr;
+
+ bool cancelled = false;
+
+ std::unique_ptr<Response> response;
+ const std::shared_ptr<const Response> existingResponse;
+
+ jobject obj = nullptr;
+
+ uv_timer_t *timer = nullptr;
+ enum : bool { PreemptImmediately, ExponentialBackoff } strategy = PreemptImmediately;
+ int attempts = 0;
+
+ static const int maxAttempts = 4;
+
+ static const int connectionError = 0;
+ static const int temporaryError = 1;
+ static const int permanentError = 1;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+HTTPAndroidContext::HTTPAndroidContext(uv_loop_t *loop_)
+ : HTTPContextBase(loop_),
+ loop(loop_),
+ vm(mbgl::android::theJVM) {
+
+ JNIEnv *env = nullptr;
+ bool detach = mbgl::android::attach_jni_thread(vm, &env, "HTTPAndroidContext::HTTPAndroidContext()");
+
+ const std::vector<JNINativeMethod> methods = {
+ {"nativeOnFailure", "(JILjava/lang/String;)V", reinterpret_cast<void *>(&nativeOnFailure)},
+ {"nativeOnResponse",
+ "(JILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[B)V",
+ reinterpret_cast<void *>(&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 Resource& resource,
+ RequestBase::Callback callback,
+ uv_loop_t* loop_,
+ std::shared_ptr<const Response> response) {
+ return new HTTPAndroidRequest(this, resource, callback, loop_, response);
+}
+
+HTTPAndroidRequest::HTTPAndroidRequest(HTTPAndroidContext* context_, const Resource& resource_, Callback callback_, uv_loop_t*, std::shared_ptr<const Response> response_)
+ : HTTPRequestBase(resource_, callback_),
+ context(context_),
+ existingResponse(response_) {
+
+ std::string etagStr;
+ std::string modifiedStr;
+ if (existingResponse) {
+ if (!existingResponse->etag.empty()) {
+ etagStr = existingResponse->etag;
+ } else if (existingResponse->modified) {
+ modifiedStr = util::rfc1123(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, resource.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<jlong>(this), resourceUrl, userAgent, etag, modified);
+ if (env->ExceptionCheck() || (obj == nullptr)) {
+ env->ExceptionDescribe();
+ }
+
+ obj = env->NewGlobalRef(obj);
+ if (obj == nullptr) {
+ env->ExceptionDescribe();
+ }
+
+ mbgl::android::detach_jni_thread(context->vm, &env, detach);
+
+ context->addRequest(this);
+ start();
+}
+
+HTTPAndroidRequest::~HTTPAndroidRequest() {
+ context->removeRequest(this);
+
+ 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);
+
+ if (timer) {
+ uv_timer_stop(timer);
+ uv::close(timer);
+ timer = nullptr;
+ }
+}
+
+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::start() {
+ attempts++;
+
+ JNIEnv *env = nullptr;
+ bool detach = mbgl::android::attach_jni_thread(context->vm, &env, "HTTPAndroidContext::start()");
+
+ env->CallVoidMethod(obj, mbgl::android::httpRequestStartId);
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ }
+
+ mbgl::android::detach_jni_thread(context->vm, &env, detach);
+}
+
+void HTTPAndroidRequest::retry(uint64_t timeout) {
+ response.reset();
+
+ assert(!timer);
+ timer = new uv_timer_t;
+ timer->data = this;
+ uv_timer_init(context->loop, timer);
+ uv_timer_start(timer, restart, timeout, 0);
+}
+
+void HTTPAndroidRequest::retry() {
+ if (timer && strategy == PreemptImmediately) {
+ uv_timer_stop(timer);
+ uv_timer_start(timer, restart, 0, 0);
+ }
+}
+
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+void HTTPAndroidRequest::restart(uv_timer_t *timer, int) {
+#else
+void HTTPAndroidRequest::restart(uv_timer_t *timer) {
+#endif
+ auto baton = reinterpret_cast<HTTPAndroidRequest *>(timer->data);
+
+ baton->timer = nullptr;
+ uv::close(timer);
+
+ baton->start();
+}
+
+void HTTPAndroidRequest::finish(ResponseStatus status) {
+ if (status == ResponseStatus::TemporaryError && attempts < maxAttempts) {
+ strategy = ExponentialBackoff;
+ return retry((1 << (attempts - 1)) * 1000);
+ } else if (status == ResponseStatus::ConnectionError && attempts < maxAttempts) {
+ strategy = PreemptImmediately;
+ return retry(30000);
+ }
+
+ if (status == ResponseStatus::NotModified) {
+ notify(std::move(response), FileCache::Hint::Refresh);
+ } else {
+ notify(std::move(response), FileCache::Hint::Full);
+ }
+
+ delete this;
+}
+
+int64_t parseCacheControl(const char *value) {
+ if (value) {
+ unsigned long long seconds = 0;
+ // TODO: cache-control may contain other information as well:
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+ if (std::sscanf(value, "max-age=%llu", &seconds) == 1) {
+ return std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch()).count() +
+ seconds;
+ }
+ }
+
+ return 0;
+}
+
+void HTTPAndroidRequest::onResponse(int code, std::string message, std::string etag, std::string modified, std::string cacheControl, std::string expires, std::string body) {
+ if (cancelled) {
+ delete this;
+ return;
+ }
+
+ if (!response) {
+ response = std::make_unique<Response>();
+ }
+
+ response->message = message;
+ response->modified = parse_date(modified.c_str());
+ response->etag = etag;
+ response->expires = parseCacheControl(cacheControl.c_str());
+ if (!expires.empty()) {
+ response->expires = parse_date(expires.c_str());
+ }
+ response->data = body;
+
+ if (code == 304) {
+ if (existingResponse) {
+ response->status = existingResponse->status;
+ response->message = existingResponse->message;
+ response->modified = existingResponse->modified;
+ response->etag = existingResponse->etag;
+ response->data = existingResponse->data;
+ return finish(ResponseStatus::NotModified);
+ } else {
+ response->status = Response::Successful;
+ return finish(ResponseStatus::Successful);
+ }
+ } else if (code == 200) {
+ response->status = Response::Successful;
+ return finish(ResponseStatus::Successful);
+ } else if (code >= 500 && code < 600) {
+ response->status = Response::Error;
+ response->message = "HTTP status code " + util::toString(code);
+ return finish(ResponseStatus::TemporaryError);
+ } else {
+ response->status = Response::Error;
+ response->message = "HTTP status code " + util::toString(code);
+ return finish(ResponseStatus::PermanentError);
+ }
+
+ throw std::runtime_error("Response hasn't been handled");
+}
+
+void HTTPAndroidRequest::onFailure(int type, std::string message) {
+ if (cancelled) {
+ delete this;
+ return;
+ }
+
+ if (!response) {
+ response = std::make_unique<Response>();
+ }
+
+ response->status = Response::Error;
+ response->message = message;
+
+ switch (type) {
+ case connectionError:
+ return finish(ResponseStatus::ConnectionError);
+
+ case temporaryError:
+ return finish(ResponseStatus::TemporaryError);
+
+ default:
+ return finish(ResponseStatus::PermanentError);
+ }
+
+ throw std::runtime_error("Response hasn't been handled");
+}
+
+std::unique_ptr<HTTPContextBase> HTTPContextBase::createContext(uv_loop_t* loop) {
+ return std::make_unique<HTTPAndroidContext>(loop);
+}
+
+#pragma clang diagnostic ignored "-Wunused-parameter"
+
+void JNICALL nativeOnFailure(JNIEnv *env, jobject obj, jlong nativePtr, jint type, jstring message) {
+ mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnFailure");
+ assert(nativePtr != 0);
+ HTTPAndroidRequest *request = reinterpret_cast<HTTPAndroidRequest *>(nativePtr);
+ std::string messageStr = mbgl::android::std_string_from_jstring(env, message);
+ return request->onFailure(type, messageStr);
+}
+
+void JNICALL nativeOnResponse(JNIEnv *env, jobject obj, 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);
+ HTTPAndroidRequest *request = reinterpret_cast<HTTPAndroidRequest *>(nativePtr);
+ std::string messageStr = mbgl::android::std_string_from_jstring(env, message);
+ std::string etagStr;
+ if (etag != nullptr) {
+ etagStr = mbgl::android::std_string_from_jstring(env, etag);
+ }
+ std::string modifiedStr;
+ if (modified != nullptr) {
+ modifiedStr = mbgl::android::std_string_from_jstring(env, modified);
+ }
+ std::string cacheControlStr;
+ if (cacheControl != nullptr) {
+ cacheControlStr = mbgl::android::std_string_from_jstring(env, cacheControl);
+ }
+ std::string expiresStr;
+ if (expires != nullptr) {
+ expiresStr = mbgl::android::std_string_from_jstring(env, expires);
+ }
+ jbyte* bodyData = env->GetByteArrayElements(body, nullptr);
+ std::string bodyStr(reinterpret_cast<char*>(bodyData), env->GetArrayLength(body));
+ env->ReleaseByteArrayElements(body, bodyData, JNI_ABORT);
+ return request->onResponse(code, messageStr, etagStr, modifiedStr, cacheControlStr, expiresStr, bodyStr);
+}
+
+}