/**
* Global configuration variable for Virtual REST Services.
- * Parameters for different services are to be declared inside
- * $wgVirtualRestConfig['modules'], which is to be treated as an associative
- * array. Global parameters will be merged with service-specific ones. The
- * result will then be passed to VirtualRESTService::__construct() in the
- * module.
+ *
+ * Use the 'path' key to define automatically mounted services. The value for this
+ * key is a map of path prefixes to service configuration. The later is an array of:
+ * - class : the fully qualified class name
+ * - options : map of arguments to the class constructor
+ * Such services will be available to handle queries under their path from the VRS
+ * singleton, e.g. MediaWikiServices::getInstance()->getVirtualRESTServiceClient();
+ *
+ * Auto-mounting example for Parsoid:
+ *
+ * $wgVirtualRestConfig['paths']['/parsoid/'] = [
+ * 'class' => 'ParsoidVirtualRESTService',
+ * 'options' => [
+ * 'url' => 'http://localhost:8000',
+ * 'prefix' => 'enwiki',
+ * 'domain' => 'en.wikipedia.org'
+ * ]
+ * ];
+ *
+ * Parameters for different services can also be declared inside the 'modules' value,
+ * which is to be treated as an associative array. The parameters in 'global' will be
+ * merged with service-specific ones. The result will then be passed to
+ * VirtualRESTService::__construct() in the module.
*
* Example config for Parsoid:
*
* @since 1.25
*/
$wgVirtualRestConfig = [
+ 'paths' => [],
'modules' => [],
'global' => [
# Timeout in seconds
return $services->getService( '_MediaWikiTitleCodec' );
},
+ 'VirtualRESTServiceClient' => function( MediaWikiServices $services ) {
+ $config = $services->getMainConfig()->get( 'VirtualRestConfig' );
+
+ $vrsClient = new VirtualRESTServiceClient( new MultiHttpClient( [] ) );
+ foreach ( $config['paths'] as $prefix => $serviceConfig ) {
+ $class = $serviceConfig['class'];
+ // Merge in the global defaults
+ $constructArg = isset( $serviceConfig['options'] )
+ ? $serviceConfig['options']
+ : [];
+ $constructArg += $config['global'];
+ // Make the VRS service available at the mount point
+ $vrsClient->mount( $prefix, [ 'class' => $class, 'config' => $constructArg ] );
+ }
+
+ return $vrsClient;
+ },
+
///////////////////////////////////////////////////////////////////////////
// NOTE: When adding a service here, don't forget to add a getter function
// in the MediaWikiServices class. The convenience getter should just call
*/
class VirtualRESTServiceClient {
/** @var MultiHttpClient */
- protected $http;
- /** @var VirtualRESTService[] Map of (prefix => VirtualRESTService) */
- protected $instances = [];
+ private $http;
+ /** @var array Map of (prefix => VirtualRESTService|array) */
+ private $instances = [];
const VALID_MOUNT_REGEX = '#^/[0-9a-z]+/([0-9a-z]+/)*$#';
/**
* Map a prefix to service handler
*
+ * If $instance is in array, it must have these keys:
+ * - class : string; fully qualified VirtualRESTService class name
+ * - config : array; map of parameters that is the first __construct() argument
+ *
* @param string $prefix Virtual path
- * @param VirtualRESTService $instance
+ * @param VirtualRESTService|array $instance Service or info to yield the service
*/
- public function mount( $prefix, VirtualRESTService $instance ) {
+ public function mount( $prefix, $instance ) {
if ( !preg_match( self::VALID_MOUNT_REGEX, $prefix ) ) {
throw new UnexpectedValueException( "Invalid service mount point '$prefix'." );
} elseif ( isset( $this->instances[$prefix] ) ) {
throw new UnexpectedValueException( "A service is already mounted on '$prefix'." );
}
+ if ( !( $instance instanceof VirtualRESTService ) ) {
+ if ( !isset( $instance['class'] ) || !isset( $instance['config'] ) ) {
+ throw new UnexpectedValueException( "Missing 'class' or 'config' ('$prefix')." );
+ }
+ }
$this->instances[$prefix] = $instance;
}
};
$matches = []; // matching prefixes (mount points)
- foreach ( $this->instances as $prefix => $service ) {
+ foreach ( $this->instances as $prefix => $unused ) {
if ( strpos( $path, $prefix ) === 0 ) {
$matches[] = $prefix;
}
usort( $matches, $cmpFunc );
// Return the most specific prefix and corresponding service
- return isset( $matches[0] )
- ? [ $matches[0], $this->instances[$matches[0]] ]
+ return $matches
+ ? [ $matches[0], $this->getInstance( $matches[0] ) ]
: [ null, null ];
}
// defer the original or to set a proxy response to the original.
$newReplaceReqsByService = [];
foreach ( $replaceReqsByService as $prefix => $servReqs ) {
- $service = $this->instances[$prefix];
+ $service = $this->getInstance( $prefix );
foreach ( $service->onRequests( $servReqs, $idFunc ) as $index => $req ) {
// Services use unique IDs for replacement requests
if ( isset( $servReqs[$index] ) || isset( $origPending[$index] ) ) {
// forced by setting 'response' rather than actually be sent over the wire.
$newReplaceReqsByService = [];
foreach ( $checkReqIndexesByPrefix as $prefix => $servReqIndexes ) {
- $service = $this->instances[$prefix];
+ $service = $this->getInstance( $prefix );
// $doneReqs actually has the requests (with 'response' set)
$servReqs = array_intersect_key( $doneReqs, $servReqIndexes );
foreach ( $service->onResponses( $servReqs, $idFunc ) as $index => $req ) {
return $responses;
}
+
+ /**
+ * @param string $prefix
+ * @return VirtualRESTService
+ */
+ private function getInstance( $prefix ) {
+ if ( !isset( $this->instances[$prefix] ) ) {
+ throw new RunTimeException( "No service registered at prefix '{$prefix}'." );
+ }
+
+ if ( !( $this->instances[$prefix] instanceof VirtualRESTService ) ) {
+ $config = $this->instances[$prefix]['config'];
+ $class = $this->instances[$prefix]['class'];
+ $service = new $class( $config );
+ if ( !( $service instanceof VirtualRESTService ) ) {
+ throw new UnexpectedValueException( "Registered service has the wrong class." );
+ }
+ $this->instances[$prefix] = $service;
+ }
+
+ return $this->instances[$prefix];
+ }
}