/** * * @file ConfigLoader.cc * @author An Tao * * Copyright 2018, An Tao. All rights reserved. * https://github.com/an-tao/drogon * Use of this source code is governed by a MIT license * that can be found in the License file. * * Drogon * */ #include "ConfigLoader.h" #include "HttpAppFrameworkImpl.h" #include #include #include #include #include #include #if !defined(_WIN32) #include #define os_access access #else #include #ifndef __MINGW32__ #define os_access _waccess #define R_OK 04 #define W_OK 02 #else #define os_access access #endif #endif #include #include "ConfigAdapterManager.h" #include using namespace drogon; static bool bytesSize(std::string &sizeStr, size_t &size) { if (sizeStr.empty()) { size = -1; return true; } else { size = 1; switch (sizeStr[sizeStr.length() - 1]) { case 'k': case 'K': size = 1024; sizeStr.resize(sizeStr.length() - 1); break; case 'M': case 'm': size = (1024 * 1024); sizeStr.resize(sizeStr.length() - 1); break; case 'g': case 'G': size = (1024 * 1024 * 1024); sizeStr.resize(sizeStr.length() - 1); break; #if ((ULONG_MAX) != (UINT_MAX)) // 64bit system case 't': case 'T': size = (1024L * 1024L * 1024L * 1024L); sizeStr.resize(sizeStr.length() - 1); break; #endif case '0': case '1': case '2': case '3': case '4': case '5': case '7': case '8': case '9': break; default: return false; break; } std::istringstream iss(sizeStr); size_t tmpSize; iss >> tmpSize; if (iss.fail()) { return false; } if ((size_t(-1) / tmpSize) >= size) size *= tmpSize; else { size = -1; } return true; } } ConfigLoader::ConfigLoader(const std::string &configFile) { if (os_access(drogon::utils::toNativePath(configFile).c_str(), 0) != 0) { throw std::runtime_error("Config file " + configFile + " not found!"); } if (os_access(drogon::utils::toNativePath(configFile).c_str(), R_OK) != 0) { throw std::runtime_error("No permission to read config file " + configFile); } configFile_ = configFile; auto pos = configFile.find_last_of('.'); if (pos == std::string::npos) { throw std::runtime_error("Invalid config file name!"); } auto ext = configFile.substr(pos + 1); std::ifstream infile(drogon::utils::toNativePath(configFile).c_str(), std::ifstream::in); // get the content of the infile std::string content((std::istreambuf_iterator(infile)), std::istreambuf_iterator()); try { configJsonRoot_ = ConfigAdapterManager::instance().getJson(content, std::move(ext)); } catch (std::exception &e) { throw std::runtime_error("Error reading config file " + configFile + ": " + e.what()); } } ConfigLoader::ConfigLoader(const Json::Value &data) : configJsonRoot_(data) { } ConfigLoader::ConfigLoader(Json::Value &&data) : configJsonRoot_(std::move(data)) { } ConfigLoader::~ConfigLoader() { } static void loadLogSetting(const Json::Value &log) { if (!log) return; auto useSpdlog = log.get("use_spdlog", false).asBool(); auto logPath = log.get("log_path", "").asString(); auto baseName = log.get("logfile_base_name", "").asString(); auto logSize = log.get("log_size_limit", 100000000).asUInt64(); auto maxFiles = log.get("max_files", 0).asUInt(); HttpAppFrameworkImpl::instance().setLogPath( logPath, baseName, logSize, maxFiles, useSpdlog); auto logLevel = log.get("log_level", "DEBUG").asString(); if (logLevel == "TRACE") { trantor::Logger::setLogLevel(trantor::Logger::kTrace); } else if (logLevel == "DEBUG") { trantor::Logger::setLogLevel(trantor::Logger::kDebug); } else if (logLevel == "INFO") { trantor::Logger::setLogLevel(trantor::Logger::kInfo); } else if (logLevel == "WARN") { trantor::Logger::setLogLevel(trantor::Logger::kWarn); } auto localTime = log.get("display_local_time", false).asBool(); trantor::Logger::setDisplayLocalTime(localTime); } static void loadControllers(const Json::Value &controllers) { if (!controllers) return; for (auto const &controller : controllers) { auto path = controller.get("path", "").asString(); auto ctrlName = controller.get("controller", "").asString(); if (path == "" || ctrlName == "") continue; std::vector constraints; if (!controller["http_methods"].isNull()) { for (auto const &method : controller["http_methods"]) { auto strMethod = method.asString(); std::transform(strMethod.begin(), strMethod.end(), strMethod.begin(), [](unsigned char c) { return tolower(c); }); if (strMethod == "get") { constraints.push_back(Get); } else if (strMethod == "post") { constraints.push_back(Post); } else if (strMethod == "head") // The branch never work { constraints.push_back(Head); } else if (strMethod == "put") { constraints.push_back(Put); } else if (strMethod == "delete") { constraints.push_back(Delete); } else if (strMethod == "patch") { constraints.push_back(Patch); } } } if (!controller["filters"].isNull()) { for (auto const &filter : controller["filters"]) { constraints.push_back(filter.asString()); } } drogon::app().registerHttpSimpleController(path, ctrlName, constraints); } } static void loadApp(const Json::Value &app) { if (!app) return; // threads number auto threadsNum = app.get("threads_num", 1).asUInt64(); if (threadsNum == 1) { threadsNum = app.get("number_of_threads", 1).asUInt64(); } if (threadsNum == 0) { // set the number to the number of processors. threadsNum = std::thread::hardware_concurrency(); LOG_TRACE << "The number of processors is " << threadsNum; } if (threadsNum < 1) threadsNum = 1; drogon::app().setThreadNum(threadsNum); // session auto enableSession = app.get("enable_session", false).asBool(); if (enableSession) { auto timeout = app.get("session_timeout", 0).asUInt64(); auto sameSite = app.get("session_same_site", "Null").asString(); auto cookieKey = app.get("session_cookie_key", "JSESSIONID").asString(); auto maxAge = app.get("session_max_age", -1).asInt(); drogon::app().enableSession(timeout, Cookie::convertString2SameSite(sameSite), cookieKey, maxAge); } else drogon::app().disableSession(); // document root auto documentRoot = app.get("document_root", "").asString(); if (documentRoot != "") { drogon::app().setDocumentRoot(documentRoot); } if (!app["static_file_headers"].empty()) { if (app["static_file_headers"].isArray()) { std::vector> headers; for (auto &header : app["static_file_headers"]) { headers.emplace_back( std::make_pair(header["name"].asString(), header["value"].asString())); } drogon::app().setStaticFileHeaders(headers); } else { throw std::runtime_error( "The static_file_headers option must be an array"); } } // upload path auto uploadPath = app.get("upload_path", "uploads").asString(); drogon::app().setUploadPath(uploadPath); // file types auto fileTypes = app["file_types"]; if (fileTypes.isArray() && !fileTypes.empty()) { std::vector types; for (auto const &fileType : fileTypes) { types.push_back(fileType.asString()); LOG_TRACE << "file type:" << types.back(); } drogon::app().setFileTypes(types); } // locations if (app.isMember("locations")) { auto &locations = app["locations"]; if (!locations.isArray()) { throw std::runtime_error("The locations option must be an array"); } for (auto &location : locations) { auto uri = location.get("uri_prefix", "").asString(); if (uri.empty()) continue; auto defaultContentType = location.get("default_content_type", "").asString(); auto alias = location.get("alias", "").asString(); auto isCaseSensitive = location.get("is_case_sensitive", false).asBool(); auto allAll = location.get("allow_all", true).asBool(); auto isRecursive = location.get("is_recursive", true).asBool(); if (!location["filters"].isNull()) { if (location["filters"].isArray()) { std::vector filters; for (auto const &filter : location["filters"]) { filters.push_back(filter.asString()); } drogon::app().addALocation(uri, defaultContentType, alias, isCaseSensitive, allAll, isRecursive, filters); } else { throw std::runtime_error("the filters of location '" + uri + "' should be an array"); } } else { drogon::app().addALocation(uri, defaultContentType, alias, isCaseSensitive, allAll, isRecursive); } } } // max connections auto maxConns = app.get("max_connections", 0).asUInt64(); if (maxConns > 0) { drogon::app().setMaxConnectionNum(maxConns); } // max connections per IP auto maxConnsPerIP = app.get("max_connections_per_ip", 0).asUInt64(); if (maxConnsPerIP > 0) { drogon::app().setMaxConnectionNumPerIP(maxConnsPerIP); } #ifndef _WIN32 // dynamic views auto enableDynamicViews = app.get("load_dynamic_views", false).asBool(); if (enableDynamicViews) { auto viewsPaths = app["dynamic_views_path"]; if (viewsPaths.isArray() && viewsPaths.size() > 0) { std::vector paths; for (auto const &viewsPath : viewsPaths) { paths.push_back(viewsPath.asString()); LOG_TRACE << "views path:" << paths.back(); } auto outputPath = app.get("dynamic_views_output_path", "").asString(); drogon::app().enableDynamicViewsLoading(paths, outputPath); } } #endif auto stackLimit = app.get("json_parser_stack_limit", 1000).asUInt64(); drogon::app().setJsonParserStackLimit(stackLimit); auto unicodeEscaping = app.get("enable_unicode_escaping_in_json", true).asBool(); drogon::app().setUnicodeEscapingInJson(unicodeEscaping); auto &precision = app["float_precision_in_json"]; if (!precision.isNull()) { auto precisionLength = precision.get("precision", 0).asUInt64(); auto precisionType = precision.get("precision_type", "significant").asString(); drogon::app().setFloatPrecisionInJson((unsigned int)precisionLength, precisionType); } // log loadLogSetting(app["log"]); // run as daemon auto runAsDaemon = app.get("run_as_daemon", false).asBool(); if (runAsDaemon) { drogon::app().enableRunAsDaemon(); } // handle SIGTERM auto handleSigterm = app.get("handle_sig_term", true).asBool(); if (!handleSigterm) { drogon::app().disableSigtermHandling(); } // relaunch auto relaunch = app.get("relaunch_on_error", false).asBool(); if (relaunch) { drogon::app().enableRelaunchOnError(); } auto useSendfile = app.get("use_sendfile", true).asBool(); drogon::app().enableSendfile(useSendfile); auto useGzip = app.get("use_gzip", true).asBool(); drogon::app().enableGzip(useGzip); auto useBr = app.get("use_brotli", false).asBool(); drogon::app().enableBrotli(useBr); auto staticFilesCacheTime = app.get("static_files_cache_time", 5).asInt(); drogon::app().setStaticFilesCacheTime(staticFilesCacheTime); loadControllers(app["simple_controllers_map"]); // Kick off idle connections auto kickOffTimeout = app.get("idle_connection_timeout", 60).asUInt64(); drogon::app().setIdleConnectionTimeout(kickOffTimeout); auto server = app.get("server_header_field", "").asString(); if (!server.empty()) drogon::app().setServerHeaderField(server); auto sendServerHeader = app.get("enable_server_header", true).asBool(); drogon::app().enableServerHeader(sendServerHeader); auto sendDateHeader = app.get("enable_date_header", true).asBool(); drogon::app().enableDateHeader(sendDateHeader); auto keepaliveReqs = app.get("keepalive_requests", 0).asUInt64(); drogon::app().setKeepaliveRequestsNumber(keepaliveReqs); auto pipeliningReqs = app.get("pipelining_requests", 0).asUInt64(); drogon::app().setPipeliningRequestsNumber(pipeliningReqs); auto useGzipStatic = app.get("gzip_static", true).asBool(); drogon::app().setGzipStatic(useGzipStatic); auto useBrStatic = app.get("br_static", true).asBool(); drogon::app().setBrStatic(useBrStatic); auto maxBodySize = app.get("client_max_body_size", "1M").asString(); size_t size; if (bytesSize(maxBodySize, size)) { drogon::app().setClientMaxBodySize(size); } else { throw std::runtime_error("Error format of client_max_body_size"); } auto maxMemoryBodySize = app.get("client_max_memory_body_size", "64K").asString(); if (bytesSize(maxMemoryBodySize, size)) { drogon::app().setClientMaxMemoryBodySize(size); } else { throw std::runtime_error("Error format of client_max_memory_body_size"); } auto maxWsMsgSize = app.get("client_max_websocket_message_size", "128K").asString(); if (bytesSize(maxWsMsgSize, size)) { drogon::app().setClientMaxWebSocketMessageSize(size); } else { throw std::runtime_error( "Error format of client_max_websocket_message_size"); } drogon::app().enableReusePort(app.get("reuse_port", false).asBool()); drogon::app().setHomePage(app.get("home_page", "index.html").asString()); drogon::app().setImplicitPageEnable( app.get("use_implicit_page", true).asBool()); drogon::app().setImplicitPage( app.get("implicit_page", "index.html").asString()); auto mimes = app["mime"]; if (!mimes.isNull()) { auto names = mimes.getMemberNames(); for (const auto &mime : names) { auto ext = mimes[mime]; std::vector exts; if (ext.isString()) exts.push_back(ext.asString()); else if (ext.isArray()) { for (const auto &extension : ext) exts.push_back(extension.asString()); } for (const auto &extension : exts) drogon::app().registerCustomExtensionMime(extension, mime); } } bool enableCompressedRequests = app.get("enabled_compressed_request", false).asBool(); drogon::app().enableCompressedRequest(enableCompressedRequests); drogon::app().enableRequestStream( app.get("enable_request_stream", false).asBool()); } static void loadDbClients(const Json::Value &dbClients) { if (!dbClients) return; for (auto const &client : dbClients) { auto type = client.get("rdbms", "postgresql").asString(); std::transform(type.begin(), type.end(), type.begin(), [](unsigned char c) { return tolower(c); }); auto host = client.get("host", "127.0.0.1").asString(); unsigned short port = client.get("port", 5432).asUInt(); auto dbname = client.get("dbname", "").asString(); if (dbname.empty() && type != "sqlite3") { throw std::runtime_error( "Please configure dbname in the configuration file"); } auto user = client.get("user", "postgres").asString(); auto password = client.get("passwd", "").asString(); if (password.empty()) { password = client.get("password", "").asString(); } auto connNum = client.get("connection_number", 1).asUInt(); if (connNum == 1) { connNum = client.get("number_of_connections", 1).asUInt(); } auto name = client.get("name", "default").asString(); auto filename = client.get("filename", "").asString(); auto isFast = client.get("is_fast", false).asBool(); auto characterSet = client.get("characterSet", "").asString(); if (characterSet.empty()) { characterSet = client.get("client_encoding", "").asString(); } auto connectOptions = client.get("connect_options", Json::Value()); auto timeout = client.get("timeout", -1.0).asDouble(); auto autoBatch = client.get("auto_batch", false).asBool(); std::unordered_map options; if (connectOptions.isObject() && !connectOptions.empty()) { for (const auto &key : connectOptions.getMemberNames()) { options[key] = connectOptions[key].asString(); } } HttpAppFrameworkImpl::instance().addDbClient(type, host, port, dbname, user, password, connNum, filename, name, isFast, characterSet, timeout, autoBatch, std::move(options)); } } static void loadRedisClients(const Json::Value &redisClients) { if (!redisClients) return; for (auto const &client : redisClients) { std::promise promise; auto future = promise.get_future(); auto host = client.get("host", "127.0.0.1").asString(); trantor::Resolver::newResolver()->resolve( host, [&promise](const trantor::InetAddress &address) { promise.set_value(address.toIp()); }); auto port = client.get("port", 6379).asUInt(); auto username = client.get("username", "").asString(); auto password = client.get("passwd", "").asString(); if (password.empty()) { password = client.get("password", "").asString(); } auto connNum = client.get("connection_number", 1).asUInt(); if (connNum == 1) { connNum = client.get("number_of_connections", 1).asUInt(); } auto name = client.get("name", "default").asString(); auto isFast = client.get("is_fast", false).asBool(); auto timeout = client.get("timeout", -1.0).asDouble(); auto db = client.get("db", 0).asUInt(); auto hostIp = future.get(); drogon::app().createRedisClient(hostIp, port, name, password, connNum, isFast, timeout, db, username); } } static void loadListeners(const Json::Value &listeners) { if (!listeners) return; LOG_TRACE << "Has " << listeners.size() << " listeners"; for (auto const &listener : listeners) { auto addr = listener.get("address", "0.0.0.0").asString(); auto port = (uint16_t)listener.get("port", 0).asUInt(); auto useSSL = listener.get("https", false).asBool(); auto cert = listener.get("cert", "").asString(); auto key = listener.get("key", "").asString(); auto useOldTLS = listener.get("use_old_tls", false).asBool(); std::vector> sslConfCmds; if (listener.isMember("ssl_conf")) { for (const auto &opt : listener["ssl_conf"]) { if (opt.size() == 0 || opt.size() > 2) { LOG_FATAL << "SSL configuration option should be an 1 or " "2-element array"; abort(); } sslConfCmds.emplace_back(opt[0].asString(), opt.get(1, "").asString()); } } LOG_TRACE << "Add listener:" << addr << ":" << port; drogon::app().addListener( addr, port, useSSL, cert, key, useOldTLS, sslConfCmds); } } static void loadSSL(const Json::Value &sslConf) { if (!sslConf) return; auto key = sslConf.get("key", "").asString(); auto cert = sslConf.get("cert", "").asString(); drogon::app().setSSLFiles(cert, key); std::vector> sslConfCmds; if (sslConf.isMember("conf")) { for (const auto &opt : sslConf["conf"]) { if (opt.size() == 0 || opt.size() > 2) { LOG_FATAL << "SSL configuration option should be an 1 or " "2-element array"; abort(); } sslConfCmds.emplace_back(opt[0].asString(), opt.get(1, "").asString()); } } drogon::app().setSSLConfigCommands(sslConfCmds); } void ConfigLoader::load() { // std::cout<