[all] Replace HTTPContextBase/HTTPRequestBase with FileSource

This commit is contained in:
John Firebaugh
2016-02-15 17:35:13 -08:00
parent 6d88a23a60
commit 3af3e72bb3
16 changed files with 377 additions and 600 deletions

View File

@@ -27,7 +27,7 @@
'src/jni.cpp',
'src/attach_env.cpp',
'src/log_android.cpp',
'src/http_request_android.cpp',
'src/http_file_source.cpp',
'src/asset_file_source.cpp',
'../default/thread.cpp',
'../default/string_stdlib.cpp',

View File

@@ -1,5 +1,4 @@
#include <mbgl/storage/http_context_base.hpp>
#include <mbgl/storage/http_request_base.hpp>
#include <mbgl/storage/http_file_source.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/platform/log.hpp>
@@ -7,26 +6,24 @@
#include <mbgl/util/async_task.hpp>
#include <mbgl/util/util.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/http_header.hpp>
#include <jni/jni.hpp>
#include "attach_env.hpp"
namespace mbgl {
namespace android {
class HTTPContext : public HTTPContextBase {
class HTTPFileSource::Impl {
public:
HTTPRequestBase* createRequest(const Resource&, HTTPRequestBase::Callback) final;
UniqueEnv env { android::AttachEnv() };
android::UniqueEnv env { android::AttachEnv() };
};
class HTTPRequest : public HTTPRequestBase {
class HTTPRequest : public AsyncRequest {
public:
static constexpr auto Name() { return "com/mapbox/mapboxsdk/http/HTTPRequest"; };
HTTPRequest(jni::JNIEnv&, const Resource&, Callback);
void cancel() final;
HTTPRequest(jni::JNIEnv&, const Resource&, FileSource::Callback);
~HTTPRequest();
void onFailure(jni::JNIEnv&, int type, jni::String message);
void onResponse(jni::JNIEnv&, int code,
@@ -38,12 +35,16 @@ public:
jni::UniqueObject<HTTPRequest> javaRequest;
private:
void finish();
Resource resource;
FileSource::Callback callback;
Response response;
std::unique_ptr<Response> response;
const std::shared_ptr<const Response> existingResponse;
util::AsyncTask async;
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;
@@ -52,6 +53,8 @@ private:
jni::Class<HTTPRequest> HTTPRequest::javaClass;
namespace android {
void RegisterNativeHTTPRequest(jni::JNIEnv& env) {
HTTPRequest::javaClass = *jni::Class<HTTPRequest>::Find(env).NewGlobalRef(env).release();
@@ -62,15 +65,11 @@ void RegisterNativeHTTPRequest(jni::JNIEnv& env) {
METHOD(&HTTPRequest::onResponse, "nativeOnResponse"));
}
// -------------------------------------------------------------------------------------------------
} // namespace android
HTTPRequestBase* HTTPContext::createRequest(const Resource& resource, HTTPRequestBase::Callback callback) {
return new HTTPRequest(*env, resource, callback);
}
HTTPRequest::HTTPRequest(jni::JNIEnv& env, const Resource& resource_, Callback callback_)
: HTTPRequestBase(resource_, callback_),
async([this] { finish(); }) {
HTTPRequest::HTTPRequest(jni::JNIEnv& env, const Resource& resource_, FileSource::Callback callback_)
: resource(resource_),
callback(callback_) {
std::string etagStr;
std::string modifiedStr;
@@ -93,63 +92,53 @@ HTTPRequest::HTTPRequest(jni::JNIEnv& env, const Resource& resource_, Callback c
jni::Make<jni::String>(env, modifiedStr)).NewGlobalRef(env);
}
void HTTPRequest::cancel() {
UniqueEnv env = android::AttachEnv();
HTTPRequest::~HTTPRequest() {
android::UniqueEnv env = android::AttachEnv();
static auto cancel = javaClass.GetMethod<void ()>(*env, "cancel");
javaRequest->Call(*env, cancel);
delete this;
}
void HTTPRequest::finish() {
assert(response);
notify(*response);
delete this;
}
void HTTPRequest::onResponse(jni::JNIEnv& env, int code,
jni::String etag, jni::String modified, jni::String cacheControl,
jni::String expires, jni::Array<jni::jbyte> body) {
response = std::make_unique<Response>();
using Error = Response::Error;
if (etag) {
response->etag = jni::Make<std::string>(env, etag);
response.etag = jni::Make<std::string>(env, etag);
}
if (modified) {
response->modified = util::parseTimePoint(jni::Make<std::string>(env, modified).c_str());
response.modified = util::parseTimePoint(jni::Make<std::string>(env, modified).c_str());
}
if (cacheControl) {
response->expires = parseCacheControl(jni::Make<std::string>(env, cacheControl).c_str());
response.expires = http::CacheControl::parse(jni::Make<std::string>(env, cacheControl).c_str()).toTimePoint();
}
if (expires) {
response->expires = util::parseTimePoint(jni::Make<std::string>(env, expires).c_str());
response.expires = util::parseTimePoint(jni::Make<std::string>(env, expires).c_str());
}
if (code == 200) {
if (body) {
auto data = std::make_shared<std::string>(body.Length(env), char());
jni::GetArrayRegion(env, *body, 0, data->size(), reinterpret_cast<jbyte*>(&(*data)[0]));
response->data = data;
response.data = data;
} else {
response->data = std::make_shared<std::string>();
response.data = std::make_shared<std::string>();
}
} else if (code == 204 || (code == 404 && resource.kind == Resource::Kind::Tile)) {
response->noContent = true;
response.noContent = true;
} else if (code == 304) {
response->notModified = true;
response.notModified = true;
} else if (code == 404) {
response->error = std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404");
response.error = std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404");
} else if (code >= 500 && code < 600) {
response->error = std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } + std::to_string(code));
response.error = std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } + std::to_string(code));
} else {
response->error = std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } + std::to_string(code));
response.error = std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } + std::to_string(code));
}
async.send();
@@ -158,30 +147,33 @@ void HTTPRequest::onResponse(jni::JNIEnv& env, int code,
void HTTPRequest::onFailure(jni::JNIEnv& env, int type, jni::String message) {
std::string messageStr = jni::Make<std::string>(env, message);
response = std::make_unique<Response>();
using Error = Response::Error;
switch (type) {
case connectionError:
response->error = std::make_unique<Error>(Error::Reason::Connection, messageStr);
response.error = std::make_unique<Error>(Error::Reason::Connection, messageStr);
break;
case temporaryError:
response->error = std::make_unique<Error>(Error::Reason::Server, messageStr);
response.error = std::make_unique<Error>(Error::Reason::Server, messageStr);
break;
default:
response->error = std::make_unique<Error>(Error::Reason::Other, messageStr);
response.error = std::make_unique<Error>(Error::Reason::Other, messageStr);
}
async.send();
}
} // namespace android
std::unique_ptr<HTTPContextBase> HTTPContextBase::createContext() {
return std::make_unique<android::HTTPContext>();
HTTPFileSource::HTTPFileSource()
: impl(std::make_unique<Impl>()) {
}
uint32_t HTTPContextBase::maximumConcurrentRequests() {
HTTPFileSource::~HTTPFileSource() = default;
std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource, Callback callback) {
return std::make_unique<HTTPRequest>(*impl->env, resource, callback);
}
uint32_t HTTPFileSource::maximumConcurrentRequests() {
return 20;
}

View File

@@ -0,0 +1,228 @@
#include <mbgl/storage/http_file_source.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/util/http_header.hpp>
#include <mbgl/util/async_task.hpp>
#import <Foundation/Foundation.h>
#include <mutex>
namespace mbgl {
// Data that is shared between the requesting thread and the thread running the completion handler.
class HTTPRequestShared {
public:
HTTPRequestShared(Response& response_, util::AsyncTask& async_)
: response(response_),
async(async_) {
}
void notify(const Response& response_) {
std::lock_guard<std::mutex> lock(mutex);
if (!cancelled) {
response = response_;
async.send();
}
}
void cancel() {
std::lock_guard<std::mutex> lock(mutex);
cancelled = true;
}
private:
std::mutex mutex;
bool cancelled = false;
Response& response;
util::AsyncTask& async;
};
class HTTPRequest : public AsyncRequest {
public:
HTTPRequest(FileSource::Callback callback_)
: shared(std::make_shared<HTTPRequestShared>(response, async)),
callback(callback_) {
}
~HTTPRequest() override {
shared->cancel();
if (task) {
[task cancel];
}
}
std::shared_ptr<HTTPRequestShared> shared;
NSURLSessionDataTask* task = nil;
private:
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_);
} };
};
class HTTPFileSource::Impl {
public:
Impl() {
@autoreleasepool {
NSURLSessionConfiguration* sessionConfig =
[NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForResource = 30;
sessionConfig.HTTPMaximumConnectionsPerHost = 8;
sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
sessionConfig.URLCache = nil;
session = [NSURLSession sessionWithConfiguration:sessionConfig];
// Write user agent string
userAgent = @"MapboxGL";
accountType = [[NSUserDefaults standardUserDefaults] integerForKey:@"MGLMapboxAccountType"];
}
}
NSURLSession* session = nil;
NSString* userAgent = nil;
NSInteger accountType = 0;
};
HTTPFileSource::HTTPFileSource()
: impl(std::make_unique<Impl>()) {
}
HTTPFileSource::~HTTPFileSource() = default;
uint32_t HTTPFileSource::maximumConcurrentRequests() {
return 20;
}
std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource, Callback callback) {
auto request = std::make_unique<HTTPRequest>(callback);
auto shared = request->shared; // Explicit copy so that it also gets copied into the completion handler block below.
@autoreleasepool {
NSURL* url = [NSURL URLWithString:@(resource.url.c_str())];
if (impl->accountType == 0 &&
([url.host isEqualToString:@"mapbox.com"] || [url.host hasSuffix:@".mapbox.com"])) {
NSString* absoluteString = [url.absoluteString
stringByAppendingFormat:(url.query ? @"&%@" : @"?%@"), @"events=true"];
url = [NSURL URLWithString:absoluteString];
}
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url];
if (resource.priorEtag) {
[req addValue:@(resource.priorEtag->c_str())
forHTTPHeaderField:@"If-None-Match"];
} else if (resource.priorModified) {
[req addValue:@(util::rfc1123(*resource.priorModified).c_str())
forHTTPHeaderField:@"If-Modified-Since"];
}
[req addValue:impl->userAgent forHTTPHeaderField:@"User-Agent"];
request->task = [impl->session
dataTaskWithRequest:req
completionHandler:^(NSData* data, NSURLResponse* res, NSError* error) {
if (error && [error code] == NSURLErrorCancelled) {
return;
}
Response response;
using Error = Response::Error;
if (error) {
if (data) {
response.data =
std::make_shared<std::string>((const char*)[data bytes], [data length]);
}
switch ([error code]) {
case NSURLErrorBadServerResponse: // 5xx errors
response.error = std::make_unique<Error>(
Error::Reason::Server, [[error localizedDescription] UTF8String]);
break;
case NSURLErrorNetworkConnectionLost:
case NSURLErrorCannotFindHost:
case NSURLErrorCannotConnectToHost:
case NSURLErrorDNSLookupFailed:
case NSURLErrorNotConnectedToInternet:
case NSURLErrorInternationalRoamingOff:
case NSURLErrorCallIsActive:
case NSURLErrorDataNotAllowed:
case NSURLErrorTimedOut:
response.error = std::make_unique<Error>(
Error::Reason::Connection, [[error localizedDescription] UTF8String]);
break;
default:
response.error = std::make_unique<Error>(
Error::Reason::Other, [[error localizedDescription] UTF8String]);
break;
}
} else if ([res isKindOfClass:[NSHTTPURLResponse class]]) {
const long responseCode = [(NSHTTPURLResponse *)res statusCode];
NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields];
NSString *cache_control = [headers objectForKey:@"Cache-Control"];
if (cache_control) {
response.expires = http::CacheControl::parse([cache_control UTF8String]).toTimePoint();
}
NSString *expires = [headers objectForKey:@"Expires"];
if (expires) {
response.expires = util::parseTimePoint([expires UTF8String]);
}
NSString *last_modified = [headers objectForKey:@"Last-Modified"];
if (last_modified) {
response.modified = util::parseTimePoint([last_modified UTF8String]);
}
NSString *etag = [headers objectForKey:@"ETag"];
if (etag) {
response.etag = std::string([etag UTF8String]);
}
if (responseCode == 200) {
response.data = std::make_shared<std::string>((const char *)[data bytes], [data length]);
} else if (responseCode == 204 || (responseCode == 404 && resource.kind == Resource::Kind::Tile)) {
response.noContent = true;
} else if (responseCode == 304) {
response.notModified = true;
} else if (responseCode == 404) {
response.error =
std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404");
} else if (responseCode >= 500 && responseCode < 600) {
response.error =
std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } +
std::to_string(responseCode));
} else {
response.error =
std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } +
std::to_string(responseCode));
}
} else {
// This should never happen.
response.error = std::make_unique<Error>(Error::Reason::Other,
"Response class is not NSHTTPURLResponse");
}
shared->notify(response);
}];
[request->task resume];
}
return std::move(request);
}
}

View File

@@ -1,243 +0,0 @@
#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/async_task.hpp>
#include <mbgl/util/run_loop.hpp>
#import <Foundation/Foundation.h>
#include <map>
#include <cassert>
#include <mutex>
namespace mbgl {
class HTTPNSURLContext;
class HTTPNSURLRequest : public HTTPRequestBase {
public:
HTTPNSURLRequest(HTTPNSURLContext*, Resource, Callback);
void cancel() final;
private:
static std::unique_ptr<Response> handleResult(NSData *data, NSURLResponse *res, NSError *error, Resource);
void handleResponse();
HTTPNSURLContext *context = nullptr;
std::shared_ptr<std::pair<bool, std::mutex>> cancelled;
NSURLSessionDataTask *task = nullptr;
std::unique_ptr<Response> response;
util::AsyncTask async;
};
// -------------------------------------------------------------------------------------------------
class HTTPNSURLContext : public HTTPContextBase {
public:
HTTPNSURLContext();
HTTPRequestBase* createRequest(const Resource&, HTTPRequestBase::Callback) final;
NSURLSession *session = nil;
NSString *userAgent = nil;
NSInteger accountType = 0;
};
HTTPNSURLContext::HTTPNSURLContext() {
@autoreleasepool {
NSURLSessionConfiguration* sessionConfig =
[NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForResource = 30;
sessionConfig.HTTPMaximumConnectionsPerHost = 8;
sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
sessionConfig.URLCache = nil;
session = [NSURLSession sessionWithConfiguration:sessionConfig];
// Write user agent string
userAgent = @"MapboxGL";
accountType = [[NSUserDefaults standardUserDefaults] integerForKey:@"MGLMapboxAccountType"];
}
}
HTTPRequestBase* HTTPNSURLContext::createRequest(const Resource& resource, HTTPRequestBase::Callback callback) {
return new HTTPNSURLRequest(this, resource, callback);
}
// -------------------------------------------------------------------------------------------------
HTTPNSURLRequest::HTTPNSURLRequest(HTTPNSURLContext* context_,
Resource resource_,
Callback callback_)
: HTTPRequestBase(resource_, callback_),
context(context_),
async([this] { handleResponse(); }) {
// Ensure that a stack-allocated std::shared_ptr gets copied into the Objective-C
// block used as the completion handler below. Objective-C will implicitly copy captured
// stack variables. For member variable access it will implicitly copy the this pointer.
// That wouldn't work here because we need the block to have its own shared_ptr.
auto cancelled_ = cancelled = std::make_shared<std::pair<bool, std::mutex>>();
cancelled->first = false;
@autoreleasepool {
NSURL* url = [NSURL URLWithString:@(resource.url.c_str())];
if (context->accountType == 0 &&
([url.host isEqualToString:@"mapbox.com"] || [url.host hasSuffix:@".mapbox.com"])) {
NSString* absoluteString = [url.absoluteString
stringByAppendingFormat:(url.query ? @"&%@" : @"?%@"), @"events=true"];
url = [NSURL URLWithString:absoluteString];
}
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url];
if (resource.priorEtag) {
[req addValue:@(resource.priorEtag->c_str())
forHTTPHeaderField:@"If-None-Match"];
} else if (resource.priorModified) {
[req addValue:@(util::rfc1123(*resource.priorModified).c_str())
forHTTPHeaderField:@"If-Modified-Since"];
}
[req addValue:context->userAgent forHTTPHeaderField:@"User-Agent"];
task = [context->session
dataTaskWithRequest:req
completionHandler:^(NSData* data, NSURLResponse* res, NSError* error) {
std::unique_ptr<Response> response_ = HTTPNSURLRequest::handleResult(data, res, error, resource_);
std::lock_guard<std::mutex> lock(cancelled_->second);
if (!cancelled_->first) {
response = std::move(response_);
async.send();
}
}];
[task resume];
}
}
void HTTPNSURLRequest::handleResponse() {
assert(response);
notify(*response);
delete this;
}
void HTTPNSURLRequest::cancel() {
[task cancel];
task = nil;
{
std::lock_guard<std::mutex> lock(cancelled->second);
cancelled->first = true;
}
// The lock is in place to enforce that `async` is not accessed if the request has been
// cancelled. Therefore it's not necessary to hold the lock beyond setting cancelled to
// true, and in fact it's unsafe to so: if this is the last remaining shared reference,
// `delete this` will destroy the mutex. If the lock was held, it would then be orphaned.
delete this;
}
std::unique_ptr<Response> HTTPNSURLRequest::handleResult(NSData *data, NSURLResponse *res, NSError *error, Resource resource) {
std::unique_ptr<Response> response = std::make_unique<Response>();
using Error = Response::Error;
if (error) {
if ([error code] == NSURLErrorCancelled) {
response.reset();
} else {
if (data) {
response->data =
std::make_shared<std::string>((const char*)[data bytes], [data length]);
}
switch ([error code]) {
case NSURLErrorBadServerResponse: // 5xx errors
response->error = std::make_unique<Error>(
Error::Reason::Server, [[error localizedDescription] UTF8String]);
break;
case NSURLErrorNetworkConnectionLost:
case NSURLErrorCannotFindHost:
case NSURLErrorCannotConnectToHost:
case NSURLErrorDNSLookupFailed:
case NSURLErrorNotConnectedToInternet:
case NSURLErrorInternationalRoamingOff:
case NSURLErrorCallIsActive:
case NSURLErrorDataNotAllowed:
case NSURLErrorTimedOut:
response->error = std::make_unique<Error>(
Error::Reason::Connection, [[error localizedDescription] UTF8String]);
break;
default:
response->error = std::make_unique<Error>(
Error::Reason::Other, [[error localizedDescription] UTF8String]);
break;
}
}
} else if ([res isKindOfClass:[NSHTTPURLResponse class]]) {
const long responseCode = [(NSHTTPURLResponse *)res statusCode];
NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields];
NSString *cache_control = [headers objectForKey:@"Cache-Control"];
if (cache_control) {
response->expires = parseCacheControl([cache_control UTF8String]);
}
NSString *expires = [headers objectForKey:@"Expires"];
if (expires) {
response->expires = util::parseTimePoint([expires UTF8String]);
}
NSString *last_modified = [headers objectForKey:@"Last-Modified"];
if (last_modified) {
response->modified = util::parseTimePoint([last_modified UTF8String]);
}
NSString *etag = [headers objectForKey:@"ETag"];
if (etag) {
response->etag = std::string([etag UTF8String]);
}
if (responseCode == 200) {
response->data = std::make_shared<std::string>((const char *)[data bytes], [data length]);
} else if (responseCode == 204 || (responseCode == 404 && resource.kind == Resource::Kind::Tile)) {
response->noContent = true;
} else if (responseCode == 304) {
response->notModified = true;
} else if (responseCode == 404) {
response->error =
std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404");
} else if (responseCode >= 500 && responseCode < 600) {
response->error =
std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } +
std::to_string(responseCode));
} else {
response->error =
std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } +
std::to_string(responseCode));
}
} else {
// This should never happen.
response->error = std::make_unique<Error>(Error::Reason::Other,
"Response class is not NSHTTPURLResponse");
}
return response;
}
std::unique_ptr<HTTPContextBase> HTTPContextBase::createContext() {
return std::make_unique<HTTPNSURLContext>();
}
uint32_t HTTPContextBase::maximumConcurrentRequests() {
return 20;
}
}

View File

@@ -1,5 +1,4 @@
#include <mbgl/storage/http_context_base.hpp>
#include <mbgl/storage/http_request_base.hpp>
#include <mbgl/storage/http_file_source.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/platform/log.hpp>
@@ -9,28 +8,23 @@
#include <mbgl/util/string.hpp>
#include <mbgl/util/timer.hpp>
#include <mbgl/util/chrono.hpp>
#include <mbgl/util/http_header.hpp>
#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>
#include <cstring>
#include <cstdio>
void handleError(CURLMcode code) {
static void handleError(CURLMcode code) {
if (code != CURLM_OK) {
throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(code));
}
}
void handleError(CURLcode code) {
static void handleError(CURLcode code) {
if (code != CURLE_OK) {
throw std::runtime_error(std::string("CURL easy error: ") + curl_easy_strerror(code));
}
@@ -38,20 +32,14 @@ void handleError(CURLcode code) {
namespace mbgl {
class HTTPCURLRequest;
class HTTPCURLContext : public HTTPContextBase {
MBGL_STORE_THREAD(tid)
class HTTPFileSource::Impl {
public:
HTTPCURLContext();
~HTTPCURLContext();
HTTPRequestBase* createRequest(const Resource&, HTTPRequestBase::Callback) final;
Impl();
~Impl();
static int handleSocket(CURL *handle, curl_socket_t s, int action, void *userp, void *socketp);
static int startTimeout(CURLM *multi, long timeout_ms, void *userp);
static void onTimeout(HTTPCURLContext *context);
static void onTimeout(HTTPFileSource::Impl *context);
void perform(curl_socket_t s, util::RunLoop::Event event);
CURL *getHandle();
@@ -73,14 +61,10 @@ public:
std::queue<CURL *> handles;
};
class HTTPCURLRequest : public HTTPRequestBase {
MBGL_STORE_THREAD(tid)
class HTTPRequest : public AsyncRequest {
public:
HTTPCURLRequest(HTTPCURLContext*, const Resource&, Callback);
~HTTPCURLRequest();
void cancel() final;
HTTPRequest(HTTPFileSource::Impl*, const Resource&, FileSource::Callback);
~HTTPRequest();
void handleResult(CURLcode code);
@@ -88,24 +72,21 @@ private:
static size_t headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp);
static size_t writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp);
HTTPCURLContext *context = nullptr;
HTTPFileSource::Impl* context = nullptr;
Resource resource;
FileSource::Callback callback;
// Will store the current response.
std::shared_ptr<std::string> data;
std::unique_ptr<Response> response;
// In case of revalidation requests, this will store the old response.
const std::shared_ptr<const Response> existingResponse;
CURL *handle = nullptr;
curl_slist *headers = nullptr;
char error[CURL_ERROR_SIZE];
char error[CURL_ERROR_SIZE] = { 0 };
};
// -------------------------------------------------------------------------------------------------
HTTPCURLContext::HTTPCURLContext() {
HTTPFileSource::Impl::Impl() {
if (curl_global_init(CURL_GLOBAL_ALL)) {
throw std::runtime_error("Could not init cURL");
}
@@ -119,7 +100,7 @@ HTTPCURLContext::HTTPCURLContext() {
handleError(curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this));
}
HTTPCURLContext::~HTTPCURLContext() {
HTTPFileSource::Impl::~Impl() {
while (!handles.empty()) {
curl_easy_cleanup(handles.front());
handles.pop();
@@ -134,11 +115,7 @@ HTTPCURLContext::~HTTPCURLContext() {
timeout.stop();
}
HTTPRequestBase* HTTPCURLContext::createRequest(const Resource& resource, HTTPRequestBase::Callback callback) {
return new HTTPCURLRequest(this, resource, callback);
}
CURL *HTTPCURLContext::getHandle() {
CURL *HTTPFileSource::Impl::getHandle() {
if (!handles.empty()) {
auto handle = handles.front();
handles.pop();
@@ -148,20 +125,19 @@ CURL *HTTPCURLContext::getHandle() {
}
}
void HTTPCURLContext::returnHandle(CURL *handle) {
void HTTPFileSource::Impl::returnHandle(CURL *handle) {
curl_easy_reset(handle);
handles.push(handle);
}
void HTTPCURLContext::checkMultiInfo() {
MBGL_VERIFY_THREAD(tid);
void HTTPFileSource::Impl::checkMultiInfo() {
CURLMsg *message = nullptr;
int pending = 0;
while ((message = curl_multi_info_read(multi, &pending))) {
switch (message->msg) {
case CURLMSG_DONE: {
HTTPCURLRequest *baton = nullptr;
HTTPRequest *baton = nullptr;
curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton);
assert(baton);
baton->handleResult(message->data.result);
@@ -174,9 +150,7 @@ void HTTPCURLContext::checkMultiInfo() {
}
}
void HTTPCURLContext::perform(curl_socket_t s, util::RunLoop::Event events) {
MBGL_VERIFY_THREAD(tid);
void HTTPFileSource::Impl::perform(curl_socket_t s, util::RunLoop::Event events) {
int flags = 0;
if (events == util::RunLoop::Event::Read) {
@@ -192,23 +166,22 @@ void HTTPCURLContext::perform(curl_socket_t s, util::RunLoop::Event events) {
checkMultiInfo();
}
int HTTPCURLContext::handleSocket(CURL * /* handle */, curl_socket_t s, int action, void *userp,
int HTTPFileSource::Impl::handleSocket(CURL * /* handle */, curl_socket_t s, int action, void *userp,
void * /* socketp */) {
assert(userp);
auto context = reinterpret_cast<HTTPCURLContext *>(userp);
MBGL_VERIFY_THREAD(context->tid);
auto context = reinterpret_cast<Impl *>(userp);
switch (action) {
case CURL_POLL_IN: {
using namespace std::placeholders;
util::RunLoop::Get()->addWatch(s, util::RunLoop::Event::Read,
std::bind(&HTTPCURLContext::perform, context, _1, _2));
std::bind(&Impl::perform, context, _1, _2));
break;
}
case CURL_POLL_OUT: {
using namespace std::placeholders;
util::RunLoop::Get()->addWatch(s, util::RunLoop::Event::Write,
std::bind(&HTTPCURLContext::perform, context, _1, _2));
std::bind(&Impl::perform, context, _1, _2));
break;
}
case CURL_POLL_REMOVE:
@@ -221,8 +194,7 @@ int HTTPCURLContext::handleSocket(CURL * /* handle */, curl_socket_t s, int acti
return 0;
}
void HTTPCURLContext::onTimeout(HTTPCURLContext *context) {
MBGL_VERIFY_THREAD(context->tid);
void HTTPFileSource::Impl::onTimeout(Impl *context) {
int running_handles;
CURLMcode error = curl_multi_socket_action(context->multi, CURL_SOCKET_TIMEOUT, 0, &running_handles);
if (error != CURLM_OK) {
@@ -231,127 +203,27 @@ void HTTPCURLContext::onTimeout(HTTPCURLContext *context) {
context->checkMultiInfo();
}
int HTTPCURLContext::startTimeout(CURLM * /* multi */, long timeout_ms, void *userp) {
int HTTPFileSource::Impl::startTimeout(CURLM * /* multi */, long timeout_ms, void *userp) {
assert(userp);
auto context = reinterpret_cast<HTTPCURLContext *>(userp);
MBGL_VERIFY_THREAD(context->tid);
auto context = reinterpret_cast<Impl *>(userp);
if (timeout_ms < 0) {
// A timeout of 0 ms means that the timer will invoked in the next loop iteration.
timeout_ms = 0;
}
context->timeout.stop();
context->timeout.start(mbgl::Milliseconds(timeout_ms), Duration::zero(),
std::bind(&HTTPCURLContext::onTimeout, context));
std::bind(&Impl::onTimeout, context));
return 0;
}
// -------------------------------------------------------------------------------------------------
#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_)
: HTTPRequestBase(resource_, callback_),
context(context_),
HTTPRequest::HTTPRequest(HTTPFileSource::Impl* context_, const Resource& resource_, FileSource::Callback callback_)
: context(context_),
resource(resource_),
callback(callback_),
handle(context->getHandle()) {
// Zero out the error buffer.
memset(error, 0, sizeof(error));
// If there's already a response, set the correct etags/modified headers to make sure we are
// getting a 304 response if possible. This avoids redownloading unchanged data.
@@ -370,12 +242,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));
@@ -394,9 +261,7 @@ HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& reso
handleError(curl_multi_add_handle(context->multi, handle));
}
HTTPCURLRequest::~HTTPCURLRequest() {
MBGL_VERIFY_THREAD(tid);
HTTPRequest::~HTTPRequest() {
handleError(curl_multi_remove_handle(context->multi, handle));
context->returnHandle(handle);
handle = nullptr;
@@ -407,16 +272,11 @@ HTTPCURLRequest::~HTTPCURLRequest() {
}
}
void HTTPCURLRequest::cancel() {
delete this;
}
// This function is called when we have new data for a request. We just append it to the string
// containing the previous data.
size_t HTTPCURLRequest::writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp) {
size_t HTTPRequest::writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp) {
assert(userp);
auto impl = reinterpret_cast<HTTPCURLRequest *>(userp);
MBGL_VERIFY_THREAD(impl->tid);
auto impl = reinterpret_cast<HTTPRequest *>(userp);
if (!impl->data) {
impl->data = std::make_shared<std::string>();
@@ -442,10 +302,9 @@ size_t headerMatches(const char *const header, const char *const buffer, const s
return i == headerLength ? i : std::string::npos;
}
size_t HTTPCURLRequest::headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp) {
size_t HTTPRequest::headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp) {
assert(userp);
auto baton = reinterpret_cast<HTTPCURLRequest *>(userp);
MBGL_VERIFY_THREAD(baton->tid);
auto baton = reinterpret_cast<HTTPRequest *>(userp);
if (!baton->response) {
baton->response = std::make_unique<Response>();
@@ -462,7 +321,7 @@ size_t HTTPCURLRequest::headerCallback(char *const buffer, const size_t size, co
baton->response->etag = std::string(buffer + begin, length - begin - 2); // remove \r\n
} else if ((begin = headerMatches("cache-control: ", buffer, length)) != std::string::npos) {
const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
baton->response->expires = parseCacheControl(value.c_str());
baton->response->expires = http::CacheControl::parse(value.c_str()).toTimePoint();
} else if ((begin = headerMatches("expires: ", buffer, length)) != std::string::npos) {
const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
baton->response->expires = SystemClock::from_time_t(curl_getdate(value.c_str(), nullptr));
@@ -471,16 +330,7 @@ size_t HTTPCURLRequest::headerCallback(char *const buffer, const size_t size, co
return length;
}
void HTTPCURLRequest::handleResult(CURLcode code) {
MBGL_VERIFY_THREAD(tid);
if (cancelled) {
// In this case, it doesn't make sense to even process the response even further since
// the request was canceled anyway.
delete this;
return;
}
void HTTPRequest::handleResult(CURLcode code) {
// Make sure a response object exists in case we haven't got any headers or content.
if (!response) {
response = std::make_unique<Response>();
@@ -533,16 +383,23 @@ void HTTPCURLRequest::handleResult(CURLcode code) {
}
}
// Actually return the response.
notify(*response);
delete this;
// Calling `callback` may result in deleting `this`. Copy data to temporaries first.
auto callback_ = callback;
auto response_ = *response;
callback_(response_);
}
std::unique_ptr<HTTPContextBase> HTTPContextBase::createContext() {
return std::make_unique<HTTPCURLContext>();
HTTPFileSource::HTTPFileSource()
: impl(std::make_unique<Impl>()) {
}
uint32_t HTTPContextBase::maximumConcurrentRequests() {
HTTPFileSource::~HTTPFileSource() = default;
std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource, Callback callback) {
return std::make_unique<HTTPRequest>(impl.get(), resource, callback);
}
uint32_t HTTPFileSource::maximumConcurrentRequests() {
return 20;
}

View File

@@ -1,5 +1,5 @@
#include <mbgl/storage/online_file_source.hpp>
#include <mbgl/storage/http_context_base.hpp>
#include <mbgl/storage/http_file_source.hpp>
#include <mbgl/storage/network_status.hpp>
#include <mbgl/storage/response.hpp>
@@ -35,7 +35,7 @@ public:
OnlineFileSource::Impl& impl;
Resource resource;
HTTPRequestBase* request = nullptr;
std::unique_ptr<AsyncRequest> request;
util::Timer timer;
Callback callback;
@@ -82,7 +82,7 @@ public:
assert(activeRequests.find(request) == activeRequests.end());
assert(!request->request);
if (activeRequests.size() >= HTTPContextBase::maximumConcurrentRequests()) {
if (activeRequests.size() >= HTTPFileSource::maximumConcurrentRequests()) {
queueRequest(request);
} else {
activateRequest(request);
@@ -96,10 +96,10 @@ public:
void activateRequest(OnlineFileRequest* request) {
activeRequests.insert(request);
request->request = httpContext->createRequest(request->resource, [=] (Response response) {
request->request = httpFileSource.request(request->resource, [=] (Response response) {
activeRequests.erase(request);
activatePendingRequest();
request->request = nullptr;
request->request.reset();
request->completed(response);
});
}
@@ -140,7 +140,7 @@ private:
std::unordered_map<OnlineFileRequest*, std::list<OnlineFileRequest*>::iterator> pendingRequestsMap;
std::unordered_set<OnlineFileRequest*> activeRequests;
const std::unique_ptr<HTTPContextBase> httpContext { HTTPContextBase::createContext() };
HTTPFileSource httpFileSource;
util::AsyncTask reachability { std::bind(&Impl::networkIsReachableAgain, this) };
};
@@ -198,9 +198,6 @@ OnlineFileRequest::OnlineFileRequest(const Resource& resource_, Callback callbac
OnlineFileRequest::~OnlineFileRequest() {
impl.remove(this);
if (request) {
request->cancel();
}
}
static Duration errorRetryTimeout(Response::Error::Reason failedRequestReason, uint32_t failedRequests) {

View File

@@ -98,7 +98,7 @@
'../default/mbgl/storage/offline_download.cpp',
'../default/sqlite3.hpp',
'../default/sqlite3.cpp',
'../darwin/src/http_request_nsurl.mm',
'../darwin/src/http_file_source.mm',
'../darwin/src/log_nslog.mm',
'../darwin/src/string_nsstring.mm',
'../darwin/src/image.mm',

View File

@@ -57,7 +57,7 @@
'../default/png_reader.cpp',
'../default/jpeg_reader.cpp',
'../default/asset_file_source.cpp',
'../default/http_request_curl.cpp',
'../default/http_file_source.cpp',
'../default/default_file_source.cpp',
'../default/online_file_source.cpp',
'../default/mbgl/storage/offline.hpp',

View File

@@ -59,7 +59,7 @@
'../default/mbgl/storage/offline_download.cpp',
'../default/sqlite3.hpp',
'../default/sqlite3.cpp',
'../darwin/src/http_request_nsurl.mm',
'../darwin/src/http_file_source.mm',
'../darwin/src/log_nslog.mm',
'../darwin/src/string_nsstring.mm',
'../darwin/src/image.mm',

View File

@@ -1,5 +0,0 @@
#include <mbgl/storage/http_context_base.hpp>
namespace mbgl {
} // namespace mbgl

View File

@@ -1,22 +0,0 @@
#ifndef MBGL_STORAGE_HTTP_CONTEXT_BASE
#define MBGL_STORAGE_HTTP_CONTEXT_BASE
#include <mbgl/storage/http_request_base.hpp>
#include <mbgl/storage/network_status.hpp>
#include <set>
namespace mbgl {
class HTTPContextBase {
public:
static std::unique_ptr<HTTPContextBase> createContext();
static uint32_t maximumConcurrentRequests();
virtual ~HTTPContextBase() = default;
virtual HTTPRequestBase* createRequest(const Resource&, HTTPRequestBase::Callback) = 0;
};
} // namespace mbgl
#endif // MBGL_STORAGE_HTTP_CONTEXT_BASE

View File

@@ -0,0 +1,25 @@
#ifndef MBGL_STORAGE_HTTP_FILE_SOURCE
#define MBGL_STORAGE_HTTP_FILE_SOURCE
#include <mbgl/storage/file_source.hpp>
namespace mbgl {
class HTTPFileSource : public FileSource {
public:
HTTPFileSource();
~HTTPFileSource() override;
std::unique_ptr<AsyncRequest> request(const Resource&, Callback) override;
static uint32_t maximumConcurrentRequests();
private:
friend class HTTPRequest;
class Impl;
std::unique_ptr<Impl> impl;
};
} // namespace mbgl
#endif // MBGL_STORAGE_HTTP_FILE_SOURCE

View File

@@ -1,23 +0,0 @@
#include <mbgl/storage/http_request_base.hpp>
#include <mbgl/util/http_header.hpp>
#include <mbgl/util/chrono.hpp>
namespace mbgl {
optional<SystemTimePoint> HTTPRequestBase::parseCacheControl(const char *value) {
if (!value) {
return {};
}
const auto cacheControl = http::CacheControl::parse(value);
if (!cacheControl.maxAge) {
return {};
}
// Round trip through time_t to truncate fractional seconds.
return SystemClock::from_time_t(SystemClock::to_time_t(
SystemClock::now() + std::chrono::seconds(*cacheControl.maxAge)));
}
} // namespace mbgl

View File

@@ -1,40 +0,0 @@
#ifndef MBGL_STORAGE_HTTP_REQUEST_BASE
#define MBGL_STORAGE_HTTP_REQUEST_BASE
#include <mbgl/storage/response.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/util/noncopyable.hpp>
#include <mbgl/util/chrono.hpp>
#include <mbgl/util/optional.hpp>
#include <functional>
namespace mbgl {
class Response;
class HTTPRequestBase : private util::noncopyable {
public:
using Callback = std::function<void (Response)>;
HTTPRequestBase(const Resource& resource_, Callback notify_)
: resource(resource_)
, notify(std::move(notify_))
, cancelled(false) {
}
virtual ~HTTPRequestBase() = default;
virtual void cancel() { cancelled = true; };
protected:
static optional<SystemTimePoint> parseCacheControl(const char *value);
Resource resource;
Callback notify;
bool cancelled;
};
} // namespace mbgl
#endif // MBGL_STORAGE_HTTP_REQUEST_BASE

View File

@@ -25,5 +25,13 @@ CacheControl CacheControl::parse(const std::string& value) {
return result;
}
optional<SystemTimePoint> CacheControl::toTimePoint() const {
// Round trip through time_t to truncate fractional seconds.
return maxAge
? SystemClock::from_time_t(SystemClock::to_time_t(
SystemClock::now() + std::chrono::seconds(*maxAge)))
: optional<SystemTimePoint>();
}
} // namespace http
} // namespace mbgl

View File

@@ -2,6 +2,7 @@
#define MBGL_UTIL_HTTP_HEADER
#include <mbgl/util/optional.hpp>
#include <mbgl/util/chrono.hpp>
#include <string>
@@ -14,6 +15,8 @@ public:
optional<uint64_t> maxAge;
bool mustRevalidate = false;
optional<SystemTimePoint> toTimePoint() const;
};
} // namespace http