diff options
author | Leith Bade <leith@mapbox.com> | 2015-08-12 14:58:33 +1000 |
---|---|---|
committer | Leith Bade <leith@mapbox.com> | 2015-08-12 14:58:33 +1000 |
commit | d4159d51419ae59824c1a20cb682d2a66150a7ea (patch) | |
tree | 45a04302c1d11c9d7dec87d00de3406b65769b5e /platform | |
parent | c472717b604643cd2a2df0c45f04b2d55affe8ef (diff) | |
parent | 0cbe7e07f5ada444de45b3936f7378dd4f3b9d6b (diff) | |
download | qtlocation-mapboxgl-d4159d51419ae59824c1a20cb682d2a66150a7ea.tar.gz |
Merge branch 'android-okhttp'
Diffstat (limited to 'platform')
-rw-r--r-- | platform/android/http_request_android.cpp | 399 | ||||
-rw-r--r-- | platform/default/http_request_curl.cpp | 109 |
2 files changed, 399 insertions, 109 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); +} + +} diff --git a/platform/default/http_request_curl.cpp b/platform/default/http_request_curl.cpp index 0f7f8c0ac5..e416034b40 100644 --- a/platform/default/http_request_curl.cpp +++ b/platform/default/http_request_curl.cpp @@ -11,12 +11,6 @@ #include <curl/curl.h> -#ifdef __ANDROID__ -#include <mbgl/android/jni.hpp> -#include <zip.h> -#include <openssl/ssl.h> -#endif - #include <queue> #include <map> #include <cassert> @@ -331,104 +325,6 @@ int HTTPCURLContext::startTimeout(CURLM * /* multi */, long timeout_ms, void *us // ------------------------------------------------------------------------------------------------- -#ifdef __ANDROID__ - -// This function is called to load the CA bundle -// from http://curl.haxx.se/libcurl/c/cacertinmem.html¯ -static CURLcode sslctx_function(CURL * /* curl */, void *sslctx, void * /* parm */) { - - int error = 0; - struct zip *apk = zip_open(mbgl::android::apkPath.c_str(), 0, &error); - if (apk == nullptr) { - return CURLE_SSL_CACERT_BADFILE; - } - - struct zip_file *apkFile = zip_fopen(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE); - if (apkFile == nullptr) { - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - struct zip_stat stat; - if (zip_stat(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE, &stat) != 0) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - if (stat.size > std::numeric_limits<int>::max()) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - const auto pem = std::make_unique<char[]>(stat.size); - - if (static_cast<zip_uint64_t>(zip_fread(apkFile, reinterpret_cast<void *>(pem.get()), stat.size)) != stat.size) { - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // get a pointer to the X509 certificate store (which may be empty!) - X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX *)sslctx); - if (store == nullptr) { - return CURLE_SSL_CACERT_BADFILE; - } - - // get a BIO - BIO *bio = BIO_new_mem_buf(pem.get(), static_cast<int>(stat.size)); - if (bio == nullptr) { - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // use it to read the PEM formatted certificate from memory into an X509 - // structure that SSL can use - X509 *cert = nullptr; - while (PEM_read_bio_X509(bio, &cert, 0, nullptr) != nullptr) { - if (cert == nullptr) { - BIO_free(bio); - bio = nullptr; - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - // add our certificate to this store - if (X509_STORE_add_cert(store, cert) == 0) { - X509_free(cert); - cert = nullptr; - BIO_free(bio); - bio = nullptr; - store = nullptr; - return CURLE_SSL_CACERT_BADFILE; - } - - X509_free(cert); - cert = nullptr; - } - - // decrease reference counts - BIO_free(bio); - bio = nullptr; - - zip_fclose(apkFile); - apkFile = nullptr; - zip_close(apk); - apk = nullptr; - - // all set to go - return CURLE_OK; -} -#endif - HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& resource_, Callback callback_, uv_loop_t*, std::shared_ptr<const Response> response_) : HTTPRequestBase(resource_, callback_), context(context_), @@ -458,12 +354,7 @@ HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& reso handleError(curl_easy_setopt(handle, CURLOPT_PRIVATE, this)); handleError(curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error)); -#ifdef __ANDROID__ - handleError(curl_easy_setopt(handle, CURLOPT_SSLCERTTYPE, "PEM")); - handleError(curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function)); -#else handleError(curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt")); -#endif handleError(curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1)); handleError(curl_easy_setopt(handle, CURLOPT_URL, resource.url.c_str())); handleError(curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback)); |