blob: ecb1f51a8145ae13ccbfbbddae6e4f25a01cfb89 [file] [log] [blame]
#include "region.h"
#include <curl/curl.h>
#include <iostream>
#include <sstream>
#include <string>
// For some reason, the libjsoncpp package installs to /usr/include/jsoncpp/json
// instead of /usr{,/local}/include/json
#include <jsoncpp/json/json.h>
#include "errors.h"
namespace speedtest {
namespace {
bool AddUrl(const Json::Value &url_json, std::vector<http::Url> *urls) {
if (!url_json.isString()) {
return false;
}
http::Url url = http::Url(url_json.asString());
if (!url.ok()) {
return false;
}
urls->push_back(url);
return true;
}
} // namesapce
std::string DescribeRegion(const Region &region) {
if (region.id.empty() && region.name.empty()) {
return region.urls.front().url();
}
if (region.id.empty()) {
return region.name;
}
if (region.name.empty()) {
return region.id;
}
std::stringstream ss;
ss << region.name << " (" << region.id << ")";
return ss.str();
}
RegionResult LoadRegions(RegionOptions options) {
RegionResult result;
result.start_time = SystemTimeMicros();
if (!options.request_factory) {
result.status = Status(StatusCode::INVALID_ARGUMENT,
"request factory not set");
result.end_time = SystemTimeMicros();
return result;
}
if (!options.global) {
if (options.verbose) {
std::cout << "Explicit server list:\n";
for (const auto &url : options.regional_urls) {
std::cout << " " << url.url() << "\n";
}
}
for (const http::Url &url : options.regional_urls) {
Region region;
region.urls.emplace_back(url.url());
result.regions.emplace_back(region);
}
result.status = Status::OK;
result.end_time = SystemTimeMicros();
return result;
}
http::Url config_url(options.global_url);
config_url.set_path("/fiber/config");
if (options.verbose) {
std::cout << "Loading regions from " << config_url.url() << "\n";
}
http::Request::Ptr request = options.request_factory(config_url);
request->set_url(config_url);
request->set_timeout_millis(500);
std::string json;
CURLcode code = request->Get([&](void *data, size_t size){
json.assign(static_cast<const char *>(data), size);
});
if (code != CURLE_OK) {
result.status = Status(StatusCode::INTERNAL, http::ErrorString(code));
} else {
result.status = ParseRegions(json, &result.regions);
}
result.end_time = SystemTimeMicros();
return result;
}
Status ParseRegions(const std::string &json, std::vector<Region> *regions) {
if (!regions) {
return Status(StatusCode::FAILED_PRECONDITION, "Regions is null");
}
Json::Reader reader;
Json::Value root;
if (!reader.parse(json, root, false)) {
return Status(StatusCode::INVALID_ARGUMENT, "Failed to parse regions JSON");
}
if (!root.isMember("regions") || !root["regions"].isArray()) {
return Status(StatusCode::INVALID_ARGUMENT, "no regions element found");
}
for (const auto &it : root["regions"]) {
Region region;
if (!it.isMember("id")) {
return Status(StatusCode::INVALID_ARGUMENT, "Region missing id");
}
if (!it["id"].isString()) {
return Status(StatusCode::INVALID_ARGUMENT, "Region id not a string");
}
region.id = it["id"].asString();
if (it.isMember("name")) {
if (!it["name"].isString()) {
return Status(StatusCode::INVALID_ARGUMENT, "Region name not a string");
}
region.name = it["name"].asString();
}
if (!it.isMember("url")) {
return Status(StatusCode::INVALID_ARGUMENT, "Region URL missing");
}
if (it["url"].isString()) {
if (!AddUrl(it["url"], &region.urls)) {
return Status(StatusCode::INVALID_ARGUMENT,
"Failed to parse region URL");
}
} else if (it["url"].isArray()) {
for (const auto &url_it : it["url"]) {
if (!AddUrl(url_it, &region.urls)) {
return Status(StatusCode::INVALID_ARGUMENT,
"Failed to parse region URL");
}
}
if (region.urls.empty()) {
return Status(StatusCode::INVALID_ARGUMENT, "Region missing URLs");
}
} else {
return Status(StatusCode::INVALID_ARGUMENT,
"Region URL not string or array");
}
regions->emplace_back(region);
}
return Status::OK;
}
} // namespace