aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordaniel <dkinzler@wikimedia.org>2023-11-23 10:34:04 +0100
committerdaniel <dkinzler@wikimedia.org>2024-03-12 14:37:18 +0100
commitae9d8e60542aad435f3e42d145006cb180430234 (patch)
tree18cb5dcf5b4b6903ea49634f388ff604f01897a5
parent5c87a85f236a500d7a9e92b92775b568eec8c16d (diff)
downloadmediawikicore-ae9d8e60542aad435f3e42d145006cb180430234.tar.gz
mediawikicore-ae9d8e60542aad435f3e42d145006cb180430234.zip
Rest: Turn Rest\EntryPoint into a MediaWikiEntryPoint subclass
The idea is for all entry points to use the MediaWikiEntryPoint base class, to improve consistency and testability. Bug: T354216 Change-Id: I3678afe32c7c1a313d2dcb1808286c25ecd167eb
-rw-r--r--includes/Rest/EntryPoint.php154
-rw-r--r--rest.php9
-rw-r--r--tests/phpunit/includes/Rest/EntryPointTest.php94
-rw-r--r--tests/phpunit/structure/RestStructureTest.php8
4 files changed, 149 insertions, 116 deletions
diff --git a/includes/Rest/EntryPoint.php b/includes/Rest/EntryPoint.php
index aecae45e26ed..ec8dfa37c751 100644
--- a/includes/Rest/EntryPoint.php
+++ b/includes/Rest/EntryPoint.php
@@ -3,14 +3,14 @@
namespace MediaWiki\Rest;
use ExtensionRegistry;
-use MediaWiki;
use MediaWiki\Config\Config;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Context\IContextSource;
use MediaWiki\Context\RequestContext;
+use MediaWiki\EntryPointEnvironment;
use MediaWiki\MainConfigNames;
+use MediaWiki\MediaWikiEntryPoint;
use MediaWiki\MediaWikiServices;
-use MediaWiki\Request\WebResponse;
use MediaWiki\Rest\BasicAccess\CompoundAuthorizer;
use MediaWiki\Rest\BasicAccess\MWBasicAuthorizer;
use MediaWiki\Rest\Reporter\MWErrorReporter;
@@ -19,21 +19,18 @@ use MediaWiki\Title\Title;
use MWExceptionRenderer;
use Wikimedia\Message\ITextFormatter;
-class EntryPoint {
- /** @var RequestInterface */
- private $request;
- /** @var WebResponse */
- private $webResponse;
- /** @var Router */
- private $router;
- /** @var RequestContext */
- private $context;
- /** @var CorsUtils */
- private $cors;
- /** @var ?RequestInterface */
- private static $mainRequest;
+/**
+ * @internal
+ */
+class EntryPoint extends MediaWikiEntryPoint {
+
+ private RequestInterface $request;
+ private ?Router $router = null;
+ private ?CorsUtils $cors = null;
/**
+ * @internal Public for use in core tests
+ *
* @param MediaWikiServices $services
* @param IContextSource $context
* @param RequestInterface $request
@@ -42,7 +39,7 @@ class EntryPoint {
*
* @return Router
*/
- private static function createRouter(
+ public static function createRouter(
MediaWikiServices $services,
IContextSource $context,
RequestInterface $request,
@@ -84,64 +81,68 @@ class EntryPoint {
}
/**
+ * @internal
* @return RequestInterface The RequestInterface object used by this entry point.
*/
public static function getMainRequest(): RequestInterface {
- if ( self::$mainRequest === null ) {
+ static $mainRequest = null;
+
+ if ( $mainRequest === null ) {
$conf = MediaWikiServices::getInstance()->getMainConfig();
- self::$mainRequest = new RequestFromGlobals( [
+ $mainRequest = new RequestFromGlobals( [
'cookiePrefix' => $conf->get( MainConfigNames::CookiePrefix )
] );
}
- return self::$mainRequest;
+
+ return $mainRequest;
}
- public static function main() {
- // URL safety checks
- global $wgRequest;
+ protected function doSetup() {
+ parent::doSetup();
$context = RequestContext::getMain();
// Set $wgTitle and the title in RequestContext, as in api.php
global $wgTitle;
- $wgTitle = Title::makeTitle( NS_SPECIAL, 'Badtitle/rest.php' );
+ $wgTitle = Title::makeTitle(
+ NS_SPECIAL,
+ 'Badtitle/rest.php'
+ );
$context->setTitle( $wgTitle );
- $services = MediaWikiServices::getInstance();
- $conf = $services->getMainConfig();
-
- $responseFactory = new ResponseFactory( self::getTextFormatters( $services ) );
- $responseFactory->setShowExceptionDetails( MWExceptionRenderer::shouldShowExceptionDetails() );
+ $responseFactory = new ResponseFactory( $this->getTextFormatters() );
+ $responseFactory->setShowExceptionDetails(
+ MWExceptionRenderer::shouldShowExceptionDetails()
+ );
- $cors = new CorsUtils(
+ $this->cors = new CorsUtils(
new ServiceOptions(
- CorsUtils::CONSTRUCTOR_OPTIONS, $conf
+ CorsUtils::CONSTRUCTOR_OPTIONS,
+ $this->getServiceContainer()->getMainConfig()
),
$responseFactory,
$context->getUser()
);
- $request = self::getMainRequest();
-
- $router = self::createRouter( $services, $context, $request, $responseFactory, $cors );
-
- $entryPoint = new self(
- $context,
- $request,
- $wgRequest->response(),
- $router,
- $cors
- );
- $entryPoint->execute();
+ if ( !$this->router ) {
+ $this->router = $this->createRouter(
+ $this->getServiceContainer(),
+ $context,
+ $this->request,
+ $responseFactory,
+ $this->cors
+ );
+ }
}
/**
* Get a TextFormatter array from MediaWikiServices
*
- * @param MediaWikiServices $services
* @return ITextFormatter[]
*/
- private static function getTextFormatters( MediaWikiServices $services ) {
+ private function getTextFormatters() {
+ $services = $this->getServiceContainer();
+
$code = $services->getContentLanguage()->getCode();
$langs = array_unique( [ $code, 'en' ] );
$textFormatters = [];
@@ -150,11 +151,13 @@ class EntryPoint {
foreach ( $langs as $lang ) {
$textFormatters[] = $factory->getTextFormatter( $lang );
}
+
return $textFormatters;
}
/**
* @param Config $conf
+ *
* @return string[]
*/
private static function getRouteFiles( $conf ) {
@@ -166,59 +169,80 @@ class EntryPoint {
$conf->get( MainConfigNames::RestAPIAdditionalRouteFiles )
);
foreach ( $routeFiles as &$file ) {
- if ( str_starts_with( $file, '/' ) ) {
+ if (
+ str_starts_with( $file, '/' )
+ ) {
// Allow absolute paths on non-Windows
- } elseif ( str_starts_with( $file, 'extensions/' ) ) {
+ } elseif (
+ str_starts_with( $file, 'extensions/' )
+ ) {
// Support hacks like Wikibase.ci.php
- $file = substr_replace( $file, $extensionsDir, 0, strlen( 'extensions' ) );
+ $file = substr_replace( $file, $extensionsDir,
+ 0, strlen( 'extensions' ) );
} else {
$file = "$IP/$file";
}
}
+
return $routeFiles;
}
- public function __construct( RequestContext $context, RequestInterface $request,
- WebResponse $webResponse, Router $router, CorsUtils $cors
+ public function __construct(
+ RequestInterface $request,
+ RequestContext $context,
+ EntryPointEnvironment $environment,
+ MediaWikiServices $mediaWikiServices
) {
- $this->context = $context;
+ parent::__construct( $context, $environment, $mediaWikiServices );
+
$this->request = $request;
- $this->webResponse = $webResponse;
+ }
+
+ /**
+ * Sets the router to use.
+ * Intended for testing.
+ *
+ * @param Router $router
+ */
+ public function setRouter( Router $router ): void {
$this->router = $router;
- $this->cors = $cors;
}
public function execute() {
- ob_start();
+ $this->startOutputBuffer();
+
$response = $this->cors->modifyResponse(
$this->request,
$this->router->execute( $this->request )
);
- $this->webResponse->header(
- 'HTTP/' . $response->getProtocolVersion() . ' ' .
- $response->getStatusCode() . ' ' .
- $response->getReasonPhrase() );
+ $webResponse = $this->getResponse();
+
+ $webResponse->header(
+ 'HTTP/' . $response->getProtocolVersion() . ' ' . $response->getStatusCode() . ' ' .
+ $response->getReasonPhrase()
+ );
foreach ( $response->getRawHeaderLines() as $line ) {
- $this->webResponse->header( $line );
+ $webResponse->header( $line );
}
foreach ( $response->getCookies() as $cookie ) {
- $this->webResponse->setCookie(
+ $webResponse->setCookie(
$cookie['name'],
$cookie['value'],
$cookie['expiry'],
- $cookie['options'] );
+ $cookie['options']
+ );
}
// Clear all errors that might have been displayed if display_errors=On
- ob_end_clean();
+ $this->discardOutputBuffer();
$stream = $response->getBody();
$stream->rewind();
- MediaWiki::preOutputCommit( $this->context );
+ $this->prepareForOutput();
if ( $stream instanceof CopyableStreamInterface ) {
$stream->copyToStream( fopen( 'php://output', 'w' ) );
@@ -228,11 +252,9 @@ class EntryPoint {
if ( $buffer === '' ) {
break;
}
- echo $buffer;
+ $this->print( $buffer );
}
}
-
- $mw = new MediaWiki;
- $mw->doPostOutputShutdown();
}
+
}
diff --git a/rest.php b/rest.php
index f647f91fe0e4..62f298712f33 100644
--- a/rest.php
+++ b/rest.php
@@ -21,6 +21,8 @@
* @file
*/
+use MediaWiki\EntryPointEnvironment;
+use MediaWiki\MediaWikiServices;
use MediaWiki\Rest\EntryPoint;
define( 'MW_REST_API', true );
@@ -28,4 +30,9 @@ define( 'MW_ENTRY_POINT', 'rest' );
require __DIR__ . '/includes/WebStart.php';
-EntryPoint::main();
+( new EntryPoint(
+ EntryPoint::getMainRequest(),
+ RequestContext::getMain(),
+ new EntryPointEnvironment(),
+ MediaWikiServices::getInstance()
+) )->run();
diff --git a/tests/phpunit/includes/Rest/EntryPointTest.php b/tests/phpunit/includes/Rest/EntryPointTest.php
index 775987fe9d3d..4275a59dcbad 100644
--- a/tests/phpunit/includes/Rest/EntryPointTest.php
+++ b/tests/phpunit/includes/Rest/EntryPointTest.php
@@ -4,13 +4,12 @@ namespace MediaWiki\Tests\Rest;
use GuzzleHttp\Psr7\Stream;
use GuzzleHttp\Psr7\Uri;
-use MediaWiki\Context\RequestContext;
-use MediaWiki\Request\WebResponse;
-use MediaWiki\Rest\CorsUtils;
+use MediaWiki\MainConfigNames;
use MediaWiki\Rest\EntryPoint;
use MediaWiki\Rest\Handler;
use MediaWiki\Rest\RequestData;
use MediaWiki\Rest\RequestInterface;
+use MediaWiki\Tests\MockEnvironment;
use MediaWikiIntegrationTestCase;
/**
@@ -20,24 +19,34 @@ use MediaWikiIntegrationTestCase;
class EntryPointTest extends MediaWikiIntegrationTestCase {
use RestTestTrait;
+ public function setUp(): void {
+ parent::setUp();
+
+ $this->overrideConfigValue( MainConfigNames::RestPath, '/rest' );
+ }
+
private function createRouter( RequestInterface $request ) {
return $this->newRouter( [
'request' => $request
] );
}
- private function createWebResponse() {
- return $this->getMockBuilder( WebResponse::class )
- ->onlyMethods( [ 'header' ] )
- ->getMock();
- }
-
- private function createCorsUtils() {
- $cors = $this->createMock( CorsUtils::class );
- $cors->method( 'modifyResponse' )
- ->willReturnArgument( 1 );
+ /**
+ * @param RequestData $request
+ * @param MockEnvironment $env
+ *
+ * @return EntryPoint
+ */
+ private function getEntryPoint( RequestData $request, MockEnvironment $env ): EntryPoint {
+ $entryPoint = new EntryPoint(
+ $request,
+ $env->makeFauxContext(),
+ $env,
+ $this->getServiceContainer()
+ );
- return $cors;
+ $entryPoint->setRouter( $this->createRouter( $request ) );
+ return $entryPoint;
}
public static function mockHandlerHeader() {
@@ -51,31 +60,21 @@ class EntryPointTest extends MediaWikiIntegrationTestCase {
}
public function testHeader() {
- $webResponse = $this->createWebResponse();
- $expectedHeaders = [
- 'HTTP/1.1 200 OK',
- 'Foo: Bar',
- ];
- $webResponse->expects( $this->atLeast( count( $expectedHeaders ) ) )
- ->method( 'header' )
- ->willReturnCallback( static function ( $headerString ) use ( &$expectedHeaders ) {
- $headerIdx = array_search( $headerString, $expectedHeaders, true );
- if ( $headerIdx !== false ) {
- unset( $expectedHeaders[$headerIdx] );
- }
- } );
-
- $request = new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/header' ) ] );
+ $uri = '/rest/mock/EntryPoint/header';
+ $request = new RequestData( [ 'uri' => new Uri( $uri ) ] );
- $entryPoint = new EntryPoint(
- RequestContext::getMain(),
+ $env = new MockEnvironment();
+ $env->setRequestInfo( $uri );
+
+ $entryPoint = $this->getEntryPoint(
$request,
- $webResponse,
- $this->createRouter( $request ),
- $this->createCorsUtils()
+ $env
);
- $entryPoint->execute();
- $this->assertCount( 0, $expectedHeaders );
+
+ $entryPoint->enableOutputCapture();
+ $entryPoint->run();
+ $env->assertHeaderValue( 'Bar', 'Foo' );
+ $env->assertStatusCode( 200 );
}
public static function mockHandlerBodyRewind() {
@@ -94,17 +93,22 @@ class EntryPointTest extends MediaWikiIntegrationTestCase {
* Make sure EntryPoint rewinds a seekable body stream before reading.
*/
public function testBodyRewind() {
- $request = new RequestData( [ 'uri' => new Uri( '/rest/mock/EntryPoint/bodyRewind' ) ] );
- $entryPoint = new EntryPoint(
- RequestContext::getMain(),
+ $uri = '/rest/mock/EntryPoint/bodyRewind';
+ $request = new RequestData( [ 'uri' => new Uri( $uri ) ] );
+
+ $env = new MockEnvironment();
+ $env->setRequestInfo( $uri );
+
+ $entryPoint = $this->getEntryPoint(
$request,
- $this->createWebResponse(),
- $this->createRouter( $request ),
- $this->createCorsUtils()
+ $env
);
- ob_start();
- $entryPoint->execute();
- $this->assertSame( 'hello', ob_get_clean() );
+
+ $entryPoint->enableOutputCapture();
+ $entryPoint->run();
+
+ // NOTE: MediaWikiEntryPoint::doPostOutputShutdown flushes all output buffers
+ $this->assertStringContainsString( 'hello', $entryPoint->getCapturedOutput() );
}
}
diff --git a/tests/phpunit/structure/RestStructureTest.php b/tests/phpunit/structure/RestStructureTest.php
index ebaa5ba90420..b28ad8fe31a4 100644
--- a/tests/phpunit/structure/RestStructureTest.php
+++ b/tests/phpunit/structure/RestStructureTest.php
@@ -108,8 +108,7 @@ class RestStructureTest extends MediaWikiIntegrationTestCase {
$services = $this->getFakeServiceContainer();
// NOTE: createRouter() implements the logic for determining the list of route files to load.
- $entryPoint = TestingAccessWrapper::newFromClass( EntryPoint::class );
- $router = $entryPoint->createRouter( $services, $context, new RequestData(), $responseFactory, $cors );
+ $router = EntryPoint::createRouter( $services, $context, new RequestData(), $responseFactory, $cors );
$router = TestingAccessWrapper::newFromObject( $router );
}
@@ -132,8 +131,9 @@ class RestStructureTest extends MediaWikiIntegrationTestCase {
$responseFactory = $this->createNoOpMock( ResponseFactory::class );
$cors = $this->createNoOpMock( CorsUtils::class );
- $this->router = TestingAccessWrapper::newFromClass( EntryPoint::class )
- ->createRouter( $this->getServiceContainer(), $context, new RequestData(), $responseFactory, $cors );
+ $this->router = EntryPoint::createRouter(
+ $this->getServiceContainer(), $context, new RequestData(), $responseFactory, $cors
+ );
}
return $this->router;
}