[all] Replace HTTPContextBase/HTTPRequestBase with FileSource
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
228
platform/darwin/src/http_file_source.mm
Normal file
228
platform/darwin/src/http_file_source.mm
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
#include <mbgl/storage/http_context_base.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
} // namespace mbgl
|
||||
@@ -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
|
||||
25
src/mbgl/storage/http_file_source.hpp
Normal file
25
src/mbgl/storage/http_file_source.hpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user