Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: propagate status code from platform api when routing wiki requests #410

Merged
merged 7 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/mediawiki.verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,9 @@ jobs:
- run: curl -L -s -N "http://site2.localhost:8001/w/api.php" | grep -q "Main module"
- run: curl -L -s -N "http://site2.localhost:8001/w/load.php" | grep -q "no modules were requested"
- run: curl -L -s -N "http://site2.localhost:8001/w/rest.php" | grep -q "did not match any known handler"

- run: curl -L -s -N "http://notfound.localhost:8001/wiki/Main_Page" | grep -q "It may never have existed"

- run: curl -L -s -N "http://failwith500.localhost:8001/wiki/Main_Page" | grep -q "server error in the platform API"

- run: curl -L -s -N "http://broken.localhost:8001/wiki/Main_Page" | grep -q "server error in the platform API"
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

Tags have the format: `<MediaWiki core version>-<PHP Version>-<date>-<build number>`

## 1.39-7.4-20240116-0
- On failure, propagate status code from Platform API to clients (T343744)

## 1.39-7.4-20240103-0
- Add QuestyCaptcha config option

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"lint": [
"parallel-lint --exclude ./dist --exclude ./sync/.tmp .",
"rc=0 && for x in `find . -not -path './sync/.tmp/*' -not -path './dist/*' -name \"*.json\" -type f`; do vendor/bin/jsonlint \"$x\" || rc=$?; done && exit $rc"
"rc=0 && for x in `find . -not -path './sync/.tmp/*' -not -path './dist/*' -name \"*.json\" ! -name \"WikiInfo-broken.json\" -type f`; do vendor/bin/jsonlint \"$x\" || rc=$?; done && exit $rc"
]
}
}
59 changes: 34 additions & 25 deletions dist-persist/wbstack/src/Info/GlobalSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,38 @@

namespace WBStack\Info;

class GlobalSetException extends \Exception {}

/**
* A class for setting a global holding a WBStackInfo object.
* This includes lookup of the data for the global from cache or API.
*/

class GlobalSet {

/**
* @param string $requestDomain A request domain, or 'maint' Example: 'addshore-alpha.wiki.opencura.com'
* @return bool was the global successfully set?
* @return void
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this makes sense and probably propagating the precise nature of the failure with an Exception.

In any case this probably makes sense and is then super clear that this is really a side effect only function.

Naturally trying to write this in a clean way is pretty hard since the desired behaviour is charging around a setting a load of globals

*/
public static function forDomain( $requestDomain ) {
public static function forDomain($requestDomain) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the linting here looks "funny" because MediaWiki uses a very non-php standard code style. No objection to this being changed here since we don't lint this anyway but perhaps in future we should add linting to match the MediaWiki style to this weird codebase in dist-persist

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean if we want a code style, then it should be applied everywhere. I just tried to make it consistent per function. Which is probably futile ...

// Normalize the domain by setting to lowercase
$requestDomain = strtolower($requestDomain);

// If the domain is 'maint' then set the maint settings
if($requestDomain === 'maint') {
if ($requestDomain === 'maint') {
self::forMaint();
return true;
return;
}

$info = self::getCachedOrFreshInfo( $requestDomain );

if($info === null) {
return false;
$info = self::getCachedOrFreshInfo($requestDomain);
if ($info instanceof WBStackLookupFailure) {
throw new GlobalSetException(
"Failure looking up wiki $requestDomain.",
$info->statusCode,
);
}

self::setGlobal( $info );
return true;
self::setGlobal($info);
}

/**
Expand Down Expand Up @@ -60,7 +64,7 @@ private static function setGlobal( $info ) {

/**
* @param string $requestDomain
* @return WBStackInfo|null
* @return WBStackInfo|WBStackLookupFailure
*/
private static function getCachedOrFreshInfo( $requestDomain ) {
$info = self::getInfoFromApcCache( $requestDomain );
Expand All @@ -70,30 +74,29 @@ private static function getCachedOrFreshInfo( $requestDomain ) {

// TODO create an APC lock saying this proc is going to get fresh data?
// TODO in reality all of this needs to change...

$info = self::getInfoFromApi( $requestDomain );

// Cache positive results for 10 seconds, negative for 2
$ttl = $info ? 10 : 2;
// Cache positive results for 10 seconds, failures for 2
$ttl = $info instanceof WBStackInfo ? 10 : 2;
self::writeInfoToApcCache( $requestDomain, $info, $ttl );

return $info;
}

/**
* @param string $requestDomain
* @return WBStackInfo|null|bool false if no info is cached (should check), null for cached empty data (should not check)
* @return WBStackInfo|WBStackLookupFailure|false false if no info is cached (should check)
*/
private static function getInfoFromApcCache( $requestDomain ) {
return apcu_fetch( self::cacheKey($requestDomain) );
}

/**
* @param string $requestDomain
* @param WBStackInfo|null $info
* @param WBStackInfo|WBStackLookupFailure $info
* @param int $ttl
*/
private static function writeInfoToApcCache( $requestDomain, ?WBStackInfo $info, $ttl ) {
private static function writeInfoToApcCache( $requestDomain, $info, $ttl ) {
$result = apcu_store( self::cacheKey($requestDomain), $info, $ttl );
if(!$result) {
// TODO log failed stores?!
Expand All @@ -106,7 +109,7 @@ private static function cacheKey( $requestDomain ) {

/**
* @param string $requestDomain
* @return WBStackInfo|null
* @return WBStackInfo|WBStackLookupFailure
*/
private static function getInfoFromApi( $requestDomain ) {
// START generic getting of wiki info from domain
Expand All @@ -120,16 +123,22 @@ private static function getInfoFromApi( $requestDomain ) {
curl_setopt($client, CURLOPT_HTTPHEADER, $headers);
curl_setopt($client, CURLOPT_RETURNTRANSFER, true);
curl_setopt( $client, CURLOPT_USERAGENT, "WBStack - MediaWiki - WBStackInfo::getInfoFromApi" );

$response = curl_exec($client);
if ($response === false) {
return new WBStackLookupFailure(502);
}
$responseCode = intval(curl_getinfo($client, CURLINFO_RESPONSE_CODE));
m90 marked this conversation as resolved.
Show resolved Hide resolved

// TODO detect non 200 response here, and pass that out to the user as an error
if ($responseCode > 399) {
return new WBStackLookupFailure($responseCode);
}

$info = WBStackInfo::newFromJsonString($response, $requestDomain);
if (!$info) {
return null;
try {
return WBStackInfo::newFromJsonString($response, $requestDomain);
} catch (\Exception $ex) {
return new WBStackLookupFailure(502);
}
return $info;
}

}
}
13 changes: 11 additions & 2 deletions dist-persist/wbstack/src/Info/WBStackInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ public static function newFromJsonString( $infoJsonString, $requestDomain ) {

// Check if the string we were given was invalid and thus not parsed!
if ( $data === null ) {
return null;
throw new \Exception('Unexpected malformed payload.');
}

// Get the inner data from the response
$data = $data->data;

// Data from the api should always be an array with at least an id...
if (!is_object($data) || !property_exists($data, 'id')) {
return null;
throw new \Exception('Unexpected payload shape.');
}

$info = new self($data);
Expand All @@ -75,3 +75,12 @@ public function __get($name)
}

}

class WBStackLookupFailure
{
public $statusCode;

public function __construct($statusCode) {
$this->statusCode = $statusCode;
}
}
6 changes: 4 additions & 2 deletions dist-persist/wbstack/src/Shim/Cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
}

// Try and get the wiki info (from env var) or fail with a message
if(!\WBStack\Info\GlobalSet::forDomain( getenv( 'WBS_DOMAIN' ) )) {
try {
\WBStack\Info\GlobalSet::forDomain( getenv( 'WBS_DOMAIN' ) );
} catch (Exception $ex) {
echo 'Failed to work for WBS_DOMAIN: ' . getenv( 'WBS_DOMAIN' );
die(1);
}
}
17 changes: 12 additions & 5 deletions dist-persist/wbstack/src/Shim/Web.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@

require_once __DIR__ . '/../loadShim.php';

// Try and get the wiki info, for fail with a 404 web page
if(!\WBStack\Info\GlobalSet::forDomain( $_SERVER['SERVER_NAME'] ) ) {
http_response_code(404);
echo "You have requested the domain: " . $_SERVER['SERVER_NAME'] . ". But that wiki can not currently be loaded.\n";
echo "It may never have existed, or it might now be deleted.\n";
// Try and get the wiki info or fail
try {
\WBStack\Info\GlobalSet::forDomain($_SERVER['SERVER_NAME']);
} catch (\WBStack\Info\GlobalSetException $ex) {
http_response_code($ex->getCode());
echo "You have requested the domain: " . $_SERVER['SERVER_NAME'] . ". But that wiki can not currently be loaded.".PHP_EOL;
if ($ex->getCode() === 404) {
echo "It may never have existed or it might now be deleted.".PHP_EOL;
} else {
echo "There was a server error in the platform API.".PHP_EOL;
}
echo $ex->getMessage();
die(1);
}

Expand Down
59 changes: 34 additions & 25 deletions dist/wbstack/src/Info/GlobalSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,38 @@

namespace WBStack\Info;

class GlobalSetException extends \Exception {}

/**
* A class for setting a global holding a WBStackInfo object.
* This includes lookup of the data for the global from cache or API.
*/

class GlobalSet {

/**
* @param string $requestDomain A request domain, or 'maint' Example: 'addshore-alpha.wiki.opencura.com'
* @return bool was the global successfully set?
* @return void
*/
public static function forDomain( $requestDomain ) {
public static function forDomain($requestDomain) {
// Normalize the domain by setting to lowercase
$requestDomain = strtolower($requestDomain);

// If the domain is 'maint' then set the maint settings
if($requestDomain === 'maint') {
if ($requestDomain === 'maint') {
self::forMaint();
return true;
return;
}

$info = self::getCachedOrFreshInfo( $requestDomain );

if($info === null) {
return false;
$info = self::getCachedOrFreshInfo($requestDomain);
if ($info instanceof WBStackLookupFailure) {
throw new GlobalSetException(
"Failure looking up wiki $requestDomain.",
$info->statusCode,
);
}

self::setGlobal( $info );
return true;
self::setGlobal($info);
}

/**
Expand Down Expand Up @@ -60,7 +64,7 @@ private static function setGlobal( $info ) {

/**
* @param string $requestDomain
* @return WBStackInfo|null
* @return WBStackInfo|WBStackLookupFailure
*/
private static function getCachedOrFreshInfo( $requestDomain ) {
$info = self::getInfoFromApcCache( $requestDomain );
Expand All @@ -70,30 +74,29 @@ private static function getCachedOrFreshInfo( $requestDomain ) {

// TODO create an APC lock saying this proc is going to get fresh data?
// TODO in reality all of this needs to change...

$info = self::getInfoFromApi( $requestDomain );

// Cache positive results for 10 seconds, negative for 2
$ttl = $info ? 10 : 2;
// Cache positive results for 10 seconds, failures for 2
$ttl = $info instanceof WBStackInfo ? 10 : 2;
self::writeInfoToApcCache( $requestDomain, $info, $ttl );

return $info;
}

/**
* @param string $requestDomain
* @return WBStackInfo|null|bool false if no info is cached (should check), null for cached empty data (should not check)
* @return WBStackInfo|WBStackLookupFailure|false false if no info is cached (should check)
*/
private static function getInfoFromApcCache( $requestDomain ) {
return apcu_fetch( self::cacheKey($requestDomain) );
}

/**
* @param string $requestDomain
* @param WBStackInfo|null $info
* @param WBStackInfo|WBStackLookupFailure $info
* @param int $ttl
*/
private static function writeInfoToApcCache( $requestDomain, ?WBStackInfo $info, $ttl ) {
private static function writeInfoToApcCache( $requestDomain, $info, $ttl ) {
$result = apcu_store( self::cacheKey($requestDomain), $info, $ttl );
if(!$result) {
// TODO log failed stores?!
Expand All @@ -106,7 +109,7 @@ private static function cacheKey( $requestDomain ) {

/**
* @param string $requestDomain
* @return WBStackInfo|null
* @return WBStackInfo|WBStackLookupFailure
*/
private static function getInfoFromApi( $requestDomain ) {
// START generic getting of wiki info from domain
Expand All @@ -120,16 +123,22 @@ private static function getInfoFromApi( $requestDomain ) {
curl_setopt($client, CURLOPT_HTTPHEADER, $headers);
curl_setopt($client, CURLOPT_RETURNTRANSFER, true);
curl_setopt( $client, CURLOPT_USERAGENT, "WBStack - MediaWiki - WBStackInfo::getInfoFromApi" );

$response = curl_exec($client);
if ($response === false) {
return new WBStackLookupFailure(502);
}
$responseCode = intval(curl_getinfo($client, CURLINFO_RESPONSE_CODE));

// TODO detect non 200 response here, and pass that out to the user as an error
if ($responseCode > 399) {
return new WBStackLookupFailure($responseCode);
}

$info = WBStackInfo::newFromJsonString($response, $requestDomain);
if (!$info) {
return null;
try {
return WBStackInfo::newFromJsonString($response, $requestDomain);
} catch (\Exception $ex) {
return new WBStackLookupFailure(502);
}
return $info;
}

}
}
13 changes: 11 additions & 2 deletions dist/wbstack/src/Info/WBStackInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ public static function newFromJsonString( $infoJsonString, $requestDomain ) {

// Check if the string we were given was invalid and thus not parsed!
if ( $data === null ) {
return null;
throw new \Exception('Unexpected malformed payload.');
}

// Get the inner data from the response
$data = $data->data;

// Data from the api should always be an array with at least an id...
if (!is_object($data) || !property_exists($data, 'id')) {
return null;
throw new \Exception('Unexpected payload shape.');
}

$info = new self($data);
Expand All @@ -75,3 +75,12 @@ public function __get($name)
}

}

class WBStackLookupFailure
{
public $statusCode;

public function __construct($statusCode) {
$this->statusCode = $statusCode;
}
}
Loading
Loading