#include "stdafx.h" #include "V8DataModel/HttpService.h" #include "V8DataModel/DataModel.h" #include "v8datamodel/Workspace.h" #include "v8datamodel/Stats.h" #include "V8Xml/WebParser.h" #include "Network/Players.h" #include "Util/Http.h" #include "Util/RobloxGoogleAnalytics.h" DYNAMIC_FASTINTVARIABLE(UserHttpRequestsPerMinuteLimit, 500) namespace { static inline void sendHttpServiceStats(int placeId) { RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "HttpService", RBX::StringConverter::convertToString(placeId).c_str()); } } namespace RBX { const char* const sHttpService = "HttpService"; REFLECTION_BEGIN(); static Reflection::BoundYieldFuncDesc userHttpGetAsyncFunction(&HttpService::userHttpGetAsync, "GetAsync", "url", "nocache", false, Security::None); static Reflection::BoundYieldFuncDesc userHttpPostAsyncFunction(&HttpService::userHttpPostAsync, "PostAsync", "url", "data", "content_type", HttpService::APPLICATION_JSON, "compress", false, Security::None); static Reflection::BoundFuncDesc decodeJSON(&HttpService::decodeJSON, "JSONDecode", "input", Security::None); static Reflection::BoundFuncDesc encodeJSON(&HttpService::encodeJSON, "JSONEncode", "input", Security::None); static Reflection::BoundFuncDesc urlEncode(&HttpService::urlEncode, "UrlEncode", "input", Security::None); static Reflection::BoundFuncDesc generateGuid(&HttpService::generateGuid, "GenerateGUID", "wrapInCurlyBraces", true, Security::None); Reflection::BoundProp HttpService::prop_httpEnabled("HttpEnabled", category_Data, &HttpService::httpEnabled, Reflection::PropertyDescriptor::STANDARD, Security::LocalUser); REFLECTION_END(); namespace Reflection { template<> EnumDesc::EnumDesc() :EnumDescriptor("HttpContentType") { addPair(HttpService::APPLICATION_JSON, "ApplicationJson"); addPair(HttpService::APPLICATION_XML, "ApplicationXml"); addPair(HttpService::APPLICATION_URLENCODED, "ApplicationUrlEncoded"); addPair(HttpService::TEXT_PLAIN, "TextPlain"); addPair(HttpService::TEXT_XML, "TextXml"); } template<> HttpService::HttpContentType& Variant::convert(void) { return genericConvert(); } }//namespace Reflection template<> bool StringConverter::convertToValue(const std::string& text, HttpService::HttpContentType& value) { return Reflection::EnumDesc::singleton().convertToValue(text.c_str(),value); } HttpService::HttpService() : httpEnabled(false), throttle(&DFInt::UserHttpRequestsPerMinuteLimit) { setName(sHttpService); } bool HttpService::checkEverything(std::string& url, boost::function errorFunction) { if (url.size() == 0) { errorFunction("Empty URL"); return false; } if (!httpEnabled) { errorFunction("Http requests are not enabled"); return false; } if (!Network::Players::backendProcessing(this)) { errorFunction("Http requests can only be executed by game server"); return false; } if (Instance::fastDynamicCast(getParent()) == NULL) { errorFunction("Unrecognized HttpService"); return false; } if (!throttle.checkLimit()) { errorFunction("Number of requests exceeded limit"); return false; } { static boost::once_flag flag = BOOST_ONCE_INIT; DataModel* dm = DataModel::get(this); boost::call_once(flag, boost::bind(&sendHttpServiceStats,dm->getPlaceID())); } return true; } void HttpService::addIdHeader(Http& request) { DataModel* dm = DataModel::get(this); request.additionalHeaders["Roblox-Id"] = RBX::StringConverter::convertToString(dm->getPlaceID()); } void HttpService::userHttpPostAsync(std::string url, std::string data, HttpContentType contentType, bool compress, boost::function resumeFunction, boost::function errorFunction) { if(!checkEverything(url, errorFunction)) return; Http http(url); std::string contentTypeName; switch(contentType) { case APPLICATION_JSON: contentTypeName = Http::kContentTypeApplicationJson; break; case APPLICATION_XML: contentTypeName = Http::kContentTypeApplicationXml; break; case APPLICATION_URLENCODED: contentTypeName = Http::kContentTypeUrlEncoded; break; case TEXT_PLAIN: contentTypeName = Http::kContentTypeTextPlain; break; case TEXT_XML: contentTypeName = Http::kContentTypeTextXml; break; default: errorFunction("Unsupported content type"); return; } if(data.length() == 0) data = " "; addIdHeader(http); http.post(data, contentTypeName, compress, boost::bind(&DataModel::HttpHelper, _1, _2, resumeFunction, errorFunction), true); } void HttpService::userHttpGetAsync(std::string url, bool nocache, boost::function resumeFunction, boost::function errorFunction) { if(!checkEverything(url, errorFunction)) return; Http http(url); if(nocache) { http.additionalHeaders["Cache-Control"] = "no-cache"; http.doNotUseCachedResponse = true; } addIdHeader(http); http.get(boost::bind(&DataModel::HttpHelper, _1, _2, resumeFunction, errorFunction), true); } Reflection::Variant HttpService::decodeJSON(std::string input) { Reflection::Variant value; if(WebParser::parseJSONObject(input, value)) { return value; } else { throw std::runtime_error("Can't parse JSON"); } } std::string HttpService::encodeJSON(Reflection::Variant obj) { std::string result; if(obj.isType >() || obj.isType >()) { if(!WebParser::writeJSON(obj, result)) { throw std::runtime_error("Can't convert to JSON"); } } else throw std::runtime_error("Can't convert to JSON"); return result; } std::string HttpService::urlEncode(std::string data) { return Http::urlEncode(data); } std::string HttpService::generateGuid(bool wrapInCurlyBraces) { std::string result; Guid::generateStandardGUID(result); if (!wrapInCurlyBraces) { RBXASSERT(result.size() > 2); result = result.substr(1, result.size() - 2); } return result; } }