'phpfm' => 'ApiFormatPhp',
'xml' => 'ApiFormatXml',
'xmlfm' => 'ApiFormatXml',
- 'yaml' => 'ApiFormatYaml',
- 'yamlfm' => 'ApiFormatYaml',
'rawfm' => 'ApiFormatJson',
- 'txt' => 'ApiFormatTxt',
- 'txtfm' => 'ApiFormatTxt',
- 'dbg' => 'ApiFormatDbg',
- 'dbgfm' => 'ApiFormatDbg',
'none' => 'ApiFormatNone',
);
}
if ( $config->get( 'ShowHostnames' ) ) {
// servedby is especially useful when debugging errors
- $result->addValue( null, 'servedby', wfHostName(), ApiResult::NO_SIZE_CHECK );
+ $result->addValue( null, 'servedby', wfHostname(), ApiResult::NO_SIZE_CHECK );
}
if ( $warnings !== null ) {
$result->addValue( null, 'warnings', $warnings, ApiResult::NO_SIZE_CHECK );
*/
protected function checkMaxLag( $module, $params ) {
if ( $module->shouldCheckMaxlag() && isset( $params['maxlag'] ) ) {
- // Check for maxlag
$maxLag = $params['maxlag'];
list( $host, $lag ) = wfGetLB()->getMaxLag();
if ( $lag > $maxLag ) {
) {
$this->dieUsageMsg( 'readrequired' );
}
+
if ( $module->isWriteMode() ) {
if ( !$this->mEnableWrite ) {
$this->dieUsageMsg( 'writedisabled' );
- }
- if ( !$user->isAllowed( 'writeapi' ) ) {
+ } elseif ( !$user->isAllowed( 'writeapi' ) ) {
$this->dieUsageMsg( 'writerequired' );
+ } elseif ( $this->getRequest()->getHeader( 'Promise-Non-Write-API-Action' ) ) {
+ $this->dieUsage(
+ "Promise-Non-Write-API-Action HTTP header cannot be sent to write API modules",
+ 'promised-nonwrite-api'
+ );
}
- if ( wfReadOnly() ) {
- $this->dieReadOnly();
- }
+
+ $this->checkReadOnly( $module );
}
// Allow extensions to stop execution for arbitrary reasons.
}
}
+ /**
+ * Check if the DB is read-only for this user
+ * @param ApiBase $module An Api module
+ */
+ protected function checkReadOnly( $module ) {
+ if ( wfReadOnly() ) {
+ $this->dieReadOnly();
+ }
+
+ if ( $module->isWriteMode()
+ && in_array( 'bot', $this->getUser()->getGroups() )
+ && wfGetLB()->getServerCount() > 1
+ ) {
+ // Figure out how many servers have passed the lag threshold
+ $numLagged = 0;
+ $lagLimit = $this->getConfig()->get( 'APIMaxLagThreshold' );
+ foreach ( wfGetLB()->getLagTimes() as $lag ) {
+ if ( $lag > $lagLimit ) {
+ ++$numLagged;
+ }
+ }
+ // If a majority of slaves are too lagged then disallow writes
+ $slaveCount = wfGetLB()->getServerCount() - 1;
+ if ( $numLagged >= ceil( $slaveCount / 2 ) ) {
+ $parsed = $this->parseMsg( array( 'readonlytext' ) );
+ $this->dieUsage(
+ $parsed['info'],
+ $parsed['code'],
+ /* http error */
+ 0,
+ array( 'readonlyreason' => "Waiting for $numLagged lagged database(s)" )
+ );
+ }
+ }
+ }
+
/**
* Check asserts of the user's rights
* @param array $params
/**
* Log the preceding request
- * @param int $time Time in seconds
+ * @param float $time Time in seconds
*/
protected function logRequest( $time ) {
$request = $this->getRequest();
- $milliseconds = $time === null ? '?' : round( $time * 1000 );
- $s = 'API' .
- ' ' . $request->getMethod() .
- ' ' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) .
- ' ' . $request->getIP() .
- ' T=' . $milliseconds . 'ms';
+ $logCtx = array(
+ 'dt' => date( 'c' ),
+ 'client_ip' => $request->getIP(),
+ 'user_agent' => $this->getUserAgent(),
+ 'wiki' => wfWikiId(),
+ 'time_backend_ms' => round( $time * 1000 ),
+ 'params' => array(),
+ );
+
+ // Construct space separated message for 'api' log channel
+ $msg = "API {$request->getMethod()} " .
+ wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) .
+ " {$logCtx['client_ip']} " .
+ "T={$logCtx['time_backend_ms']}ms";
+
foreach ( $this->getParamsUsed() as $name ) {
$value = $request->getVal( $name );
if ( $value === null ) {
continue;
}
- $s .= ' ' . $name . '=';
+
if ( strlen( $value ) > 256 ) {
- $encValue = $this->encodeRequestLogValue( substr( $value, 0, 256 ) );
- $s .= $encValue . '[...]';
+ $value = substr( $value, 0, 256 );
+ $encValue = $this->encodeRequestLogValue( $value ) . '[...]';
} else {
- $s .= $this->encodeRequestLogValue( $value );
+ $encValue = $this->encodeRequestLogValue( $value );
}
+
+ $logCtx['params'][$name] = $value;
+ $msg .= " {$name}={$encValue}";
}
- $s .= "\n";
- wfDebugLog( 'api', $s, 'private' );
+
+ wfDebugLog( 'api', $msg, 'private' );
+ // ApiRequest channel is for structured data consumers
+ wfDebugLog( 'ApiRequest', '', 'private', $logCtx );
}
/**
$tocnumber = &$options['tocnumber'];
$header = $this->msg( 'api-help-datatypes-header' )->parse();
- $help['datatypes'] .= Html::rawelement( 'h' . min( 6, $level ),
+ $help['datatypes'] .= Html::rawElement( 'h' . min( 6, $level ),
array( 'id' => 'main/datatypes', 'class' => 'apihelp-header' ),
Html::element( 'span', array( 'id' => Sanitizer::escapeId( 'main/datatypes' ) ) ) .
$header
}
$header = $this->msg( 'api-credits-header' )->parse();
- $help['credits'] .= Html::rawelement( 'h' . min( 6, $level ),
+ $help['credits'] .= Html::rawElement( 'h' . min( 6, $level ),
array( 'id' => 'main/credits', 'class' => 'apihelp-header' ),
Html::element( 'span', array( 'id' => Sanitizer::escapeId( 'main/credits' ) ) ) .
$header
*/
public function makeHelpMsg() {
wfDeprecated( __METHOD__, '1.25' );
- global $wgMemc;
- $this->setHelp();
- // Get help text from cache if present
- $key = wfMemcKey( 'apihelp', $this->getModuleName(),
- str_replace( ' ', '_', SpecialVersion::getVersion( 'nodb' ) ) );
+ $this->setHelp();
$cacheHelpTimeout = $this->getConfig()->get( 'APICacheHelpTimeout' );
- if ( $cacheHelpTimeout > 0 ) {
- $cached = $wgMemc->get( $key );
- if ( $cached ) {
- return $cached;
- }
- }
- $retval = $this->reallyMakeHelpMsg();
- if ( $cacheHelpTimeout > 0 ) {
- $wgMemc->set( $key, $retval, $cacheHelpTimeout );
- }
- return $retval;
+ return ObjectCache::getMainWANInstance()->getWithSetCallback(
+ wfMemcKey(
+ 'apihelp',
+ $this->getModuleName(),
+ str_replace( ' ', '_', SpecialVersion::getVersion( 'nodb' ) )
+ ),
+ $cacheHelpTimeout > 0 ? $cacheHelpTimeout : WANObjectCache::TTL_UNCACHEABLE,
+ array( $this, 'reallyMakeHelpMsg' )
+ );
}
/**