#include #include #include #include #include #include #include #include #include #include "attach_env.hpp" namespace mbgl { class HTTPFileSource::Impl { public: android::UniqueEnv env { android::AttachEnv() }; }; class HTTPRequest : public AsyncRequest { public: static constexpr auto Name() { return "com/mapbox/mapboxsdk/http/HTTPRequest"; }; HTTPRequest(jni::JNIEnv&, const Resource&, FileSource::Callback); ~HTTPRequest(); void onFailure(jni::JNIEnv&, int type, jni::String message); void onResponse(jni::JNIEnv&, int code, jni::String etag, jni::String modified, jni::String cacheControl, jni::String expires, jni::String retryAfter, jni::String xRateLimitReset, jni::Array body); static jni::Class javaClass; jni::UniqueObject javaRequest; private: Resource resource; FileSource::Callback callback; Response response; util::AsyncTask async { [this] { // Calling `callback` may result in deleting `this`. Copy data to temporaries first. auto callback_ = callback; auto response_ = response; callback_(response_); } }; static const int connectionError = 0; static const int temporaryError = 1; static const int permanentError = 2; }; jni::Class HTTPRequest::javaClass; namespace android { void RegisterNativeHTTPRequest(jni::JNIEnv& env) { HTTPRequest::javaClass = *jni::Class::Find(env).NewGlobalRef(env).release(); #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name) jni::RegisterNativePeer(env, HTTPRequest::javaClass, "mNativePtr", METHOD(&HTTPRequest::onFailure, "nativeOnFailure"), METHOD(&HTTPRequest::onResponse, "nativeOnResponse")); } } // namespace android HTTPRequest::HTTPRequest(jni::JNIEnv& env, const Resource& resource_, FileSource::Callback callback_) : resource(resource_), callback(callback_) { std::string etagStr; std::string modifiedStr; if (resource.priorEtag) { etagStr = *resource.priorEtag; } else if (resource.priorModified) { modifiedStr = util::rfc1123(*resource.priorModified); } jni::UniqueLocalFrame frame = jni::PushLocalFrame(env, 10); static auto constructor = javaClass.GetConstructor(env); javaRequest = javaClass.New(env, constructor, reinterpret_cast(this), jni::Make(env, resource.url), jni::Make(env, etagStr), jni::Make(env, modifiedStr)).NewGlobalRef(env); } HTTPRequest::~HTTPRequest() { android::UniqueEnv env = android::AttachEnv(); static auto cancel = javaClass.GetMethod(*env, "cancel"); javaRequest->Call(*env, cancel); } void HTTPRequest::onResponse(jni::JNIEnv& env, int code, jni::String etag, jni::String modified, jni::String cacheControl, jni::String expires, jni::String jRetryAfter, jni::String jXRateLimitReset, jni::Array body) { using Error = Response::Error; if (etag) { response.etag = jni::Make(env, etag); } if (modified) { response.modified = util::parseTimestamp(jni::Make(env, modified).c_str()); } if (cacheControl) { response.expires = http::CacheControl::parse(jni::Make(env, cacheControl).c_str()).toTimePoint(); } if (expires) { response.expires = util::parseTimestamp(jni::Make(env, expires).c_str()); } if (code == 200) { if (body) { auto data = std::make_shared(body.Length(env), char()); jni::GetArrayRegion(env, *body, 0, data->size(), reinterpret_cast(&(*data)[0])); response.data = data; } else { response.data = std::make_shared(); } } else if (code == 204 || (code == 404 && resource.kind == Resource::Kind::Tile)) { response.noContent = true; } else if (code == 304) { response.notModified = true; } else if (code == 404) { response.error = std::make_unique(Error::Reason::NotFound, "HTTP status code 404"); } else if (code == 429) { optional retryAfter; optional xRateLimitReset; if (jRetryAfter) { retryAfter = jni::Make(env, jRetryAfter); } if (jXRateLimitReset) { xRateLimitReset = jni::Make(env, jXRateLimitReset); } response.error = std::make_unique(Error::Reason::RateLimit, "HTTP status code 429", http::parseRetryHeaders(retryAfter, xRateLimitReset)); } 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 HTTPRequest::onFailure(jni::JNIEnv& env, int type, jni::String message) { std::string messageStr = jni::Make(env, message); 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; default: response.error = std::make_unique(Error::Reason::Other, messageStr); } async.send(); } HTTPFileSource::HTTPFileSource() : impl(std::make_unique()) { } HTTPFileSource::~HTTPFileSource() = default; std::unique_ptr HTTPFileSource::request(const Resource& resource, Callback callback) { return std::make_unique(*impl->env, resource, callback); } uint32_t HTTPFileSource::maximumConcurrentRequests() { return 20; } } // namespace mbgl