Add WebRequest methods for determining "safe" requests

* This is useful for logic that decides what DB (master/slave) to use
  based on the nature of the web request. It could also be used to
  enforce clear read/write distinctions via exceptions if DB_MASTER
  is misused.
* Also fixed two IDEA errors while editing this class.

Bug: T134608
Change-Id: I43f4bc06c19d823d7d1ffd9cee8bbe60563c7f82
This commit is contained in:
Aaron Schulz 2016-05-06 15:25:36 -07:00
parent 44a4bdb691
commit 549af8bf91
4 changed files with 54 additions and 2 deletions

View file

@ -487,6 +487,7 @@ class MediaWiki {
$trxProfiler = Profiler::instance()->getTransactionProfiler();
if ( $request->wasPosted() && !$action->doesWrites() ) {
$trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ );
$request->markAsSafeRequest();
}
# Let CDN cache things if we can purge them.

View file

@ -80,6 +80,9 @@ class WebRequest {
*/
protected $sessionId = null;
/** @var bool Whether this HTTP request is "safe" (even if it is an HTTP post) */
protected $markedAsSafe = false;
public function __construct() {
$this->requestTime = isset( $_SERVER['REQUEST_TIME_FLOAT'] )
? $_SERVER['REQUEST_TIME_FLOAT'] : microtime( true );
@ -318,7 +321,7 @@ class WebRequest {
*
* @param string $path The URL path given from the client
* @param array $bases One or more URLs, optionally with $1 at the end
* @param string $key If provided, the matching key in $bases will be
* @param string|bool $key If provided, the matching key in $bases will be
* passed on as the value of this URL parameter
* @return array Array of URL variables to interpolate; empty if no match
*/
@ -1022,7 +1025,7 @@ class WebRequest {
* @param mixed $data
*/
public function setSessionData( $key, $data ) {
return $this->getSession()->set( $key, $data );
$this->getSession()->set( $key, $data );
}
/**
@ -1245,4 +1248,50 @@ HTML;
public function setIP( $ip ) {
$this->ip = $ip;
}
/**
* Whether this request should be identified as being "safe"
*
* This means that the client is not requesting any state changes and that database writes
* are not inherently required. Ideally, no visible updates would happen at all. If they
* must, then they should not be publically attributed to the end user.
*
* In more detail:
* - Cache populations and refreshes MAY occur.
* - Private user session updates and private server logging MAY occur.
* - Updates to private viewing activity data MAY occur via DeferredUpdates.
* - Other updates SHOULD NOT occur (e.g. modifying content assets).
*
* @return bool
* @see https://tools.ietf.org/html/rfc7231#section-4.2.1
* @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
* @since 1.28
*/
public function isSafeRequest() {
if ( !isset( $_SERVER['REQUEST_METHOD'] ) ) {
return false; // CLI mode
}
if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
return $this->markedAsSafe;
} elseif ( in_array( $_SERVER['REQUEST_METHOD'], [ 'GET', 'HEAD', 'OPTIONS' ] ) ) {
return true; // HTTP "safe methods"
}
return false; // PUT/DELETE
}
/**
* Mark this request is identified as being nullipotent even if it is a POST request
*
* POST requests are often used due to the need for a client payload, even if the request
* is otherwise equivalent to a "safe method" request.
*
* @see https://tools.ietf.org/html/rfc7231#section-4.2.1
* @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
* @since 1.28
*/
public function markAsSafeRequest() {
$this->markedAsSafe = true;
}
}

View file

@ -1355,6 +1355,7 @@ class ApiMain extends ApiBase {
$trxProfiler->setExpectations( $limits['POST'], __METHOD__ );
} else {
$trxProfiler->setExpectations( $limits['POST-nonwrite'], __METHOD__ );
$this->getRequest()->markAsSafeRequest();
}
} else {
$trxProfiler->setExpectations( $limits['GET'], __METHOD__ );

View file

@ -539,6 +539,7 @@ class SpecialPageFactory {
$trxProfiler = Profiler::instance()->getTransactionProfiler();
if ( $context->getRequest()->wasPosted() && !$page->doesWrites() ) {
$trxProfiler->setExpectations( $trxLimits['POST-nonwrite'], __METHOD__ );
$context->getRequest()->markAsSafeRequest();
}
}