| #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 ®ion) { |
| 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("/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"], ®ion.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, ®ion.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 |