* Add HttpRequestFactory::createMultiClient(), which returns a MultiHttpClient with configured defaults applied. This is similar to the recently-deprecated Http::createMultiClient(). * Introduce $wgHTTPMaxTimeout and $wgHTTPMaxConnectTimeout which, if set to a lower value than their defaults of infinity, will limit the applied HTTP timeouts, whether configured or passed on a per-request basis. This is based on the frequently correct assumption that ops know more about timeouts than developers. * In case developers believe, after becoming aware of this new situation, that they actually do know more about timeouts than ops, it is possible to override the configured maximum by passing similarly named options to HttpRequestFactory::createMultiClient() and HttpRequestFactory::create(). * Apply modern standards to HttpRequestFactory by injecting a logger and all configuration parameters used by its backends. * As in Http, the new createMultiClient() will use a MediaWiki/1.35 User-Agent and the 'http' channel for logging. * Document that no proxy will be used for createMultiClient(). Proxy config is weird and was previously a good reason to use MultiHttpClient over HttpRequestFactory. * Deprecate direct construction of MWHttpRequest without a timeout parameter Bug: T245170 Change-Id: I8252f6c854b98059f4916d5460ea71cf4b580149
234 lines
5.7 KiB
PHP
234 lines
5.7 KiB
PHP
<?php
|
|
|
|
use MediaWiki\Config\ServiceOptions;
|
|
use MediaWiki\Http\HttpRequestFactory;
|
|
use Psr\Log\NullLogger;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
/**
|
|
* @covers MediaWiki\Http\HttpRequestFactory
|
|
*/
|
|
class HttpRequestFactoryTest extends MediaWikiTestCase {
|
|
|
|
/**
|
|
* @return HttpRequestFactory
|
|
*/
|
|
private function newFactory( $options = null ) {
|
|
if ( !$options ) {
|
|
$options = [
|
|
'HTTPTimeout' => 1,
|
|
'HTTPConnectTimeout' => 1,
|
|
'HTTPMaxTimeout' => INF,
|
|
'HTTPMaxConnectTimeout' => INF
|
|
];
|
|
}
|
|
return new HttpRequestFactory(
|
|
new ServiceOptions( HttpRequestFactory::CONSTRUCTOR_OPTIONS, $options ),
|
|
new NullLogger
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return HttpRequestFactory
|
|
*/
|
|
private function newFactoryWithFakeRequest(
|
|
MWHttpRequest $req,
|
|
$expectedUrl,
|
|
$expectedOptions = []
|
|
) {
|
|
$factory = $this->getMockBuilder( HttpRequestFactory::class )
|
|
->setMethods( [ 'create' ] )
|
|
->disableOriginalConstructor()
|
|
->getMock();
|
|
|
|
$factory->method( 'create' )
|
|
->willReturnCallback(
|
|
function ( $url, array $options = [], $caller = __METHOD__ )
|
|
use ( $req, $expectedUrl, $expectedOptions )
|
|
{
|
|
$this->assertSame( $url, $expectedUrl );
|
|
|
|
foreach ( $expectedOptions as $opt => $exp ) {
|
|
$this->assertArrayHasKey( $opt, $options );
|
|
$this->assertSame( $exp, $options[$opt] );
|
|
}
|
|
|
|
return $req;
|
|
}
|
|
);
|
|
|
|
return $factory;
|
|
}
|
|
|
|
/**
|
|
* @return MWHttpRequest
|
|
*/
|
|
private function newFakeRequest( $result ) {
|
|
$req = $this->getMockBuilder( MWHttpRequest::class )
|
|
->disableOriginalConstructor()
|
|
->setMethods( [ 'getContent', 'execute' ] )
|
|
->getMock();
|
|
|
|
if ( $result instanceof Status ) {
|
|
$req->method( 'getContent' )
|
|
->willReturn( $result->getValue() );
|
|
$req->method( 'execute' )
|
|
->willReturn( $result );
|
|
} else {
|
|
$req->method( 'getContent' )
|
|
->willReturn( $result );
|
|
$req->method( 'execute' )
|
|
->willReturn( Status::newGood( $result ) );
|
|
}
|
|
|
|
return $req;
|
|
}
|
|
|
|
public function testCreate() {
|
|
$factory = $this->newFactory();
|
|
$this->assertInstanceOf( 'MWHttpRequest', $factory->create( 'http://example.test' ) );
|
|
}
|
|
|
|
public function testGetUserAgent() {
|
|
$factory = $this->newFactory();
|
|
$this->assertStringStartsWith( 'MediaWiki/', $factory->getUserAgent() );
|
|
}
|
|
|
|
public function testGet() {
|
|
$req = $this->newFakeRequest( __METHOD__ );
|
|
$factory = $this->newFactoryWithFakeRequest(
|
|
$req, 'https://example.test', [ 'method' => 'GET' ]
|
|
);
|
|
|
|
$this->assertSame( __METHOD__, $factory->get( 'https://example.test' ) );
|
|
}
|
|
|
|
public function testPost() {
|
|
$req = $this->newFakeRequest( __METHOD__ );
|
|
$factory = $this->newFactoryWithFakeRequest(
|
|
$req, 'https://example.test', [ 'method' => 'POST' ]
|
|
);
|
|
|
|
$this->assertSame( __METHOD__, $factory->post( 'https://example.test' ) );
|
|
}
|
|
|
|
public function testRequest() {
|
|
$req = $this->newFakeRequest( __METHOD__ );
|
|
$factory = $this->newFactoryWithFakeRequest(
|
|
$req, 'https://example.test', [ 'method' => 'GET' ]
|
|
);
|
|
|
|
$this->assertSame( __METHOD__, $factory->request( 'GET', 'https://example.test' ) );
|
|
}
|
|
|
|
public function testRequest_failed() {
|
|
$status = Status::newFatal( 'testing' );
|
|
$req = $this->newFakeRequest( $status );
|
|
$factory = $this->newFactoryWithFakeRequest(
|
|
$req, 'https://example.test', [ 'method' => 'POST' ]
|
|
);
|
|
|
|
$this->assertNull( $factory->request( 'POST', 'https://example.test' ) );
|
|
}
|
|
|
|
public static function provideCreateTimeouts() {
|
|
return [
|
|
'normal config defaults' => [
|
|
[
|
|
'HTTPTimeout' => 10,
|
|
'HTTPConnectTimeout' => 20,
|
|
'HTTPMaxTimeout' => INF,
|
|
'HTTPMaxConnectTimeout' => INF
|
|
],
|
|
[],
|
|
[
|
|
'timeout' => 10,
|
|
'connectTimeout' => 20
|
|
]
|
|
],
|
|
'config defaults overridden by max' => [
|
|
[
|
|
'HTTPTimeout' => 10,
|
|
'HTTPConnectTimeout' => 20,
|
|
'HTTPMaxTimeout' => 9,
|
|
'HTTPMaxConnectTimeout' => 11
|
|
],
|
|
[],
|
|
[
|
|
'timeout' => 9,
|
|
'connectTimeout' => 11
|
|
]
|
|
],
|
|
'create option overridden by max config' => [
|
|
[
|
|
'HTTPTimeout' => 1,
|
|
'HTTPConnectTimeout' => 2,
|
|
'HTTPMaxTimeout' => 9,
|
|
'HTTPMaxConnectTimeout' => 11
|
|
],
|
|
[
|
|
'timeout' => 100,
|
|
'connectTimeout' => 200
|
|
],
|
|
[
|
|
'timeout' => 9,
|
|
'connectTimeout' => 11
|
|
]
|
|
],
|
|
'create option below max config' => [
|
|
[
|
|
'HTTPTimeout' => 1,
|
|
'HTTPConnectTimeout' => 2,
|
|
'HTTPMaxTimeout' => 9,
|
|
'HTTPMaxConnectTimeout' => 11
|
|
],
|
|
[
|
|
'timeout' => 7,
|
|
'connectTimeout' => 8
|
|
],
|
|
[
|
|
'timeout' => 7,
|
|
'connectTimeout' => 8
|
|
]
|
|
],
|
|
'max config overridden by max create option ' => [
|
|
[
|
|
'HTTPTimeout' => 1,
|
|
'HTTPConnectTimeout' => 2,
|
|
'HTTPMaxTimeout' => 9,
|
|
'HTTPMaxConnectTimeout' => 11
|
|
],
|
|
[
|
|
'timeout' => 100,
|
|
'connectTimeout' => 200,
|
|
'maxTimeout' => 100,
|
|
'maxConnectTimeout' => 200
|
|
],
|
|
[
|
|
'timeout' => 100,
|
|
'connectTimeout' => 200
|
|
]
|
|
],
|
|
];
|
|
}
|
|
|
|
/** @dataProvider provideCreateTimeouts */
|
|
public function testCreateTimeouts( $config, $createOptions, $expected ) {
|
|
$factory = $this->newFactory( $config );
|
|
$request = $factory->create( 'https://example.test', $createOptions );
|
|
$request = TestingAccessWrapper::newFromObject( $request );
|
|
foreach ( $expected as $key => $expectedValue ) {
|
|
$this->assertEquals( $expectedValue, $request->$key, "key $key" );
|
|
}
|
|
}
|
|
|
|
/** @dataProvider provideCreateTimeouts */
|
|
public function testCreateMultiTimeouts( $config, $createOptions, $expected ) {
|
|
$factory = $this->newFactory( $config );
|
|
$multi = $factory->createMultiClient( $createOptions );
|
|
$multi = TestingAccessWrapper::newFromObject( $multi );
|
|
$this->assertEquals( $expected['connectTimeout'], $multi->connTimeout );
|
|
$this->assertEquals( $expected['timeout'], $multi->reqTimeout );
|
|
}
|
|
|
|
}
|