Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1590 lines
59 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. #include "stdafx.h"
  3. #include "msgprotobuf.h"
  4. #include "smartptr.h"
  5. #include "rtime.h"
  6. #include "gcsdk/gcreportprinter.h"
  7. #include <winsock.h>
  8. // memdbgon must be the last include file in a .cpp file!!!
  9. #include "tier0/memdbgon.h"
  10. namespace GCSDK
  11. {
  12. GCConVar cv_webapi_result_size_limit( "webapi_result_size_limit", "50000000" );
  13. GCConVar cv_webapi_serialize_threads( "webapi_serialize_threads", "1", 0, "Switch for serializing webAPI responses on threads" );
  14. static GCConVar webapi_account_tracking( "webapi_account_tracking", "1", "Controls whether or not account tracking stats are collected for web api usage" );
  15. static GCConVar webapi_kill_switch( "webapi_kill_switch", "0", "When set to zero this will block no web api calls, when set to 1 this will block all web api except those sent by steam. To block steam, use the webapi_enable_steam_<priority> controls" );
  16. static GCConVar webapi_kill_switch_error_response( "webapi_kill_switch_error_response", "1", "Determines if a response should be sent when the kill switch kills a web api request" );
  17. static GCConVar webapi_rate_limit_calls_per_min( "webapi_rate_limit_calls_per_min", "600", "Determines how many messages can be sent from an account via the web api per minute. <0 disables this limiting" );
  18. static GCConVar webapi_rate_limit_mb_per_min( "webapi_rate_limit_mb_per_min", "20", "Determines how many megabytes of data can be sent to an account via the web api per minute. <0 disables this limiting" );
  19. static GCConVar webapi_elevated_rate_limit_calls_per_min( "webapi_elevated_rate_limit_calls_per_min", "-1", "Determines how many messages can be sent from an account via the web api per minute for elevated accounts. <0 disables this limiting" );
  20. static GCConVar webapi_elevated_rate_limit_mb_per_min( "webapi_elevated_rate_limit_mb_per_min", "-1", "Determines how many megabytes of data can be sent to an account via the web api per minute for elevated accounts. <0 disables this limiting" );
  21. static GCConVar webapi_ip_rate_limit( "webapi_ip_rate_limit", "1", "Controls whether or not we rate limit based upon IPs or just accounts" );
  22. //-----------------------------------------------------------------------------
  23. // CWebAPIAccountTracker
  24. //
  25. // Utility that tracks web api calls based upon accesses made by various users
  26. //-----------------------------------------------------------------------------
  27. class CWebAPIAccountTracker
  28. {
  29. public:
  30. //called when a web api request is made to track the call. A return value of true indicates that it should be allowed,
  31. //false indicates it should be blocked
  32. bool TrackUser( AccountID_t nID, uint32 nIP );
  33. //called once the size of the response is known and will track bandwidth and caller attributed to the provided function
  34. void TrackFunction( AccountID_t nID, uint32 nIP, const char* pszFunction, uint32 nResponseSize );
  35. //called to reset all permissions to default
  36. void ResetAccountPermissions();
  37. //called to associate a permission level with an account
  38. void SetAccountPermission( AccountID_t nID, EWebAPIAccountLevel eLevel );
  39. //completely resets accumulated stats
  40. void ResetStats();
  41. //resets just the profile stats
  42. void ResetProfileStats();
  43. //different caller report filters that can be used
  44. enum EDumpCaller
  45. {
  46. eDumpCaller_All,
  47. eDumpCaller_Blocked,
  48. eDumpCaller_Status,
  49. eDumpCaller_Calls,
  50. };
  51. //different reports that can be provided
  52. void DumpTotalCallers( EDumpCaller eFilter, const char* pszFunctionFilter = NULL ) const;
  53. void DumpTotalIPs( EDumpCaller eFilter, const char* pszFunctionFilter = NULL ) const;
  54. void DumpCaller( AccountID_t nID ) const;
  55. void DumpIP( uint32 nIP ) const;
  56. void DumpFunctions() const;
  57. void DumpProfile( bool bAllTime ) const;
  58. void DumpSteamServers() const;
  59. //given a steam server name, this will return the identifier of that server
  60. uint32 GetSteamServerID( const char* pszServer );
  61. //for steam level requests, we have a priority provided, and to track stats separately we break them up to different account IDs instead of just zero
  62. static const uint32 k_nSteamIP_High = 2;
  63. static const uint32 k_nSteamIP_Normal = 1;
  64. static const uint32 k_nSteamIP_Low = 0;
  65. private:
  66. //called to get the starting time of this rate interval
  67. RTime32 GetRateIntervalStart( AccountID_t nCaller ) const;
  68. struct SCallerStats
  69. {
  70. SCallerStats() :
  71. m_nBlockedCalls( 0 ),
  72. m_eLevel( eWebAPIAccountLevel_RateLimited )
  73. {
  74. ResetRateInterval( 0 );
  75. }
  76. void ResetRateInterval( RTime32 nStart )
  77. {
  78. m_nRateIntervalStartTime = nStart;
  79. m_nRateIntervalCalls = 0;
  80. m_nRateIntervalBytes = 0;
  81. }
  82. //the most recent rate interval that we received a message from the user (used to expire old counts)
  83. RTime32 m_nRateIntervalStartTime;
  84. //how many messages have been sent within this rate interval (used for rate limiting)
  85. uint32 m_nRateIntervalCalls;
  86. //how many bytes have been sent for this account during this interval
  87. uint32 m_nRateIntervalBytes;
  88. //total number of blocked calls
  89. uint32 m_nBlockedCalls;
  90. //flags associated with this caller, used to block/whitelist, etc
  91. EWebAPIAccountLevel m_eLevel;
  92. };
  93. struct SFunctionStats
  94. {
  95. //track the number of calls and bandwidth. The profile versions are separate and used for displaying profiles over a window
  96. uint32 m_nTotalCalls;
  97. uint32 m_nProfileCalls;
  98. uint32 m_nMaxBytes;
  99. uint32 m_nProfileMaxBytes;
  100. uint64 m_nTotalBytes;
  101. uint64 m_nProfileBytes;
  102. //a map of who has called us, which consists of the caller account and IP as the key
  103. struct SCaller
  104. {
  105. bool operator==( const SCaller& rhs ) const { return m_nAccountID == rhs.m_nAccountID && m_nIP == rhs.m_nIP; }
  106. AccountID_t m_nAccountID;
  107. uint32 m_nIP;
  108. };
  109. struct SCalls
  110. {
  111. uint32 m_nCalls;
  112. uint64 m_nBytes;
  113. };
  114. CUtlHashMapLarge< SCaller, SCalls > m_Callers;
  115. };
  116. //a structure used to simplify reporting so a vector can just be built of these, and then provided to the report function which will handle sorting it and displaying
  117. struct SReportRow
  118. {
  119. SReportRow( const char* pszFunction, uint32 nCalls, uint64 nSize ) :
  120. m_pszFunction( pszFunction ),
  121. m_nCalls( nCalls ),
  122. m_nSize( nSize )
  123. {}
  124. const char* m_pszFunction;
  125. uint32 m_nCalls;
  126. uint64 m_nSize;
  127. };
  128. struct SSteamServer
  129. {
  130. CUtlString m_sName;
  131. uint32 m_nID;
  132. };
  133. //called to find an existing user, or create one if not in the list already
  134. SCallerStats* CreateAccountUser( AccountID_t nID, RTime32 nRateIntervalStart );
  135. SCallerStats* CreateIPUser( uint32 nIP, RTime32 nRateIntervalStart );
  136. //called to print a report of the provided report rows as either an ID list or a function list. This will re-sort the provided vector
  137. static void PrintReport( const CUtlVector< SReportRow >& vec );
  138. //how many seconds are in a rate interval
  139. static const uint32 knRateIntervalTimeS = 60;
  140. CUtlHashMapLarge< AccountID_t, SCallerStats > m_AccountCallers;
  141. CUtlHashMapLarge< uint32, SCallerStats > m_IPCallers;
  142. CUtlHashMapLarge< uintp, SFunctionStats* > m_Functions;
  143. CUtlHashMapLarge< const char*, SSteamServer*, CaseSensitiveStrEquals, MurmurHash3ConstCharPtr > m_SteamServers;
  144. CJobTime m_ProfileTime;
  145. };
  146. //our global profiler
  147. static CWebAPIAccountTracker g_WebAPIAccountTracker;
  148. void WebAPIAccount_ResetAllPermissions()
  149. {
  150. g_WebAPIAccountTracker.ResetAccountPermissions();
  151. }
  152. void WebAPIAccount_SetPermission( AccountID_t nID, EWebAPIAccountLevel eLevel )
  153. {
  154. g_WebAPIAccountTracker.SetAccountPermission( nID, eLevel );
  155. }
  156. bool WebAPIAccount_BTrackUserAndValidate( AccountID_t nID, uint32 unIP )
  157. {
  158. return g_WebAPIAccountTracker.TrackUser( nID, unIP );
  159. }
  160. RTime32 CWebAPIAccountTracker::GetRateIntervalStart( AccountID_t nCaller ) const
  161. {
  162. //we shift the time by the account ID so that all users don't wrap at the same time which can cause a temporary surge in web API requests
  163. RTime32 curTime = CRTime::RTime32TimeCur() + ( nCaller % knRateIntervalTimeS );
  164. return curTime - ( curTime % knRateIntervalTimeS );
  165. }
  166. CWebAPIAccountTracker::SCallerStats* CWebAPIAccountTracker::CreateAccountUser( AccountID_t nID, RTime32 nRateIntervalStart )
  167. {
  168. int nIndex = m_AccountCallers.Find( nID );
  169. if( nIndex == m_AccountCallers.InvalidIndex() )
  170. {
  171. nIndex = m_AccountCallers.Insert( nID );
  172. SCallerStats& caller = m_AccountCallers[ nIndex ];
  173. caller.m_nRateIntervalStartTime = nRateIntervalStart;
  174. //account ID is always unrestricted!
  175. if( nID == 0 )
  176. caller.m_eLevel = eWebAPIAccountLevel_Unlimited;
  177. }
  178. return &m_AccountCallers[ nIndex ];
  179. }
  180. CWebAPIAccountTracker::SCallerStats* CWebAPIAccountTracker::CreateIPUser( uint32 nIP, RTime32 nRateIntervalStart )
  181. {
  182. int nIndex = m_IPCallers.Find( nIP );
  183. if( nIndex == m_IPCallers.InvalidIndex() )
  184. {
  185. nIndex = m_IPCallers.Insert( nIP );
  186. SCallerStats& caller = m_IPCallers[ nIndex ];
  187. caller.m_nRateIntervalStartTime = nRateIntervalStart;
  188. }
  189. return &m_IPCallers[ nIndex ];
  190. }
  191. uint32 CWebAPIAccountTracker::GetSteamServerID( const char* pszServer )
  192. {
  193. int nIndex = m_SteamServers.Find( pszServer );
  194. if( nIndex == m_SteamServers.InvalidIndex() )
  195. {
  196. SSteamServer* pServer = new SSteamServer;
  197. pServer->m_sName = pszServer;
  198. pServer->m_nID = m_SteamServers.Count();
  199. m_SteamServers.Insert( pServer->m_sName, pServer );
  200. return pServer->m_nID;
  201. }
  202. return m_SteamServers[ nIndex ]->m_nID;
  203. }
  204. void CWebAPIAccountTracker::DumpSteamServers() const
  205. {
  206. CGCReportPrinter rp;
  207. rp.AddStringColumn( "ID" );
  208. rp.AddStringColumn( "Server" );
  209. FOR_EACH_MAP_FAST( m_SteamServers, nServer )
  210. {
  211. const uint32 nID = m_SteamServers[ nServer ]->m_nID;
  212. rp.StrValue( CFmtStr( "%u.%u.%u.%u", iptod( nID << 8 ) ) );
  213. rp.StrValue( m_SteamServers[ nServer ]->m_sName );
  214. rp.CommitRow();
  215. }
  216. rp.SortReport( "ID", false );
  217. rp.PrintReport( SPEW_CONSOLE );
  218. }
  219. //determines what the resulting account level access should be based upon the access rights of the IP address and the account
  220. static EWebAPIAccountLevel DetermineAccessLevel( EWebAPIAccountLevel eAccount, EWebAPIAccountLevel eIP )
  221. {
  222. //unrestricted users should always be allowed, regardless of the IP range that they are making requests from, same with unlimited IP addresses
  223. if( ( eAccount == eWebAPIAccountLevel_Unlimited ) || ( eIP == eWebAPIAccountLevel_Unlimited ) )
  224. return eWebAPIAccountLevel_Unlimited;
  225. //otherwise, if either is blocked, then block
  226. if( ( eAccount == eWebAPIAccountLevel_Blocked ) || ( eIP == eWebAPIAccountLevel_Blocked ) )
  227. return eWebAPIAccountLevel_Blocked;
  228. //now we are dealing with default case versus elevated. Elevated wins over default
  229. if( ( eAccount == eWebAPIAccountLevel_Elevated ) || ( eIP == eWebAPIAccountLevel_Elevated ) )
  230. return eWebAPIAccountLevel_Elevated;
  231. //default
  232. return eWebAPIAccountLevel_RateLimited;
  233. }
  234. bool CWebAPIAccountTracker::TrackUser( AccountID_t nID, uint32 nIP )
  235. {
  236. if( !webapi_account_tracking.GetBool() )
  237. return true;
  238. //first off update their aggregate caller stats
  239. {
  240. //what is our current time, and at what time did this rate interval start
  241. const RTime32 rateIntervalStart = GetRateIntervalStart( nID );
  242. //see if this account is completely blocked
  243. SCallerStats* pAccountCaller = CreateAccountUser( nID, rateIntervalStart );
  244. SCallerStats* pIPCaller = CreateIPUser( nIP, rateIntervalStart );
  245. //determine what our policy should be based upon the access level of the IP and the user
  246. EWebAPIAccountLevel eAccessLevel = DetermineAccessLevel( pAccountCaller->m_eLevel, pIPCaller->m_eLevel );
  247. //if we are blocked, just bail now
  248. if( eAccessLevel == eWebAPIAccountLevel_Blocked )
  249. {
  250. pAccountCaller->m_nBlockedCalls++;
  251. pIPCaller->m_nBlockedCalls++;
  252. return false;
  253. }
  254. //reset the rate interval tracking
  255. if( pAccountCaller->m_nRateIntervalStartTime < rateIntervalStart )
  256. pAccountCaller->ResetRateInterval( rateIntervalStart );
  257. if( pIPCaller->m_nRateIntervalStartTime < rateIntervalStart )
  258. pIPCaller->ResetRateInterval( rateIntervalStart );
  259. //now handle rate limiting
  260. if( ( eAccessLevel == eWebAPIAccountLevel_RateLimited ) || ( eAccessLevel == eWebAPIAccountLevel_Elevated ) )
  261. {
  262. //determine the rate we want to limit
  263. int32 nCallsPerMin = ( eAccessLevel == eWebAPIAccountLevel_RateLimited ) ? webapi_rate_limit_calls_per_min.GetInt() : webapi_elevated_rate_limit_calls_per_min.GetInt();
  264. int32 nBytesPerMin = ( ( eAccessLevel == eWebAPIAccountLevel_RateLimited ) ? webapi_rate_limit_mb_per_min.GetInt() : webapi_elevated_rate_limit_mb_per_min.GetInt() ) * 1024 * 1024;
  265. //see if this account is rate limited
  266. if( ( eAccessLevel == eWebAPIAccountLevel_RateLimited ) )
  267. {
  268. bool bAllow = true;
  269. //see if we are being limited based upon call rate limiting (tracking based upon ip and account) Note that
  270. //we don't return until we've dones stat tracking for both so the reports are accurate and capture it at both levels
  271. if( ( nCallsPerMin >= 0 && pAccountCaller->m_nRateIntervalCalls >= ( uint32 )nCallsPerMin ) ||
  272. ( nBytesPerMin >= 0 && pAccountCaller->m_nRateIntervalBytes >= ( uint32 )nBytesPerMin ) )
  273. {
  274. pAccountCaller->m_nBlockedCalls++;
  275. bAllow = false;
  276. }
  277. if( webapi_ip_rate_limit.GetBool() )
  278. {
  279. if( ( nCallsPerMin >= 0 && pIPCaller->m_nRateIntervalCalls >= ( uint32 )nCallsPerMin ) ||
  280. ( nBytesPerMin >= 0 && pIPCaller->m_nRateIntervalBytes >= ( uint32 )nBytesPerMin ) )
  281. {
  282. pIPCaller->m_nBlockedCalls++;
  283. bAllow = false;
  284. }
  285. }
  286. if( !bAllow )
  287. return false;
  288. }
  289. }
  290. }
  291. return true;
  292. }
  293. void CWebAPIAccountTracker::TrackFunction( AccountID_t nID, uint32 nIP, const char* pszFunction, uint32 nResponseSize )
  294. {
  295. if( !webapi_account_tracking.GetBool() )
  296. return;
  297. //update the bytes for that user
  298. {
  299. int nCallerIndex = m_AccountCallers.Find( nID );
  300. if( nCallerIndex != m_AccountCallers.InvalidIndex() )
  301. {
  302. SCallerStats& caller = m_AccountCallers[ nCallerIndex ];
  303. caller.m_nRateIntervalBytes += nResponseSize;
  304. caller.m_nRateIntervalCalls++;
  305. }
  306. }
  307. //update the bytes for that user and for their IP
  308. {
  309. int nCallerIndex = m_IPCallers.Find( nIP );
  310. if( nCallerIndex != m_IPCallers.InvalidIndex() )
  311. {
  312. SCallerStats& caller = m_IPCallers[ nCallerIndex ];
  313. caller.m_nRateIntervalBytes += nResponseSize;
  314. caller.m_nRateIntervalCalls++;
  315. }
  316. }
  317. //now update the function specific stats
  318. {
  319. int nFunctionIndex = m_Functions.Find( ( uintp )pszFunction );
  320. if( nFunctionIndex == m_Functions.InvalidIndex() )
  321. {
  322. SFunctionStats* pNewStats = new SFunctionStats;
  323. pNewStats->m_nTotalCalls = 0;
  324. pNewStats->m_nProfileCalls = 0;
  325. pNewStats->m_nTotalBytes = 0;
  326. pNewStats->m_nProfileBytes = 0;
  327. pNewStats->m_nMaxBytes = 0;
  328. pNewStats->m_nProfileMaxBytes = 0;
  329. nFunctionIndex = m_Functions.Insert( ( uintp )pszFunction, pNewStats );
  330. }
  331. //update our stats
  332. SFunctionStats& function = *m_Functions[ nFunctionIndex ];
  333. function.m_nTotalCalls++;
  334. function.m_nProfileCalls++;
  335. function.m_nTotalBytes += nResponseSize;
  336. function.m_nProfileBytes += nResponseSize;
  337. function.m_nMaxBytes = MAX( function.m_nMaxBytes, nResponseSize );
  338. function.m_nProfileMaxBytes = MAX( function.m_nProfileMaxBytes, nResponseSize );
  339. //update caller stats
  340. {
  341. struct SFunctionStats::SCaller caller;
  342. caller.m_nAccountID = nID;
  343. caller.m_nIP = nIP;
  344. int nCallerIndex = function.m_Callers.Find( caller );
  345. if( nCallerIndex == function.m_Callers.InvalidIndex() )
  346. {
  347. nCallerIndex = function.m_Callers.Insert( caller );
  348. function.m_Callers[ nCallerIndex ].m_nCalls = 1;
  349. function.m_Callers[ nCallerIndex ].m_nBytes = nResponseSize;
  350. }
  351. else
  352. {
  353. function.m_Callers[ nCallerIndex ].m_nCalls++;
  354. function.m_Callers[ nCallerIndex ].m_nBytes += nResponseSize;
  355. }
  356. }
  357. }
  358. }
  359. void CWebAPIAccountTracker::SetAccountPermission( AccountID_t nID, EWebAPIAccountLevel eLevel )
  360. {
  361. SCallerStats* pCaller = CreateAccountUser( nID, GetRateIntervalStart( nID ) );
  362. pCaller->m_eLevel = eLevel;
  363. }
  364. void CWebAPIAccountTracker::ResetAccountPermissions()
  365. {
  366. FOR_EACH_MAP_FAST( m_AccountCallers, nCaller )
  367. {
  368. m_AccountCallers[ nCaller ].m_eLevel = eWebAPIAccountLevel_RateLimited;
  369. }
  370. FOR_EACH_MAP_FAST( m_IPCallers, nCaller )
  371. {
  372. m_IPCallers[ nCaller ].m_eLevel = eWebAPIAccountLevel_RateLimited;
  373. }
  374. }
  375. void CWebAPIAccountTracker::ResetStats()
  376. {
  377. FOR_EACH_MAP_FAST( m_AccountCallers, nCaller )
  378. {
  379. m_AccountCallers[ nCaller ].ResetRateInterval( GetRateIntervalStart( m_AccountCallers.Key( nCaller ) ) );
  380. m_AccountCallers[ nCaller ].m_nBlockedCalls = 0;
  381. }
  382. FOR_EACH_MAP_FAST( m_IPCallers, nCaller )
  383. {
  384. m_IPCallers[ nCaller ].ResetRateInterval( GetRateIntervalStart( m_IPCallers.Key( nCaller ) ) );
  385. m_IPCallers[ nCaller ].m_nBlockedCalls = 0;
  386. }
  387. m_Functions.PurgeAndDeleteElements();
  388. }
  389. void CWebAPIAccountTracker::ResetProfileStats()
  390. {
  391. FOR_EACH_MAP_FAST( m_Functions, nFunction )
  392. {
  393. m_Functions[ nFunction ]->m_nProfileCalls = 0;
  394. m_Functions[ nFunction ]->m_nProfileBytes = 0;
  395. m_Functions[ nFunction ]->m_nProfileMaxBytes = 0;
  396. }
  397. m_ProfileTime.SetToJobTime();
  398. }
  399. static const int k_cSteamIDRenderedMaxLen = 36;
  400. //-----------------------------------------------------------------------------
  401. // Purpose: Renders the steam ID to a buffer with an admin console link. NOTE: for convenience of
  402. // calling code, this code returns a pointer to a static buffer and is NOT thread-safe.
  403. // Output: buffer with rendered Steam ID
  404. //-----------------------------------------------------------------------------
  405. static const char * CSteamID_RenderLink( const CSteamID & steamID )
  406. {
  407. // longest length of returned string is k_cBufLen
  408. // <link cmd="steamid64 %llu"></link> => 30 + 20 == 50
  409. // 50 + k_cSteamIDRenderedMaxLen + 1
  410. const int k_cBufLen = 50 + k_cSteamIDRenderedMaxLen + 1;
  411. const int k_cBufs = 4; // # of static bufs to use (so people can compose output with multiple calls to RenderLink() )
  412. static char rgchBuf[k_cBufs][k_cBufLen];
  413. static int nBuf = 0;
  414. char * pchBuf = rgchBuf[nBuf]; // get pointer to current static buf
  415. nBuf++; // use next buffer for next call to this method
  416. nBuf %= k_cBufs;
  417. Q_snprintf( pchBuf, k_cBufLen, "<link cmd=\"steamid64 %llu\">%s</link>", steamID.ConvertToUint64(), steamID.Render() );
  418. return pchBuf;
  419. }
  420. //-----------------------------------------------------------------------------
  421. // Purpose: Renders the passed-in steam ID to a buffer with admin console link. NOTE: for convenience
  422. // of calling code, this code returns a pointer to a static buffer and is NOT thread-safe.
  423. // Input: 64-bit representation of Steam ID to render
  424. // Output: buffer with rendered Steam ID link
  425. //-----------------------------------------------------------------------------
  426. static const char * CSteamID_RenderLink( uint64 ulSteamID )
  427. {
  428. CSteamID steamID( ulSteamID );
  429. return CSteamID_RenderLink( steamID );
  430. }
  431. void CWebAPIAccountTracker::DumpCaller( AccountID_t nID ) const
  432. {
  433. const CSteamID steamID = GGCInterface()->ConstructSteamIDForClient( nID );
  434. //cache the account name here so we don't yield while we have indices
  435. CUtlString sPersona = GGCBase()->YieldingGetPersonaName( steamID, "[Unknown]" );
  436. //dump high level user stats
  437. int nCallerIndex = m_AccountCallers.Find( nID );
  438. if( nCallerIndex == m_AccountCallers.InvalidIndex() )
  439. {
  440. EG_MSG( SPEW_CONSOLE, "User %u not found in any web api calls\n", nID );
  441. return;
  442. }
  443. //a map of IP addresses that have been used by this account
  444. CUtlHashMapLarge< uint32, SFunctionStats::SCalls > ipCalls;
  445. //now each function they called
  446. uint64 nTotalBytes = 0;
  447. uint32 nTotalCalls = 0;
  448. CUtlVector< SReportRow > vFuncs;
  449. FOR_EACH_MAP_FAST( m_Functions, nFunc )
  450. {
  451. //add up how many calls they made to this function across all IPs
  452. uint64 nFnBytes = 0;
  453. uint32 nFnCalls = 0;
  454. FOR_EACH_MAP_FAST( m_Functions[ nFunc ]->m_Callers, nCaller )
  455. {
  456. const CWebAPIAccountTracker::SFunctionStats::SCaller& caller = m_Functions[ nFunc ]->m_Callers.Key( nCaller );
  457. if( caller.m_nAccountID == nID )
  458. {
  459. const CWebAPIAccountTracker::SFunctionStats::SCalls& calls = m_Functions[ nFunc ]->m_Callers[ nCaller ];
  460. nFnBytes += calls.m_nBytes;
  461. nFnCalls += calls.m_nCalls;
  462. int nIPIndex = ipCalls.Find( caller.m_nIP );
  463. if( nIPIndex == ipCalls.InvalidIndex() )
  464. {
  465. SFunctionStats::SCalls toAdd;
  466. toAdd.m_nBytes = calls.m_nBytes;
  467. toAdd.m_nCalls = calls.m_nCalls;
  468. ipCalls.Insert( caller.m_nIP, toAdd );
  469. }
  470. else
  471. {
  472. ipCalls[ nIPIndex ].m_nBytes += calls.m_nBytes;
  473. ipCalls[ nIPIndex ].m_nBytes += calls.m_nCalls;
  474. }
  475. }
  476. }
  477. if( nFnCalls > 0 )
  478. {
  479. vFuncs.AddToTail( SReportRow( ( const char* )m_Functions.Key( nFunc ), nFnCalls, nFnBytes ) );
  480. }
  481. nTotalBytes += nFnBytes;
  482. nTotalCalls += nFnCalls;
  483. }
  484. const SCallerStats& caller = m_AccountCallers[ nCallerIndex ];
  485. EG_MSG( SPEW_CONSOLE, "---------------------------------------------------\n" );
  486. EG_MSG( SPEW_CONSOLE, "User %s: \"%s\"\n", CSteamID_RenderLink( steamID ), sPersona.String() );
  487. double fTotalMB = nTotalBytes / ( 1024.0 * 1024.0 );
  488. double fMBPerHour = fTotalMB / ( GGCBase()->GetGCUpTime() / 3600.0 );
  489. double fCallsPerHour = nTotalCalls / ( GGCBase()->GetGCUpTime() / 3600.0 );
  490. EG_MSG( SPEW_CONSOLE, "\tAccess: %u, Total Calls: %u, Blocked calls: %u, Total: %.2fMB, MB/h: %.2f, Calls/h: %.0f\n", caller.m_eLevel, nTotalCalls, caller.m_nBlockedCalls, fTotalMB, fMBPerHour, fCallsPerHour );
  491. //don't let someone accidentally change Steam's access!
  492. if( nID != 0 )
  493. {
  494. if( caller.m_eLevel == eWebAPIAccountLevel_RateLimited )
  495. {
  496. EG_MSG( SPEW_CONSOLE, "\t<link cmd=\"webapi_account_set_access %u %d\">[Block Account]</link>", nID, eWebAPIAccountLevel_Blocked );
  497. EG_MSG( SPEW_CONSOLE, "\t<link cmd=\"webapi_account_set_access %u %d\">[Elevate Account]</link>\n", nID, eWebAPIAccountLevel_Elevated );
  498. }
  499. else if( caller.m_eLevel == eWebAPIAccountLevel_Blocked )
  500. {
  501. EG_MSG( SPEW_CONSOLE, "\t<link cmd=\"webapi_account_set_access %u %d\">[Unblock Account]</link>\n", nID, eWebAPIAccountLevel_RateLimited );
  502. }
  503. else if( caller.m_eLevel == eWebAPIAccountLevel_Elevated )
  504. {
  505. EG_MSG( SPEW_CONSOLE, "\t<link cmd=\"webapi_account_set_access %u %d\">[Demote Account]</link>\n", nID, eWebAPIAccountLevel_RateLimited );
  506. }
  507. }
  508. //print a report of the IP addresses that they are calling from
  509. {
  510. CGCReportPrinter rp;
  511. rp.AddStringColumn( "IP" );
  512. rp.AddIntColumn( "Calls", CGCReportPrinter::eSummary_Total );
  513. rp.AddIntColumn( "MB", CGCReportPrinter::eSummary_Total, CGCReportPrinter::eIntDisplay_Memory_MB );
  514. FOR_EACH_MAP_FAST( ipCalls, nIP )
  515. {
  516. rp.StrValue( CFmtStr( "%u.%u.%u.%u", iptod( ipCalls.Key( nIP ) ) ), CFmtStr( "webapi_account_dump_ip %u", ipCalls.Key( nIP ) ) );
  517. rp.IntValue( ipCalls[ nIP ].m_nCalls );
  518. rp.IntValue( ipCalls[ nIP ].m_nBytes );
  519. rp.CommitRow();
  520. }
  521. rp.SortReport( "MB" );
  522. rp.PrintReport( SPEW_CONSOLE );
  523. }
  524. //and print a report of all the functions that they've called
  525. PrintReport( vFuncs );
  526. }
  527. void CWebAPIAccountTracker::DumpIP( uint32 nIP ) const
  528. {
  529. //dump high level user stats
  530. int nCallerIndex = m_IPCallers.Find( nIP );
  531. if( nCallerIndex == m_IPCallers.InvalidIndex() )
  532. {
  533. EG_MSG( SPEW_CONSOLE, "IP %u not found in any web api calls\n", nIP );
  534. return;
  535. }
  536. //a map of IP addresses that have been used by this account
  537. CUtlHashMapLarge< AccountID_t, SFunctionStats::SCalls > accountCalls;
  538. //now each function they called
  539. uint64 nTotalBytes = 0;
  540. uint32 nTotalCalls = 0;
  541. CUtlVector< SReportRow > vFuncs;
  542. FOR_EACH_MAP_FAST( m_Functions, nFunc )
  543. {
  544. //add up how many calls they made to this function across all IPs
  545. uint64 nFnBytes = 0;
  546. uint32 nFnCalls = 0;
  547. FOR_EACH_MAP_FAST( m_Functions[ nFunc ]->m_Callers, nCaller )
  548. {
  549. const CWebAPIAccountTracker::SFunctionStats::SCaller& caller = m_Functions[ nFunc ]->m_Callers.Key( nCaller );
  550. if( caller.m_nIP == nIP )
  551. {
  552. const CWebAPIAccountTracker::SFunctionStats::SCalls& calls = m_Functions[ nFunc ]->m_Callers[ nCaller ];
  553. nFnBytes += calls.m_nBytes;
  554. nFnCalls += calls.m_nCalls;
  555. int nAccountIndex = accountCalls.Find( caller.m_nAccountID );
  556. if( nAccountIndex == accountCalls.InvalidIndex() )
  557. {
  558. SFunctionStats::SCalls toAdd;
  559. toAdd.m_nBytes = calls.m_nBytes;
  560. toAdd.m_nCalls = calls.m_nCalls;
  561. accountCalls.Insert( caller.m_nAccountID, toAdd );
  562. GGCBase()->PreloadPersonaName( GGCInterface()->ConstructSteamIDForClient( caller.m_nAccountID ) );
  563. }
  564. else
  565. {
  566. accountCalls[ nAccountIndex ].m_nBytes += calls.m_nBytes;
  567. accountCalls[ nAccountIndex ].m_nBytes += calls.m_nCalls;
  568. }
  569. }
  570. }
  571. if( nFnCalls > 0 )
  572. {
  573. vFuncs.AddToTail( SReportRow( ( const char* )m_Functions.Key( nFunc ), nFnCalls, nFnBytes ) );
  574. }
  575. nTotalBytes += nFnBytes;
  576. nTotalCalls += nFnCalls;
  577. }
  578. const SCallerStats& caller = m_IPCallers[ nCallerIndex ];
  579. EG_MSG( SPEW_CONSOLE, "---------------------------------------------------\n" );
  580. EG_MSG( SPEW_CONSOLE, "IP %u.%u.%u.%u\n", iptod( nIP ) );
  581. double fTotalMB = nTotalBytes / ( 1024.0 * 1024.0 );
  582. double fMBPerHour = fTotalMB / ( GGCBase()->GetGCUpTime() / 3600.0 );
  583. double fCallsPerHour = nTotalCalls / ( GGCBase()->GetGCUpTime() / 3600.0 );
  584. EG_MSG( SPEW_CONSOLE, "\tAccess: %u, Total Calls: %u, Blocked calls: %u, Total: %.2fMB, MB/h: %.2f, Calls/h: %.0f\n", caller.m_eLevel, nTotalCalls, caller.m_nBlockedCalls, fTotalMB, fMBPerHour, fCallsPerHour );
  585. //print a report of the accounts that they are calling from
  586. {
  587. CGCReportPrinter rp;
  588. rp.AddSteamIDColumn( "Account" );
  589. rp.AddStringColumn( "Persona" );
  590. rp.AddIntColumn( "Calls", CGCReportPrinter::eSummary_Total );
  591. rp.AddIntColumn( "MB", CGCReportPrinter::eSummary_Total, CGCReportPrinter::eIntDisplay_Memory_MB );
  592. FOR_EACH_MAP_FAST( accountCalls, nAccount )
  593. {
  594. CSteamID steamID = GGCInterface()->ConstructSteamIDForClient( accountCalls.Key( nAccount ) );
  595. rp.SteamIDValue( steamID, CFmtStr( "webapi_account_dump_caller %u", steamID.GetAccountID() ) );
  596. rp.StrValue( GGCBase()->YieldingGetPersonaName( steamID, "[unknown]" ), CFmtStr( "webapi_account_dump_caller %u", steamID.GetAccountID() ) );
  597. rp.IntValue( accountCalls[ nAccount ].m_nCalls );
  598. rp.IntValue( accountCalls[ nAccount ].m_nBytes );
  599. rp.CommitRow();
  600. }
  601. rp.SortReport( "MB" );
  602. rp.PrintReport( SPEW_CONSOLE );
  603. }
  604. //and print a report of all the functions that they've called
  605. PrintReport( vFuncs );
  606. }
  607. struct SCallerReportStats
  608. {
  609. uint32 m_nFunctions;
  610. uint32 m_nCalls;
  611. uint64 m_nBytes;
  612. };
  613. void CWebAPIAccountTracker::DumpTotalCallers( EDumpCaller eFilter, const char* pszFunctionFilter ) const
  614. {
  615. //accumulate stats for each unique caller
  616. CUtlHashMapLarge< AccountID_t, SCallerReportStats > mapCallers;
  617. FOR_EACH_MAP_FAST( m_Functions, nCurrFunction )
  618. {
  619. //handle filtering out functions we don't care about
  620. const char* pszFunctionName = ( const char* )m_Functions.Key( nCurrFunction );
  621. if( pszFunctionFilter && ( V_stristr( pszFunctionName, pszFunctionFilter ) == NULL ) )
  622. continue;
  623. const SFunctionStats& function = *m_Functions[ nCurrFunction ];
  624. FOR_EACH_MAP_FAST( function.m_Callers, nCurrCaller )
  625. {
  626. const AccountID_t key = function.m_Callers.Key( nCurrCaller ).m_nAccountID;
  627. //add this account
  628. int nStatIndex = mapCallers.Find( key );
  629. if( nStatIndex == mapCallers.InvalidIndex() )
  630. {
  631. SCallerReportStats stats;
  632. stats.m_nFunctions = 0;
  633. stats.m_nCalls = 0;
  634. stats.m_nBytes = 0;
  635. nStatIndex = mapCallers.Insert( key, stats );
  636. GGCBase()->PreloadPersonaName( GGCInterface()->ConstructSteamIDForClient( key ) );
  637. }
  638. mapCallers[ nStatIndex ].m_nFunctions += 1;
  639. mapCallers[ nStatIndex ].m_nCalls += function.m_Callers[ nCurrCaller ].m_nCalls;
  640. mapCallers[ nStatIndex ].m_nBytes += function.m_Callers[ nCurrCaller ].m_nBytes;
  641. }
  642. }
  643. CGCReportPrinter rp;
  644. rp.AddStringColumn( "Account" );
  645. rp.AddIntColumn( "Calls", CGCReportPrinter::eSummary_Total );
  646. rp.AddIntColumn( "MB", CGCReportPrinter::eSummary_Total, CGCReportPrinter::eIntDisplay_Memory_MB );
  647. rp.AddIntColumn( "Blocked", CGCReportPrinter::eSummary_Total );
  648. rp.AddIntColumn( "Access", CGCReportPrinter::eSummary_None );
  649. rp.AddIntColumn( "APIs", CGCReportPrinter::eSummary_None );
  650. rp.AddIntColumn( "MB/h", CGCReportPrinter::eSummary_Total, CGCReportPrinter::eIntDisplay_Memory_MB );
  651. rp.AddIntColumn( "Calls/h", CGCReportPrinter::eSummary_Total );
  652. rp.AddIntColumn( "KB/c", CGCReportPrinter::eSummary_None );
  653. rp.AddStringColumn( "UserName" );
  654. const double fUpTimeHrs = MAX( GGCBase()->GetGCUpTime() / 3600.0, 0.01 );
  655. //now show the report
  656. FOR_EACH_MAP_FAST( m_AccountCallers, nCurrCaller )
  657. {
  658. //apply filters to our results
  659. if( ( eFilter == eDumpCaller_Blocked ) && ( m_AccountCallers[ nCurrCaller ].m_nBlockedCalls == 0 ) )
  660. continue;
  661. if( ( eFilter == eDumpCaller_Status ) && ( m_AccountCallers[ nCurrCaller ].m_eLevel == eWebAPIAccountLevel_RateLimited ) )
  662. continue;
  663. const AccountID_t accountID = m_AccountCallers.Key( nCurrCaller );
  664. const SCallerReportStats* pStats = NULL;
  665. int nCollectedStats = mapCallers.Find( accountID );
  666. if( nCollectedStats != mapCallers.InvalidIndex() )
  667. {
  668. pStats = &mapCallers[ nCollectedStats ];
  669. }
  670. //filter out users that didn't make any calls if appropriate
  671. if( ( eFilter == eDumpCaller_Calls ) && ( !pStats || ( pStats->m_nCalls == 0 ) ) )
  672. continue;
  673. const CSteamID steamID( GGCInterface()->ConstructSteamIDForClient( accountID ) );
  674. rp.StrValue( steamID.Render() , CFmtStr( "webapi_account_dump_caller %u", accountID ) );
  675. rp.IntValue( ( pStats ) ? pStats->m_nCalls : 0 );
  676. rp.IntValue( ( pStats ) ? pStats->m_nBytes : 0 );
  677. rp.IntValue( m_AccountCallers[ nCurrCaller ].m_nBlockedCalls );
  678. rp.IntValue( m_AccountCallers[ nCurrCaller ].m_eLevel );
  679. rp.IntValue( ( pStats ) ? pStats->m_nFunctions : 0 );
  680. rp.IntValue( ( int64 )( ( ( pStats ) ? pStats->m_nBytes : 0 ) / fUpTimeHrs ) );
  681. rp.IntValue( ( int64 )( ( ( pStats ) ? pStats->m_nCalls : 0 ) / fUpTimeHrs ) );
  682. rp.IntValue( ( pStats && pStats->m_nCalls > 0 ) ? ( pStats->m_nBytes / pStats->m_nCalls ) / 1024 : 0 );
  683. rp.StrValue( GGCBase()->YieldingGetPersonaName( steamID, "[unknown]" ) );
  684. rp.CommitRow();
  685. }
  686. const char* pszSort = "MB/h";
  687. if( eFilter == eDumpCaller_Blocked )
  688. pszSort = "Blocked";
  689. else if( eFilter == eDumpCaller_Status )
  690. pszSort = "Access";
  691. rp.SortReport( pszSort );
  692. rp.PrintReport( SPEW_CONSOLE );
  693. }
  694. void CWebAPIAccountTracker::DumpTotalIPs( EDumpCaller eFilter, const char* pszFunctionFilter ) const
  695. {
  696. //accumulate stats for each unique caller
  697. CUtlHashMapLarge< uint32, SCallerReportStats > mapIPs;
  698. FOR_EACH_MAP_FAST( m_Functions, nCurrFunction )
  699. {
  700. //handle filtering out functions we don't care about
  701. const char* pszFunctionName = ( const char* )m_Functions.Key( nCurrFunction );
  702. if( pszFunctionFilter && ( V_stristr( pszFunctionName, pszFunctionFilter ) == NULL ) )
  703. continue;
  704. const SFunctionStats& function = *m_Functions[ nCurrFunction ];
  705. FOR_EACH_MAP_FAST( function.m_Callers, nCurrCaller )
  706. {
  707. const uint32 key = function.m_Callers.Key( nCurrCaller ).m_nIP;
  708. //add this account
  709. int nStatIndex = mapIPs.Find( key );
  710. if( nStatIndex == mapIPs.InvalidIndex() )
  711. {
  712. SCallerReportStats stats;
  713. stats.m_nFunctions = 0;
  714. stats.m_nCalls = 0;
  715. stats.m_nBytes = 0;
  716. nStatIndex = mapIPs.Insert( key, stats );
  717. }
  718. mapIPs[ nStatIndex ].m_nFunctions += 1;
  719. mapIPs[ nStatIndex ].m_nCalls += function.m_Callers[ nCurrCaller ].m_nCalls;
  720. mapIPs[ nStatIndex ].m_nBytes += function.m_Callers[ nCurrCaller ].m_nBytes;
  721. }
  722. }
  723. CGCReportPrinter rp;
  724. rp.AddStringColumn( "IP" );
  725. rp.AddIntColumn( "Calls", CGCReportPrinter::eSummary_Total );
  726. rp.AddIntColumn( "MB", CGCReportPrinter::eSummary_Total, CGCReportPrinter::eIntDisplay_Memory_MB );
  727. rp.AddIntColumn( "Blocked", CGCReportPrinter::eSummary_Total );
  728. rp.AddIntColumn( "Access", CGCReportPrinter::eSummary_None );
  729. rp.AddIntColumn( "APIs", CGCReportPrinter::eSummary_None );
  730. rp.AddIntColumn( "MB/h", CGCReportPrinter::eSummary_Total, CGCReportPrinter::eIntDisplay_Memory_MB );
  731. rp.AddIntColumn( "Calls/h", CGCReportPrinter::eSummary_Total );
  732. rp.AddIntColumn( "KB/c", CGCReportPrinter::eSummary_None );
  733. const double fUpTimeHrs = MAX( GGCBase()->GetGCUpTime() / 3600.0, 0.01 );
  734. //now show the report
  735. FOR_EACH_MAP_FAST( m_IPCallers, nCurrCaller )
  736. {
  737. //apply filters to our results
  738. if( ( eFilter == eDumpCaller_Blocked ) && ( m_IPCallers[ nCurrCaller ].m_nBlockedCalls == 0 ) )
  739. continue;
  740. if( ( eFilter == eDumpCaller_Status ) && ( m_IPCallers[ nCurrCaller ].m_eLevel == eWebAPIAccountLevel_RateLimited ) )
  741. continue;
  742. const uint32 nIP = m_IPCallers.Key( nCurrCaller );
  743. const SCallerReportStats* pStats = NULL;
  744. int nCollectedStats = mapIPs.Find( nIP );
  745. if( nCollectedStats != mapIPs.InvalidIndex() )
  746. {
  747. pStats = &mapIPs[ nCollectedStats ];
  748. }
  749. //filter out users that didn't make any calls if appropriate
  750. if( ( eFilter == eDumpCaller_Calls ) && ( !pStats || ( pStats->m_nCalls == 0 ) ) )
  751. continue;
  752. rp.StrValue( CFmtStr( "%u.%u.%u.%u", iptod( nIP ) ), CFmtStr( "webapi_account_dump_ip %u", nIP ) );
  753. rp.IntValue( ( pStats ) ? pStats->m_nCalls : 0 );
  754. rp.IntValue( ( pStats ) ? pStats->m_nBytes : 0 );
  755. rp.IntValue( m_IPCallers[ nCurrCaller ].m_nBlockedCalls );
  756. rp.IntValue( m_IPCallers[ nCurrCaller ].m_eLevel );
  757. rp.IntValue( ( pStats ) ? pStats->m_nFunctions : 0 );
  758. rp.IntValue( ( int64 )( ( ( pStats ) ? pStats->m_nBytes : 0 ) / fUpTimeHrs ) );
  759. rp.IntValue( ( int64 )( ( ( pStats ) ? pStats->m_nCalls : 0 ) / fUpTimeHrs ) );
  760. rp.IntValue( ( pStats && pStats->m_nCalls > 0 ) ? ( pStats->m_nBytes / pStats->m_nCalls ) / 1024 : 0 );
  761. rp.CommitRow();
  762. }
  763. const char* pszSort = "MB/h";
  764. if( eFilter == eDumpCaller_Blocked )
  765. pszSort = "Blocked";
  766. else if( eFilter == eDumpCaller_Status )
  767. pszSort = "Access";
  768. rp.SortReport( pszSort );
  769. rp.PrintReport( SPEW_CONSOLE );
  770. }
  771. void CWebAPIAccountTracker::DumpFunctions() const
  772. {
  773. CUtlVector< SReportRow > vFuncs;
  774. vFuncs.EnsureCapacity( m_Functions.Count() );
  775. FOR_EACH_MAP_FAST( m_Functions, i )
  776. {
  777. vFuncs.AddToTail( SReportRow( ( const char* )m_Functions.Key( i ), m_Functions[ i ]->m_nTotalCalls, m_Functions[ i ]->m_nTotalBytes ) );
  778. }
  779. PrintReport( vFuncs );
  780. }
  781. void CWebAPIAccountTracker::DumpProfile( bool bAllTime ) const
  782. {
  783. //accumulate totals so we can do percentage
  784. uint32 nTotalCalls = 0;
  785. uint64 nTotalBytes = 0;
  786. FOR_EACH_MAP_FAST( m_Functions, nFunction )
  787. {
  788. if( bAllTime )
  789. {
  790. nTotalCalls += m_Functions[ nFunction ]->m_nTotalCalls;
  791. nTotalBytes += m_Functions[ nFunction ]->m_nTotalBytes;
  792. }
  793. else
  794. {
  795. nTotalCalls += m_Functions[ nFunction ]->m_nProfileCalls;
  796. nTotalBytes += m_Functions[ nFunction ]->m_nProfileBytes;
  797. }
  798. }
  799. //determine how much time we are covering, and come up with a scale to normalize the values
  800. uint64 nSampleMicroS = ( bAllTime ) ? ( uint64 )GGCBase()->GetGCUpTime() * 1000000 : m_ProfileTime.CServerMicroSecsPassed();
  801. double fToPS = ( nSampleMicroS > 0 ) ? 1000000.0 / ( double )nSampleMicroS : 1.0;
  802. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, "Web API Profile: Sampled %.2f seconds\n", nSampleMicroS / ( 1000.0 * 1000.0 ) );
  803. CGCReportPrinter rp;
  804. rp.AddStringColumn( "Web API Name" );
  805. rp.AddIntColumn( "MB", CGCReportPrinter::eSummary_Total, CGCReportPrinter::eIntDisplay_Memory_MB );
  806. rp.AddFloatColumn( "%", CGCReportPrinter::eSummary_Total, 1 );
  807. rp.AddFloatColumn( "KBPS", CGCReportPrinter::eSummary_Total, 1 );
  808. rp.AddIntColumn( "Calls", CGCReportPrinter::eSummary_Total );
  809. rp.AddFloatColumn( "%", CGCReportPrinter::eSummary_Total, 1 );
  810. rp.AddFloatColumn( "CallsPS", CGCReportPrinter::eSummary_Total, 1 );
  811. rp.AddIntColumn( "MaxKB", CGCReportPrinter::eSummary_Max );
  812. FOR_EACH_MAP_FAST( m_Functions, nFunction )
  813. {
  814. const SFunctionStats* pFunc = m_Functions[ nFunction ];
  815. uint32 nCalls = ( bAllTime ) ? pFunc->m_nTotalCalls : pFunc->m_nProfileCalls;
  816. uint32 nMax = ( bAllTime ) ? pFunc->m_nMaxBytes : pFunc->m_nProfileMaxBytes;
  817. uint64 nBytes = ( bAllTime ) ? pFunc->m_nTotalBytes : pFunc->m_nProfileBytes;
  818. rp.StrValue( ( const char* )m_Functions.Key( nFunction ) );
  819. rp.IntValue( nBytes );
  820. rp.FloatValue( ( 100.0 * nBytes ) / nTotalBytes );
  821. rp.FloatValue( ( nBytes / 1024.0 ) * fToPS );
  822. rp.IntValue( nCalls );
  823. rp.FloatValue( ( 100.0 * nCalls ) / nTotalCalls );
  824. rp.FloatValue( nCalls * fToPS );
  825. rp.IntValue( nMax / 1024 );
  826. rp.CommitRow();
  827. }
  828. rp.SortReport( "KBPS" );
  829. rp.PrintReport( SPEW_CONSOLE );
  830. }
  831. void CWebAPIAccountTracker::PrintReport( const CUtlVector< SReportRow >& vec )
  832. {
  833. CGCReportPrinter rp;
  834. //now print it out based upon the type
  835. rp.AddStringColumn( "Function" );
  836. rp.AddIntColumn( "Calls", CGCReportPrinter::eSummary_Total );
  837. rp.AddIntColumn( "MB", CGCReportPrinter::eSummary_Total, CGCReportPrinter::eIntDisplay_Memory_MB );
  838. rp.AddFloatColumn( "Calls/h", CGCReportPrinter::eSummary_Total, 0 );
  839. rp.AddFloatColumn( "MB/h", CGCReportPrinter::eSummary_Total );
  840. const double fUpTimeHrs = MAX( GGCBase()->GetGCUpTime() / 3600.0, 0.01 );
  841. for( int i = 0; i < vec.Count(); i++ )
  842. {
  843. rp.StrValue( vec[ i ].m_pszFunction, CFmtStr( "webapi_account_dump_function_callers %s", vec[ i ].m_pszFunction ) );
  844. rp.IntValue( vec[ i ].m_nCalls );
  845. rp.IntValue( vec[ i ].m_nSize );
  846. rp.FloatValue( vec[ i ].m_nCalls / fUpTimeHrs );
  847. rp.FloatValue( ( vec[ i ].m_nSize / ( 1024.0 * 1024.0 ) ) / fUpTimeHrs );
  848. rp.CommitRow();
  849. }
  850. rp.SortReport( "MB/h" );
  851. rp.PrintReport( SPEW_CONSOLE );
  852. }
  853. GC_CON_COMMAND( webapi_account_dump_steam_servers, "Dumps the ID listings of the various steam servers encoded in the IP address of Steam requests" )
  854. {
  855. g_WebAPIAccountTracker.DumpSteamServers();
  856. }
  857. GC_CON_COMMAND( webapi_account_dump_callers, "Dumps the most frequent callers of web api's for the current run of the GC" )
  858. {
  859. g_WebAPIAccountTracker.DumpTotalCallers( CWebAPIAccountTracker::eDumpCaller_Calls );
  860. }
  861. GC_CON_COMMAND( webapi_account_dump_ips, "Dumps the most frequent ip callers of web api's for the current run of the GC" )
  862. {
  863. g_WebAPIAccountTracker.DumpTotalIPs( CWebAPIAccountTracker::eDumpCaller_Calls );
  864. }
  865. GC_CON_COMMAND( webapi_account_dump_blocked_callers, "Dumps the callers that have been blocked and how many calls have been blocked" )
  866. {
  867. g_WebAPIAccountTracker.DumpTotalCallers( CWebAPIAccountTracker::eDumpCaller_Blocked );
  868. }
  869. GC_CON_COMMAND( webapi_account_dump_caller_access, "Dumps the access rights of any caller that is not the default rate limiting" )
  870. {
  871. g_WebAPIAccountTracker.DumpTotalCallers( CWebAPIAccountTracker::eDumpCaller_Status );
  872. }
  873. GC_CON_COMMAND( webapi_account_dump_functions, "Dumps the most frequently called web api functions" )
  874. {
  875. g_WebAPIAccountTracker.DumpFunctions();
  876. }
  877. GC_CON_COMMAND_PARAMS( webapi_account_dump_function_callers, 1, "<function name> - Dumps the most frequent callers of functions that match the provided substring" )
  878. {
  879. g_WebAPIAccountTracker.DumpTotalCallers( CWebAPIAccountTracker::eDumpCaller_Calls, args[ 1 ] );
  880. }
  881. GC_CON_COMMAND_PARAMS( webapi_account_dump_caller, 1, "<caller account> - Dumps the functions that the provided account ID has been calling the most" )
  882. {
  883. g_WebAPIAccountTracker.DumpCaller( ( AccountID_t )V_atoui64( args[ 1 ] ) );
  884. }
  885. GC_CON_COMMAND_PARAMS( webapi_account_dump_ip, 1, "<ip> - Dumps the functions that the provided ip has been calling the most" )
  886. {
  887. g_WebAPIAccountTracker.DumpIP( ( AccountID_t )V_atoui64( args[ 1 ] ) );
  888. }
  889. GC_CON_COMMAND( webapi_account_reset_stats, "Forces a reset of all stats collected for web api account stats" )
  890. {
  891. g_WebAPIAccountTracker.ResetStats();
  892. }
  893. //utility class for dumping out the profile results after time has expired
  894. static void DumpWebAPIProfile()
  895. {
  896. g_WebAPIAccountTracker.DumpProfile( false );
  897. }
  898. GC_CON_COMMAND_PARAMS( webapi_profile, 1, "<seconds to profile> Turns on web api profiling for N seconds and dumps the results" )
  899. {
  900. float fSeconds = MAX( 1.0f, atof( args[ 1 ] ) );
  901. g_WebAPIAccountTracker.ResetProfileStats();
  902. static CGlobalScheduledFunction s_DumpProfile;
  903. s_DumpProfile.ScheduleMS( DumpWebAPIProfile, fSeconds * 1000.0f );
  904. }
  905. //console commands to control web API profiling
  906. GC_CON_COMMAND( webapi_profile_reset, "Turns on web api profiling" )
  907. {
  908. g_WebAPIAccountTracker.ResetProfileStats();
  909. }
  910. GC_CON_COMMAND( webapi_profile_dump, "Displays stats collected while web api profiling was enabled" )
  911. {
  912. g_WebAPIAccountTracker.DumpProfile( false );
  913. }
  914. GC_CON_COMMAND( webapi_profile_dump_total, "Displays stats collected while web api profiling was enabled" )
  915. {
  916. g_WebAPIAccountTracker.DumpProfile( true );
  917. }
  918. //-----------------------------------------------------------------------------
  919. // Purpose: Sends a message and waits for a response
  920. // Input: steamIDTarget - The entity this message is going to
  921. // msgOut - The message to send
  922. // nTimeoutSec - Number of seconds to wait for a response
  923. // pMsgIn - Pointer to the message that will contain the response
  924. // eMsg - The type of message the response should be
  925. // Returns: True is the response was received, false otherwise. The contents
  926. // of pMsgIn will be valid only if the function returns true.
  927. //-----------------------------------------------------------------------------
  928. bool CGCJob::BYldSendMessageAndGetReply( CSteamID &steamIDTarget, CGCMsgBase &msgOut, uint nTimeoutSec, CGCMsgBase *pMsgIn, MsgType_t eMsg )
  929. {
  930. IMsgNetPacket *pNetPacket = NULL;
  931. if ( !BYldSendMessageAndGetReply( steamIDTarget, msgOut, nTimeoutSec, &pNetPacket ) )
  932. return false;
  933. pMsgIn->SetPacket( pNetPacket );
  934. if ( pMsgIn->Hdr().m_eMsg != eMsg )
  935. return false;
  936. return true;
  937. }
  938. //-----------------------------------------------------------------------------
  939. // Purpose: Sends a message and waits for a response
  940. // Input: steamIDTarget - The entity this message is going to
  941. // msgOut - The message to send
  942. // nTimeoutSec - Number of seconds to wait for a response
  943. // ppNetPackets - Pointer to a IMsgNetPacket pointer which will contain
  944. // the response
  945. // Returns: True is the response was received, false otherwise. *ppNetPacket
  946. // will point to a valid packet only if the function returns true.
  947. //-----------------------------------------------------------------------------
  948. bool CGCJob::BYldSendMessageAndGetReply( CSteamID &steamIDTarget, CGCMsgBase &msgOut, uint nTimeoutSec, IMsgNetPacket **ppNetPacket )
  949. {
  950. msgOut.ExpectingReply( GetJobID() );
  951. if ( !m_pGC->BSendGCMsgToClient( steamIDTarget, msgOut ) )
  952. return false;
  953. SetJobTimeout( nTimeoutSec );
  954. return BYieldingWaitForMsg( ppNetPacket );
  955. }
  956. //-----------------------------------------------------------------------------
  957. // Purpose: BYldSendMessageAndGetReply, ProtoBuf edition
  958. //-----------------------------------------------------------------------------
  959. bool CGCJob::BYldSendMessageAndGetReply( CSteamID &steamIDTarget, CProtoBufMsgBase &msgOut, uint nTimeoutSec, CProtoBufMsgBase *pMsgIn, MsgType_t eMsg )
  960. {
  961. IMsgNetPacket *pNetPacket = NULL;
  962. if ( !BYldSendMessageAndGetReply( steamIDTarget, msgOut, nTimeoutSec, &pNetPacket ) )
  963. return false;
  964. pMsgIn->InitFromPacket( pNetPacket );
  965. if ( pMsgIn->GetEMsg() != eMsg )
  966. return false;
  967. return true;
  968. }
  969. bool CGCJob::BYldSendMessageAndGetReply( CSteamID &steamIDTarget, CProtoBufMsgBase &msgOut, uint nTimeoutSec, IMsgNetPacket **ppNetPacket )
  970. {
  971. msgOut.ExpectingReply( GetJobID() );
  972. if ( !m_pGC->BSendGCMsgToClient( steamIDTarget, msgOut ) )
  973. return false;
  974. SetJobTimeout( nTimeoutSec );
  975. return BYieldingWaitForMsg( ppNetPacket );
  976. }
  977. //-----------------------------------------------------------------------------
  978. // Purpose: Constructor
  979. //-----------------------------------------------------------------------------
  980. CGCWGJob::CGCWGJob( CGCBase *pGCBase )
  981. : CGCJob( pGCBase ),
  982. m_steamID( k_steamIDNil ),
  983. m_pWebApiFunc( NULL )
  984. {
  985. }
  986. //-----------------------------------------------------------------------------
  987. // Purpose: Destructor
  988. //-----------------------------------------------------------------------------
  989. CGCWGJob::~CGCWGJob()
  990. {
  991. }
  992. //-----------------------------------------------------------------------------
  993. // Purpose: Receive a k_EMsgWGRequest and manage keyvalues serialization/deserialization
  994. //-----------------------------------------------------------------------------
  995. bool CGCWGJob::BYieldingRunGCJob( IMsgNetPacket * pNetPacket )
  996. {
  997. CGCMsg<MsgGCWGRequest_t> msg( pNetPacket );
  998. CUtlString strRequestName;
  999. KeyValuesAD pkvRequest( "request" );
  1000. KeyValuesAD pkvResponse( "response" );
  1001. m_steamID = CSteamID( msg.Body().m_ulSteamID );
  1002. msg.BReadStr( &strRequestName );
  1003. //deserialize KV
  1004. m_bufRequest.Clear();
  1005. m_bufRequest.Put( msg.PubReadCur(), msg.Body().m_cubKeyValues );
  1006. KVPacker packer;
  1007. if ( !packer.ReadAsBinary( pkvRequest, m_bufRequest ) )
  1008. {
  1009. AssertMsg( false, "Failed to deserialize key values from WG request" );
  1010. CGCWGJobMgr::SendErrorMessage( msg, "Failed to deserialize key values from WG request", k_EResultInvalidParam );
  1011. return false;
  1012. }
  1013. if( !BVerifyParams( msg, pkvRequest, m_pWebApiFunc ) )
  1014. return false;
  1015. bool bRet = BYieldingRunJobFromRequest( pkvRequest, pkvResponse );
  1016. // request failed, set success for wg
  1017. if ( pkvResponse->IsEmpty( "success" ) )
  1018. {
  1019. pkvResponse->SetInt( "success", bRet ? k_EResultOK : k_EResultFail );
  1020. }
  1021. // send response msg
  1022. CGCWGJobMgr::SendResponse( msg, pkvResponse, bRet );
  1023. return true;
  1024. }
  1025. bool CGCWGJob::BVerifyParams( const CGCMsg<MsgGCWGRequest_t> & msg, KeyValues *pkvRequest, const WebApiFunc_t * pWebApiFunc )
  1026. {
  1027. // we've found the function; now validate the call
  1028. for ( int i = 0; i < Q_ARRAYSIZE( pWebApiFunc->m_rgParams ); i++ )
  1029. {
  1030. if ( !pWebApiFunc->m_rgParams[i].m_pchParam )
  1031. break;
  1032. // just simple validation for now; make sure the key exists
  1033. if ( !pWebApiFunc->m_rgParams[i].m_bOptional && !pkvRequest->FindKey( pWebApiFunc->m_rgParams[i].m_pchParam ) )
  1034. {
  1035. CGCWGJobMgr::SendErrorMessage( msg,
  1036. CFmtStr( "Error: Missing parameter '%s' from web request '%s'\n", pWebApiFunc->m_rgParams[i].m_pchParam, pWebApiFunc->m_pchRequestName ),
  1037. k_EResultInvalidParam );
  1038. EmitWarning( SPEW_GC, 2, "Error: Missing parameter '%s' from web request '%s'\n", pWebApiFunc->m_rgParams[i].m_pchParam, pWebApiFunc->m_pchRequestName );
  1039. return false;
  1040. }
  1041. }
  1042. return true;
  1043. }
  1044. void CGCWGJob::SetErrorMessage( KeyValues *pkvErr, const char *pchErrorMsg, int32 nResult )
  1045. {
  1046. CGCWGJobMgr::SetErrorMessage( pkvErr, pchErrorMsg, nResult );
  1047. }
  1048. //-----------------------------------------------------------------------------
  1049. // CGCJobVerifySession - A job that asks steam if a given user is still connected
  1050. // and cleans up the session if the user is gone
  1051. //-----------------------------------------------------------------------------
  1052. bool CGCJobVerifySession::BYieldingRunGCJob()
  1053. {
  1054. if ( !m_pGC->BYieldingLockSteamID( m_steamID, __FILE__, __LINE__ ) )
  1055. return false;
  1056. m_pGC->YieldingRequestSession( m_steamID );
  1057. return true;
  1058. }
  1059. //-----------------------------------------------------------------------------
  1060. // Purpose: Constructor
  1061. //-----------------------------------------------------------------------------
  1062. CWebAPIJob::CWebAPIJob( CGCBase *pGC, EWebAPIOutputFormat eDefaultOutputFormat )
  1063. : CGCJob( pGC ), m_eDefaultOutputFormat( eDefaultOutputFormat )
  1064. {
  1065. }
  1066. //-----------------------------------------------------------------------------
  1067. // Purpose: Destructor
  1068. //-----------------------------------------------------------------------------
  1069. CWebAPIJob::~CWebAPIJob()
  1070. {
  1071. }
  1072. //-----------------------------------------------------------------------------
  1073. // Called to handle converting a web api response to a serialized result and free the object as well on a background thread
  1074. class CEmitWebAPIData
  1075. {
  1076. public:
  1077. CEmitWebAPIData( CMsgHttpRequest* pRequest) : m_bResult( false ), m_Request( pRequest ) {}
  1078. //inputs
  1079. CHTTPRequest m_Request;
  1080. //outputs
  1081. CHTTPResponse m_Response;
  1082. std::string m_sSerializedResponse;
  1083. bool m_bResult;
  1084. };
  1085. //the worker thread function that is responsible for serializing the response from web api values to a text buffer, freeing the web api value tree, and serializing the message to a protobuf for
  1086. //direct sending. If this function fails (a false response value) the message will not be serialized
  1087. static void ThreadedEmitFormattedOutputWrapperAndFreeResponse( CWebAPIResponse *pResponse, CEmitWebAPIData* pEmitData, EWebAPIOutputFormat eDefaultFormat, size_t unMaxResultSize )
  1088. {
  1089. // parse the output type that we want the result to be in
  1090. EWebAPIOutputFormat eOutputFormat = eDefaultFormat;
  1091. const char *pszParamOutput = pEmitData->m_Request.GetGETParamString( "format", NULL );
  1092. if ( !pszParamOutput )
  1093. pszParamOutput = pEmitData->m_Request.GetPOSTParamString( "format", NULL );
  1094. if ( pszParamOutput )
  1095. {
  1096. if ( Q_stricmp( pszParamOutput, "xml" ) == 0 )
  1097. eOutputFormat = k_EWebAPIOutputFormat_XML;
  1098. else if ( Q_stricmp( pszParamOutput, "vdf" ) == 0 )
  1099. eOutputFormat = k_EWebAPIOutputFormat_VDF;
  1100. else if ( Q_stricmp( pszParamOutput, "json" ) == 0 )
  1101. eOutputFormat = k_EWebAPIOutputFormat_JSON;
  1102. }
  1103. pEmitData->m_bResult = pResponse->BEmitFormattedOutput( eOutputFormat, *( pEmitData->m_Response.GetBodyBuffer() ), unMaxResultSize );
  1104. delete pResponse;
  1105. //update the response code on the output (can probably be done elsewhere), but must be done before we pack the message below
  1106. switch( eOutputFormat )
  1107. {
  1108. case k_EWebAPIOutputFormat_JSON:
  1109. pEmitData->m_Response.SetResponseHeaderValue( "content-type", "application/json; charset=UTF-8" );
  1110. break;
  1111. case k_EWebAPIOutputFormat_XML:
  1112. pEmitData->m_Response.SetResponseHeaderValue( "content-type", "text/xml; charset=UTF-8" );
  1113. break;
  1114. case k_EWebAPIOutputFormat_VDF:
  1115. pEmitData->m_Response.SetResponseHeaderValue( "content-type", "text/vdf; charset=UTF-8" );
  1116. break;
  1117. default:
  1118. break;
  1119. }
  1120. //if successful, we can go ahead and convert this all the way into a completely serialized form for sending over the wire
  1121. if( pEmitData->m_bResult )
  1122. {
  1123. CMsgHttpResponse msgResponse;
  1124. pEmitData->m_Response.SerializeIntoProtoBuf( msgResponse );
  1125. msgResponse.SerializeToString( &pEmitData->m_sSerializedResponse );
  1126. }
  1127. }
  1128. //called to respond to a web api request with the specified response value
  1129. static void WebAPIRespondToRequest( const char* pszName, uint32 nSenderIP, const CHTTPResponse& response, const CProtoBufMsg< CMsgWebAPIRequest >& msg )
  1130. {
  1131. VPROF_BUDGET( "WebAPI - sending result", VPROF_BUDGETGROUP_STEAM );
  1132. CProtoBufMsg<CMsgHttpResponse> msgResponse( k_EGCMsgWebAPIJobRequestHttpResponse, msg );
  1133. response.SerializeIntoProtoBuf( msgResponse.Body() );
  1134. GGCBase()->BReplyToMessage( msgResponse, msg );
  1135. //track this message in the web API response
  1136. g_WebAPIAccountTracker.TrackFunction( msg.Body().api_key().account_id(), nSenderIP, pszName, msgResponse.Body().body().size() );
  1137. }
  1138. //responses to the web api message with an error code
  1139. static void WebAPIRespondWithError( const char* pszName, uint32 nSenderIP, const CProtoBufMsg< CMsgWebAPIRequest >& msg, EHTTPStatusCode statusCode )
  1140. {
  1141. CHTTPResponse response;
  1142. response.SetStatusCode( statusCode );
  1143. WebAPIRespondToRequest( pszName, nSenderIP, response, msg );
  1144. }
  1145. //parses an IP address out of the provided string. This will be the last IP address in the list
  1146. static uint32 ParseIPAddrFromForwardHeader( const char* pszHeader )
  1147. {
  1148. //find the last comma in the string, our IP address follows that
  1149. const char* pszStart = V_strrchr( pszHeader, ',' );
  1150. //if no match, then we just have the ip address, otherwise advance past the comma
  1151. if( !pszStart )
  1152. pszStart = pszHeader;
  1153. else
  1154. pszStart++;
  1155. //skip leading spaces
  1156. while( V_isspace( *pszStart ) )
  1157. pszStart++;
  1158. return ntohl( inet_addr( pszStart ) );
  1159. }
  1160. //-----------------------------------------------------------------------------
  1161. // Purpose: Receive a k_EMsgWebAPIJobRequest and manage serialization/deserialization to
  1162. // web request/response objects
  1163. //-----------------------------------------------------------------------------
  1164. bool CWebAPIJob::BYieldingRunJobFromMsg( IMsgNetPacket * pNetPacket )
  1165. {
  1166. VPROF_BUDGET( "WebAPI", VPROF_BUDGETGROUP_STEAM );
  1167. CProtoBufMsg<CMsgWebAPIRequest> msg( pNetPacket );
  1168. //make sure all the required parameters were present
  1169. bool bMsgParsedOK = msg.Body().has_api_key()
  1170. && msg.Body().has_interface_name()
  1171. && msg.Body().has_method_name()
  1172. && msg.Body().has_request()
  1173. && msg.Body().has_version();
  1174. if( !bMsgParsedOK )
  1175. {
  1176. WebAPIRespondWithError( GetName(), 0, msg, k_EHTTPStatusCode400BadRequest );
  1177. return true;
  1178. }
  1179. //determine the account that sent this request
  1180. const AccountID_t nSenderAccountID = msg.Body().api_key().account_id();
  1181. uint32 nSenderIP = 0;
  1182. //if this isn't a system request, try and identify the IP address of the sender so we can rate limit accordingly
  1183. if( nSenderAccountID != 0 )
  1184. {
  1185. const int nNumHeaders = msg.Body().request().headers_size();
  1186. for( int nHeader = 0; nHeader < nNumHeaders; nHeader++ )
  1187. {
  1188. if( strcmp( msg.Body().request().headers( nHeader ).name().c_str(), "X-Forwarded-For" ) != 0 )
  1189. continue;
  1190. //this is our IP address
  1191. nSenderIP = ParseIPAddrFromForwardHeader( msg.Body().request().headers( nHeader ).value().c_str() );
  1192. }
  1193. //see if we have the kill switch turned on
  1194. if( webapi_kill_switch.GetBool() )
  1195. {
  1196. if( webapi_kill_switch_error_response.GetBool() )
  1197. WebAPIRespondWithError( GetName(), nSenderIP, msg, k_EHTTPStatusCode503ServiceUnavailable );
  1198. return true;
  1199. }
  1200. }
  1201. else
  1202. {
  1203. // !FIXME! DOTAMERGE
  1204. // //determine the priority of this steam request
  1205. // uint32 nPriority;
  1206. // nSenderIP = GetSteamRequestIPAddress( msg.Body().request(), nPriority );
  1207. // //and allow for a kill switch based upon this priority
  1208. // if( ( ( nPriority == CWebAPIAccountTracker::k_nSteamIP_Low ) && !webapi_enable_steam_low.GetBool() ) ||
  1209. // ( ( nPriority == CWebAPIAccountTracker::k_nSteamIP_Normal ) && !webapi_enable_steam_normal.GetBool() ) ||
  1210. // ( ( nPriority == CWebAPIAccountTracker::k_nSteamIP_High ) && !webapi_enable_steam_high.GetBool() ) )
  1211. // {
  1212. // if( webapi_kill_switch_error_response.GetBool() )
  1213. // WebAPIRespondWithError( GetName(), 0, msg );
  1214. // return true;
  1215. // }
  1216. }
  1217. //track stats for this account, and handle rate limiting
  1218. if( !g_WebAPIAccountTracker.TrackUser( nSenderAccountID, nSenderIP ) )
  1219. {
  1220. if( webapi_kill_switch_error_response.GetBool() )
  1221. WebAPIRespondWithError( GetName(), nSenderIP, msg, k_EHTTPStatusCode429TooManyRequests );
  1222. return true;
  1223. }
  1224. //allocate the data that we'll use to fill out the request and send it to the background thread for work
  1225. CPlainAutoPtr< CEmitWebAPIData > pEmitData( new CEmitWebAPIData( const_cast< CMsgHttpRequest* >( &msg.Body().request() ) ) );
  1226. CHTTPRequest& request = pEmitData->m_Request;
  1227. CHTTPResponse& response = pEmitData->m_Response;
  1228. {
  1229. VPROF_BUDGET( "WebAPI - Prepare msg", VPROF_BUDGETGROUP_STEAM );
  1230. m_webAPIKey.DeserializeFromProtoBuf( msg.Body().api_key() );
  1231. }
  1232. CPlainAutoPtr< CWebAPIResponse > pwebAPIResponse( new CWebAPIResponse() );
  1233. {
  1234. VPROF_BUDGET( "WebAPI - Process msg", VPROF_BUDGETGROUP_STEAM );
  1235. if( !BYieldingRunJobFromAPIRequest( msg.Body().interface_name().c_str(), msg.Body().method_name().c_str(), msg.Body().version(), &request, &response, pwebAPIResponse.Get() ) )
  1236. {
  1237. //error executing our job
  1238. WebAPIRespondWithError( GetName(), nSenderIP, msg, k_EHTTPStatusCode500InternalServerError );
  1239. return false;
  1240. }
  1241. }
  1242. // !FIXME! DOTAMERGE
  1243. // //see if they want to re-route this request
  1244. // if( m_nRerouteRequest >= 0 )
  1245. // {
  1246. // if( ( uint32 )m_nRerouteRequest == m_pGC->GetGCDirIndex() )
  1247. // {
  1248. // AssertMsg( false, "Error: WebAPI %s attempting to re-route a web api message to itself (%d)", GetName(), m_nRerouteRequest );
  1249. // }
  1250. // else
  1251. // {
  1252. // //route to the other GC and discard this message
  1253. // CProtoBufMsg< CMsgGCMsgWebAPIJobRequestForwardResponse > msgRoute( k_EGCMsgWebAPIJobRequestForwardResponse );
  1254. // msgRoute.Body().set_dir_index( m_nRerouteRequest );
  1255. // m_pGC->BReplyToMessage( msgRoute, msg );
  1256. // }
  1257. // return true;
  1258. // }
  1259. VPROF_BUDGET( "WebAPI - Emitting result", VPROF_BUDGETGROUP_STEAM );
  1260. response.SetStatusCode( pwebAPIResponse->GetStatusCode() );
  1261. response.SetExpirationHeaderDeltaFromNow( pwebAPIResponse->GetExpirationSeconds() );
  1262. if( pwebAPIResponse->GetLastModified() )
  1263. response.SetHeaderTimeValue( "last-modified", pwebAPIResponse->GetLastModified() );
  1264. // if we aren't allowed to have a message body on this, simply send the result back now
  1265. if( !CHTTPUtil::BStatusCodeAllowsBody( pwebAPIResponse->GetStatusCode() ) )
  1266. {
  1267. AssertMsg( pwebAPIResponse->GetRootValue() == NULL, "Response HTTP status code %d doesn't allow a body, but one was present", pwebAPIResponse->GetStatusCode() );
  1268. //since we didn't have a body to serialize, we need to handle sending just the response
  1269. WebAPIRespondToRequest( GetName(), nSenderIP, response, msg );
  1270. return true;
  1271. }
  1272. //let the job convert the formatting and free our response for us (quite costly). Note that we detach the web API response since this job will free it
  1273. bool bThreadFuncSucceeded = BYieldingWaitForThreadFunc( CreateFunctor( ThreadedEmitFormattedOutputWrapperAndFreeResponse, pwebAPIResponse.Detach(), pEmitData.Get(), m_eDefaultOutputFormat, (size_t)cv_webapi_result_size_limit.GetInt() ) );
  1274. //if we called the function successfully and had a valid result, just send back the preserialized body
  1275. if( bThreadFuncSucceeded && pEmitData->m_bResult )
  1276. {
  1277. m_pGC->BReplyToMessageWithPreSerializedBody( k_EGCMsgWebAPIJobRequestHttpResponse, msg, ( const byte* )pEmitData->m_sSerializedResponse.c_str(), pEmitData->m_sSerializedResponse.size() );
  1278. g_WebAPIAccountTracker.TrackFunction( nSenderAccountID, nSenderIP, GetName(), pEmitData->m_sSerializedResponse.size() );
  1279. }
  1280. else
  1281. {
  1282. //we failed to generate a response, see if we ran out of space
  1283. if( response.GetBodyBuffer()->TellMaxPut() > cv_webapi_result_size_limit.GetInt() )
  1284. {
  1285. // !FIXME! DOTAMERGE
  1286. //CGCAlertInfo alert( "WebAPIResponseSize", "WebAPI request %s failed to emit because it exceeded %d characters", request.GetURL(), cv_webapi_result_size_limit.GetInt() );
  1287. //
  1288. //switch( request.GetEHTTPMethod() )
  1289. //{
  1290. //case k_EHTTPMethodGET:
  1291. // {
  1292. // const uint32 nNumParams = request.GetGETParamCount();
  1293. // for( uint32 nParam = 0; nParam < nNumParams; nParam++ )
  1294. // {
  1295. // alert.AddExtendedInfoLine( "%s=%s", request.GetGETParamName( nParam ), request.GetGETParamValue( nParam ) );
  1296. // }
  1297. // }
  1298. // break;
  1299. //case k_EHTTPMethodPOST:
  1300. // {
  1301. // const uint32 nNumParams = request.GetPOSTParamCount();
  1302. // for( uint32 nParam = 0; nParam < nNumParams; nParam++ )
  1303. // {
  1304. // alert.AddExtendedInfoLine( "%s=%s", request.GetPOSTParamName( nParam ), request.GetPOSTParamValue( nParam ) );
  1305. // }
  1306. // }
  1307. // break;
  1308. //}
  1309. //
  1310. //GGCBase()->PostAlert( alert );
  1311. GGCBase()->PostAlert( k_EAlertTypeInfo, false, CFmtStr( "WebAPI request %s failed to emit because it exceeded %d characters", request.GetURL(), cv_webapi_result_size_limit.GetInt() ) );
  1312. }
  1313. else
  1314. {
  1315. EG_WARNING( SPEW_GC, "WebAPI %s - request %s failed to emit for unknown reason\n", GetName(), request.GetURL() );
  1316. }
  1317. //make sure that they get an error code back
  1318. WebAPIRespondWithError( GetName(), nSenderIP, msg, k_EHTTPStatusCode500InternalServerError );
  1319. }
  1320. return true;
  1321. }
  1322. void CWebAPIJob::AddLocalizedString( CWebAPIValues *pOutDefn, const char *pchFieldName, const char *pchKeyName, ELanguage eLang, bool bReturnTokenIfNotFound )
  1323. {
  1324. // NULL keys we just skip
  1325. if( !pchKeyName )
  1326. return;
  1327. const char *pchValue;
  1328. if( eLang == k_Lang_None )
  1329. {
  1330. pchValue = pchKeyName;
  1331. }
  1332. else
  1333. {
  1334. pchValue = GGCBase()->LocalizeToken( pchKeyName, eLang, bReturnTokenIfNotFound );
  1335. }
  1336. if( pchValue )
  1337. {
  1338. pOutDefn->CreateChildObject( pchFieldName )->SetStringValue( pchValue );
  1339. }
  1340. }
  1341. //-----------------------------------------------------------------------------
  1342. // Purpose: A wrapper to call BEmitFormattedOutput and pass out a return value
  1343. // since functors don't make return values available.
  1344. //-----------------------------------------------------------------------------
  1345. /*static*/ void CWebAPIJob::ThreadedEmitFormattedOutputWrapper( CWebAPIResponse *pResponse, EWebAPIOutputFormat eFormat, CUtlBuffer *poutputBuffer, size_t unMaxResultSize, bool *pbResult )
  1346. {
  1347. *pbResult = pResponse->BEmitFormattedOutput( eFormat, *poutputBuffer, unMaxResultSize );
  1348. }
  1349. } // namespace GCSDK