#include #include #include #include #include #include #include #include #include #include #include #include #include "attach_env.hpp" namespace mbgl { class HTTPFileSource::Impl { public: Impl(const ResourceOptions resourceOptions_, const ClientOptions clientOptions_) : resourceOptions(resourceOptions_.clone()), clientOptions(clientOptions_.clone()) {}; android::UniqueEnv env{android::AttachEnv()}; void setResourceOptions(ResourceOptions options) { resourceOptions = options; }; ResourceOptions getResourceOptions() { return resourceOptions.clone(); }; void setClientOptions(ClientOptions options) { clientOptions = options; }; ClientOptions getClientOptions() { return clientOptions.clone(); }; private: ResourceOptions resourceOptions; ClientOptions clientOptions; }; class HTTPRequest : public AsyncRequest { public: static constexpr auto Name() { return "org/maplibre/android/http/NativeHttpRequest"; }; HTTPRequest(jni::JNIEnv&, const Resource&, FileSource::Callback); ~HTTPRequest(); void onFailure(jni::JNIEnv&, int type, const jni::String& message); void onResponse(jni::JNIEnv&, int code, const jni::String& etag, const jni::String& modified, const jni::String& cacheControl, const jni::String& expires, const jni::String& retryAfter, const jni::String& xRateLimitReset, const jni::Array& body); jni::Global> 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; }; namespace android { void RegisterNativeHTTPRequest(jni::JNIEnv& env) { static auto& javaClass = jni::Class::Singleton(env); #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name) jni::RegisterNativePeer(env, javaClass, "nativePtr", 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 dataRangeStr; std::string etagStr; std::string modifiedStr; if (resource.dataRange) { dataRangeStr = std::string("bytes=") + std::to_string(resource.dataRange->first) + std::string("-") + std::to_string(resource.dataRange->second); } if (resource.priorEtag) { etagStr = *resource.priorEtag; } else if (resource.priorModified) { modifiedStr = util::rfc1123(*resource.priorModified); } jni::UniqueLocalFrame frame = jni::PushLocalFrame(env, 10); static auto& javaClass = jni::Class::Singleton(env); static auto constructor = javaClass.GetConstructor(env); javaRequest = jni::NewGlobal(env, javaClass.New(env, constructor, reinterpret_cast(this), jni::Make(env, resource.url), jni::Make(env, dataRangeStr), jni::Make(env, etagStr), jni::Make(env, modifiedStr), (jboolean)(resource_.usage == Resource::Usage::Offline))); } HTTPRequest::~HTTPRequest() { android::UniqueEnv env = android::AttachEnv(); static auto& javaClass = jni::Class::Singleton(*env); static auto cancel = javaClass.GetMethod(*env, "cancel"); javaRequest.Call(*env, cancel); } void HTTPRequest::onResponse(jni::JNIEnv& env, int code, const jni::String& etag, const jni::String& modified, const jni::String& cacheControl, const jni::String& expires, const jni::String& jRetryAfter, const jni::String& jXRateLimitReset, const 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) { const auto cc = http::CacheControl::parse(jni::Make(env, cacheControl).c_str()); response.expires = cc.toTimePoint(); response.mustRevalidate = cc.mustRevalidate; } if (expires) { response.expires = util::parseTimestamp(jni::Make(env, expires).c_str()); } if (code == 200 || code == 206) { 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) { std::optional retryAfter; std::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 "} + util::toString(code)); } else { response.error = std::make_unique(Error::Reason::Other, std::string{"HTTP status code "} + util::toString(code)); } async.send(); } void HTTPRequest::onFailure(jni::JNIEnv& env, int type, const 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(const ResourceOptions& resourceOptions, const ClientOptions& clientOptions) : impl(std::make_unique(resourceOptions.clone(), clientOptions.clone())) {} HTTPFileSource::~HTTPFileSource() = default; std::unique_ptr HTTPFileSource::request(const Resource& resource, Callback callback) { return std::make_unique(*impl->env, resource, callback); } void HTTPFileSource::setResourceOptions(ResourceOptions options) { impl->setResourceOptions(options.clone()); } ResourceOptions HTTPFileSource::getResourceOptions() { return impl->getResourceOptions(); } void HTTPFileSource::setClientOptions(ClientOptions options) { impl->setClientOptions(options.clone()); } ClientOptions HTTPFileSource::getClientOptions() { return impl->getClientOptions(); } } // namespace mbgl