/** * Perform an HTTP request * * @param $method String: HTTP method. Usually GET/POST * @param $url String: full URL to act on. If protocol-relative, will be expanded to an http:// URL * @param $options Array: options to pass to MWHttpRequest object. * Possible keys for the array: * - timeout Timeout length in seconds * - postData An array of key-value pairs or a url-encoded form data * - proxy The proxy to use. * Will use $wgHTTPProxy (if set) otherwise. * - noProxy Override $wgHTTPProxy (if set) and don't use any proxy at all. * - sslVerifyHost (curl only) Verify hostname against certificate * - sslVerifyCert (curl only) Verify SSL certificate * - caInfo (curl only) Provide CA information * - maxRedirects Maximum number of redirects to follow (defaults to 5) * - followRedirects Whether to follow redirects (defaults to false). * Note: this should only be used when the target URL is trusted, * to avoid attacks on intranet services accessible by HTTP. * - userAgent A user agent, if you want to override the default * MediaWiki/$wgVersion * - headers Additional headers for request * - returnInstance If set the method will return MWHttpRequest instance instead of string|boolean * @return Mixed: (bool)false on failure or a string on success or MWHttpRequest instance if returnInstance option is set */ public static function request($method, $url, $options = array()) { $fname = __METHOD__ . '::' . $method; wfProfileIn($fname); wfDebug("HTTP: {$method}: {$url}\n"); $options['method'] = strtoupper($method); if (!isset($options['timeout'])) { $options['timeout'] = 'default'; } $req = MWHttpRequest::factory($url, $options); // Wikia change - @author: suchy - begin if (isset($options['headers']) && is_array($options['headers'])) { foreach ($options['headers'] as $name => $value) { $req->setHeader($name, $value); } } // Wikia change - end if (isset($options['userAgent'])) { $req->setUserAgent($options['userAgent']); } // Wikia change - @author: mech - begin $requestTime = microtime(true); // Wikia change - end $status = $req->execute(); // Wikia change - @author: mech - begin // log all the requests we make $caller = wfGetCallerClassMethod([__CLASS__, 'Hooks', 'ApiService', 'Solarium_Client', 'Solarium_Client_Adapter_Curl']); $isOk = $status->isOK(); if (class_exists('Wikia\\Logger\\WikiaLogger')) { $requestTime = (int) ((microtime(true) - $requestTime) * 1000.0); $backendTime = $req->getResponseHeader('x-backend-response-time') ?: 0; $params = ['statusCode' => $req->getStatus(), 'reqMethod' => $method, 'reqUrl' => $url, 'caller' => $caller, 'isOk' => $isOk, 'requestTimeMS' => $requestTime, 'backendTimeMS' => intval(1000 * $backendTime)]; if (!$isOk) { $params['statusMessage'] = $status->getMessage(); } \Wikia\Logger\WikiaLogger::instance()->debug('Http request', $params); } // Wikia change - @author: nAndy - begin // Introduced new returnInstance options to return MWHttpRequest instance instead of string-bool mix if (!empty($options['returnInstance'])) { $ret = $req; } else { if ($isOk) { $ret = $req->getContent(); // Wikia change - end } else { $ret = false; } } wfProfileOut($fname); return $ret; }
/** * loadVariableFromDB * * Read variable data from database in most efficient way. If you've found * faster version - fix this one. * * @author eloy@wikia * @access private * @static * * @param integer $cv_id variable id in city_variables_pool * @param string $cv_name variable name in city_variables_pool * @param integer $city_id wiki id in city_list * @param boolean $master use master or slave connection * * @return string: path to file or null if id is not a number */ private static function loadVariableFromDB($cv_id, $cv_name, $city_id, $master = false) { if (!self::isUsed()) { Wikia::log(__METHOD__, "", "WikiFactory is not used."); return false; } /** * $wiki could be empty, but we have to know which variable read */ if (!$cv_id && !$cv_name) { return false; } wfProfileIn(__METHOD__); /** * if both are defined cv_id has precedence */ if ($cv_id) { $condition = ["cv_id" => $cv_id]; $cacheKey = "id:{$cv_id}"; } else { $condition = ["cv_name" => $cv_name]; $cacheKey = "name:{$cv_name}"; } $dbr = $master ? self::db(DB_MASTER) : self::db(DB_SLAVE); $caller = wfGetCallerClassMethod(__CLASS__); $fname = __METHOD__ . " (from {$caller})"; if ($master || !isset(self::$variablesCache[$cacheKey])) { $oRow = WikiaDataAccess::cache(self::getVarMetadataKey($cacheKey), WikiaResponse::CACHE_STANDARD, function () use($dbr, $condition, $fname) { $oRow = $dbr->selectRow(["city_variables_pool"], ["cv_id", "cv_name", "cv_description", "cv_variable_type", "cv_variable_group", "cv_access_level", "cv_is_unique"], $condition, $fname); // log typos in calls to WikiFactory::loadVariableFromDB if (!is_object($oRow)) { WikiaLogger::instance()->error('WikiFactory - variable not found', ['condition' => $condition, 'exception' => new Exception()]); } return $oRow; }, $master ? WikiaDataAccess::REFRESH_CACHE : WikiaDataAccess::USE_CACHE); self::$variablesCache[$cacheKey] = $oRow; } $oRow = self::$variablesCache[$cacheKey]; if (is_object($oRow)) { $oRow = clone $oRow; } if (!isset($oRow->cv_id)) { /** * variable doesn't exist */ wfProfileOut(__METHOD__); return null; } if (!empty($city_id)) { $oRow2 = WikiaDataAccess::cache(self::getVarValueKey($city_id, $oRow->cv_id), 3600, function () use($dbr, $oRow, $city_id, $fname) { return $dbr->selectRow(["city_variables"], ["cv_city_id", "cv_variable_id", "cv_value"], ["cv_variable_id" => $oRow->cv_id, "cv_city_id" => $city_id], $fname); }); if (isset($oRow2->cv_variable_id)) { $oRow->cv_city_id = $oRow2->cv_city_id; $oRow->cv_variable_id = $oRow2->cv_variable_id; $oRow->cv_value = $oRow2->cv_value; } else { $oRow->cv_city_id = $city_id; $oRow->cv_variable_id = $oRow->cv_id; $oRow->cv_value = null; } } else { $oRow->cv_city_id = null; $oRow->cv_variable_id = $oRow->cv_id; $oRow->cv_value = null; } wfProfileOut(__METHOD__); return $oRow; }
/** * Return the name of the method (outside of the internal code) that triggered purge request * * @return string method name */ private static function getPurgeCaller() { return wfGetCallerClassMethod([__CLASS__, 'SquidUpdate', 'WikiPage', 'Article', 'Title', 'WikiaDispatchableObject']); }
/** * Send a message to a destination * * @param string $message JSON encoded message * @return boolean */ public function send($message) { $messages = array(); if (!is_array($message)) { $message = array($message); } foreach ($message as $msg) { $__message = array(self::CATEGORY_KEY => $this->category, self::MESSAGE_KEY => $msg); $logEntry = new ScribeLogEntry($__message); $messages[] = $logEntry; } $result = false; if (!empty($messages)) { try { $this->connect(); $this->transport->open(); $result = $this->client->Log($messages); if ($result == $GLOBALS['E_ResultCode']['TRY_LATER']) { Wikia::log(__METHOD__, "scribe", "Returned 'TRY_LATER' value"); } if ($result != $GLOBALS['E_ResultCode']['OK']) { Wikia::log(__METHOD__, "scribe", "Unknown result ({$result})"); } $this->transport->close(); WikiaLogger::instance()->info('Scribe', ['cmd' => 'send', 'category' => $this->category, 'caller' => wfGetCallerClassMethod(__CLASS__)]); } catch (TException $e) { // socket error Wikia::log(__METHOD__, 'scribeClient log', $e->getMessage()); $this->connected = false; } } return $result; }
/** * Really opens a connection. Uncached. * Returns a Database object whether or not the connection was successful. * @access private * * @param $server * @param $dbNameOverride bool * @return DatabaseBase */ function reallyOpenConnection($server, $dbNameOverride = false) { if (!is_array($server)) { throw new MWException('You must update your load-balancing configuration. ' . 'See DefaultSettings.php entry for $wgDBservers.'); } $host = $server['host']; $dbname = $server['dbname']; if ($dbNameOverride !== false) { $server['dbname'] = $dbname = $dbNameOverride; } # Create object wfDebug("Connecting to {$host} {$dbname}...\n"); try { $db = DatabaseBase::factory($server['type'], $server); } catch (DBConnectionError $e) { // FIXME: This is probably the ugliest thing I have ever done to // PHP. I'm half-expecting it to segfault, just out of disgust. -- TS $db = $e->db; } $db->setLBInfo($server); if (isset($server['fakeSlaveLag'])) { $db->setFakeSlaveLag($server['fakeSlaveLag']); } if (isset($server['fakeMaster'])) { $db->setFakeMaster(true); } // Wikia change - begin if ($db->getSampler()->shouldSample()) { $db->getWikiaLogger()->info("LoadBalancer::reallyOpenConnection", ['caller' => wfGetCallerClassMethod(__CLASS__), 'host' => $server['hostName'], 'dbname' => $dbname]); } // Wikia change - end return $db; }
/** * Run an SQL query and return the result. Normally throws a DBQueryError * on failure. If errors are ignored, returns false instead. * * In new code, the query wrappers select(), insert(), update(), delete(), * etc. should be used where possible, since they give much better DBMS * independence and automatically quote or validate user input in a variety * of contexts. This function is generally only useful for queries which are * explicitly DBMS-dependent and are unsupported by the query wrappers, such * as CREATE TABLE. * * However, the query wrappers themselves should call this function. * * @param string $sql : SQL query * @param string $fname : Name of the calling function, for profiling/SHOW PROCESSLIST * comment (you can use __METHOD__ or add some extra info) * @param bool $tempIgnore : Whether to avoid throwing an exception on errors... * maybe best to catch the exception instead? * * @return bool|ResultWrapper true for a successful write query, ResultWrapper object * for a successful read query, or false on failure if $tempIgnore set * * @throws DBQueryError * @throws MWException */ public function query($sql, $fname = '', $tempIgnore = false) { $isMaster = !is_null($this->getLBInfo('master')); if (!Profiler::instance()->isStub()) { # generalizeSQL will probably cut down the query to reasonable # logging size most of the time. The substr is really just a sanity check. if ($isMaster) { $queryProf = 'query-m: ' . substr(DatabaseBase::generalizeSQL($sql), 0, 255); $totalProf = 'DatabaseBase::query-master'; } else { $queryProf = 'query: ' . substr(DatabaseBase::generalizeSQL($sql), 0, 255); $totalProf = 'DatabaseBase::query'; } wfProfileIn($totalProf); wfProfileIn($queryProf); } $this->mLastQuery = $sql; $is_writeable = $this->isWriteQuery($sql); if (!$this->mDoneWrites && $is_writeable) { # Set a flag indicating that writes have been done wfDebug(__METHOD__ . ": Writes done: {$sql}\n"); $this->mDoneWrites = true; } # <Wikia> global $wgDBReadOnly; if ($is_writeable && $wgDBReadOnly) { if (!Profiler::instance()->isStub()) { wfProfileOut($queryProf); wfProfileOut($totalProf); } WikiaLogger::instance()->error('DB readonly mode', ['exception' => new Exception($sql), 'server' => $this->mServer]); wfDebug(sprintf("%s: DB read-only mode prevented the following query: %s\n", __METHOD__, $sql)); return false; } # </Wikia> # Add a comment for easy SHOW PROCESSLIST interpretation global $wgUser; if (is_object($wgUser) && $wgUser->isItemLoaded('name')) { $userName = $wgUser->getName(); if (mb_strlen($userName) > 15) { $userName = mb_substr($userName, 0, 15) . '...'; } $userName = str_replace('/', '', $userName); } else { $userName = ''; } # Wikia change - begin # @author macbre if ($fname === '') { wfDebug("Query: \$fname autogenerated, please pass __METHOD__ to the query() call!\n"); $fname = wfGetCallerClassMethod(['WikiaSQL', 'FluentSql\\SQL', 'WikiaDataAccess', __CLASS__]); } # Wikia change - end $commentedSql = preg_replace('/\\s/', " /* {$fname} {$userName} */ ", $sql, 1); # Wikia change - begin # @author macbre # Add profiling data to queries like BEGIN or COMMIT (preg_replace above will not handle them) if (strpos($sql, ' ') === false) { $commentedSql = "{$sql} /* {$fname} {$userName} */"; } # Wikia change - end # If DBO_TRX is set, start a transaction if ($this->mFlags & DBO_TRX && !$this->trxLevel() && $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK') { # avoid establishing transactions for SHOW and SET statements too - # that would delay transaction initializations to once connection # is really used by application $sqlstart = substr($sql, 0, 10); // very much worth it, benchmark certified(tm) if (strpos($sqlstart, "SHOW ") !== 0 && strpos($sqlstart, "SET ") !== 0) { $this->begin(__METHOD__ . " ({$fname})"); } } if ($this->debug()) { static $cnt = 0; $cnt++; $sqlx = substr($commentedSql, 0, 500); $sqlx = strtr($sqlx, "\t\n", ' '); $master = $isMaster ? 'master' : 'slave'; wfDebug("Query {$this->mDBname} ({$cnt}) ({$master}): {$sqlx}\n"); } if (istainted($sql) & TC_MYSQL) { throw new MWException('Tainted query found'); } $ret = $this->executeAndProfileQuery($commentedSql, $fname, $isMaster); # Try reconnecting if the connection was lost if (false === $ret && $this->wasErrorReissuable()) { # Transaction is gone, like it or not $this->mTrxLevel = 0; wfDebug("Connection lost, reconnecting...\n"); if ($this->ping()) { wfDebug("Reconnected\n"); $sqlx = substr($commentedSql, 0, 500); $sqlx = strtr($sqlx, "\t\n", ' '); global $wgRequestTime; $elapsed = round(microtime(true) - $wgRequestTime, 3); if ($elapsed < 300) { # Not a database error to lose a transaction after a minute or two wfLogDBError("Connection lost and reconnected after {$elapsed}s, query: {$sqlx}\n"); } $ret = $this->executeAndProfileQuery($commentedSql, $fname, $isMaster); } else { wfDebug("Failed\n"); } } if (false === $ret) { $this->reportQueryError($this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore); } if (!Profiler::instance()->isStub()) { wfProfileOut($queryProf); wfProfileOut($totalProf); } return $this->resultObject($ret); }
/** * Load items into $ret from $sock * * @param $sock Resource: socket to read from * @param $ret Array: returned values * * @access private */ function _load_items($sock, &$ret) { while (1) { $decl = fgets($sock); if ($decl == "END\r\n") { return true; } elseif (preg_match('/^VALUE (\\S+) (\\d+) (\\d+)\\r\\n$/', $decl, $match)) { list($rkey, $flags, $len) = array($match[1], $match[2], $match[3]); $bneed = $len + 2; $offset = 0; while ($bneed > 0) { $data = fread($sock, $bneed); $n = strlen($data); if ($n == 0) { break; } $offset += $n; $bneed -= $n; if (isset($ret[$rkey])) { $ret[$rkey] .= $data; } else { $ret[$rkey] = $data; } } if ($offset != $len + 2) { // Something is borked! if ($this->_debug) { $this->_debugprint(sprintf("Something is borked! key %s expecting %d got %d length\n", $rkey, $len + 2, $offset)); } unset($ret[$rkey]); $this->_close_sock($sock); return false; } if ($this->_have_zlib && $flags & self::COMPRESSED) { wfProfileIn(__METHOD__ . '::uncompress'); $ret[$rkey] = gzuncompress($ret[$rkey]); wfProfileOut(__METHOD__ . '::uncompress'); } $ret[$rkey] = rtrim($ret[$rkey]); if ($flags & self::SERIALIZED) { wfProfileIn(__METHOD__ . '::unserialize'); $ret[$rkey] = unserialize($ret[$rkey]); wfProfileOut(__METHOD__ . '::unserialize'); } } else { $this->_debugprint("Error parsing memcached response\n"); // Wikia change - begin // @author macbre (BugId:27916 / PLATFORM-774) $method = explode(':', wfGetCallerClassMethod(__CLASS__)); // eg. MemcachedPhpBagOStuff::get $caller = wfGetCallerClassMethod([__CLASS__, 'MemcachedPhpBagOStuff']); // eg. WikiFactory::getWikiByDB $this->error('MemcachedClient: error parsing the response', ['caller' => $caller, 'cmd' => substr($this->_current_cmd, 0, 1024), 'operation' => end($method), 'response' => $decl, 'exception' => new Exception(), 'host' => $this->_current_host]); // Wikia change - end return 0; } } }