From b13bd0996cbd0b179d869dc25bd22c17a1633995 Mon Sep 17 00:00:00 2001 From: Marko Obrovac Date: Mon, 2 Mar 2015 15:35:21 +0100 Subject: [PATCH] Introduce the Restbase Virtual REST Service class Restbase, the REST content API service, is to be queried instead of Parsoid by current Parsoid users (most importantly VE). This patch introduces the Restbase virtual REST service class and transparently maps Parsoid calls into Restbase ones if parsoidCompat is set when creating the service object. Additionally, $wgVirtualRestConfig is introduced in DefaultSettings.php. This is a first step towards global service configuration and management. Bug: T89066 Change-Id: I4d4043e5052327bbd789331f1c05b607c45fe7cb --- autoload.php | 1 + includes/DefaultSettings.php | 28 +++ .../virtualrest/ParsoidVirtualRESTService.php | 9 +- .../RestbaseVirtualRESTService.php | 177 ++++++++++++++++++ 4 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 includes/libs/virtualrest/RestbaseVirtualRESTService.php diff --git a/autoload.php b/autoload.php index faf8252481..da61403123 100644 --- a/autoload.php +++ b/autoload.php @@ -995,6 +995,7 @@ $wgAutoloadLocalClasses = array( 'ResourceLoaderUserOptionsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserOptionsModule.php', 'ResourceLoaderUserTokensModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserTokensModule.php', 'ResourceLoaderWikiModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderWikiModule.php', + 'RestbaseVirtualRESTService' => __DIR__ . '/includes/libs/virtualrest/RestbaseVirtualRESTService.php', 'ResultWrapper' => __DIR__ . '/includes/db/DatabaseUtility.php', 'RevDelArchiveItem' => __DIR__ . '/includes/revisiondelete/RevDelArchiveItem.php', 'RevDelArchiveList' => __DIR__ . '/includes/revisiondelete/RevDelArchiveList.php', diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 8080774ffb..52c0eab22b 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -7420,6 +7420,34 @@ $wgPageLanguageUseDB = false; */ $wgUseLinkNamespaceDBFields = true; +/** + * 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. + * + * Example config for Parsoid: + * + * $wgVirtualRestConfig['modules']['parsoid'] = array( + * 'url' => 'http://localhost:8000', + * 'prefix' => 'enwiki', + * ); + * + * @var array + * @since 1.25 + */ +$wgVirtualRestConfig = array( + 'modules' => array(), + 'global' => array( + # Timeout in seconds + 'timeout' => 360, + 'forwardCookies' => false, + 'HTTPProxy' => null + ) +); + /** * For really cool vim folding this needs to be at the end: * vim: foldmarker=@{,@} foldmethod=marker diff --git a/includes/libs/virtualrest/ParsoidVirtualRESTService.php b/includes/libs/virtualrest/ParsoidVirtualRESTService.php index 769cecf3e5..32a27f7956 100644 --- a/includes/libs/virtualrest/ParsoidVirtualRESTService.php +++ b/includes/libs/virtualrest/ParsoidVirtualRESTService.php @@ -34,13 +34,18 @@ class ParsoidVirtualRESTService extends VirtualRESTService { * * body: array( 'wikitext' => ... ) or array( 'wikitext' => ..., 'body' => true/false ) * * $title is optional * @param array $params Key/value map - * - URL : Parsoid server URL + * - url : Parsoid server URL * - prefix : Parsoid prefix for this wiki * - timeout : Parsoid timeout (optional) * - forwardCookies : Cookies to forward to Parsoid, or false. (optional) * - HTTPProxy : Parsoid HTTP proxy (optional) */ public function __construct( array $params ) { + // for backwards compatibility: + if ( isset( $params['URL'] ) ) { + $params['url'] = $params['URL']; + unset( $params['URL'] ); + } parent::__construct( $params ); } @@ -63,7 +68,7 @@ class ParsoidVirtualRESTService extends VirtualRESTService { throw new Exception( "Request type must be either 'page' or 'transform'" ); } - $req['url'] = $this->params['URL'] . '/' . urlencode( $this->params['prefix'] ) . '/'; + $req['url'] = $this->params['url'] . '/' . urlencode( $this->params['prefix'] ) . '/'; if ( $reqType === 'page' ) { $title = $parts[3]; diff --git a/includes/libs/virtualrest/RestbaseVirtualRESTService.php b/includes/libs/virtualrest/RestbaseVirtualRESTService.php new file mode 100644 index 0000000000..8fe5b9214b --- /dev/null +++ b/includes/libs/virtualrest/RestbaseVirtualRESTService.php @@ -0,0 +1,177 @@ + ... ) + * POST /local/v1/transform/wikitext/to/html{/title}{/revision} + * * body: array( 'wikitext' => ... ) or array( 'wikitext' => ..., 'bodyOnly' => true/false ) + * + * @param array $params Key/value map + * - url : Restbase server URL + * - domain : Wiki domain to use + * - timeout : request timeout in seconds (optional) + * - forwardCookies : cookies to forward to Restbase/Parsoid (as a Cookie + * header string) or false (optional) + * Note: forwardCookies will in the future be a boolean + * only, signifing request cookies should be forwarded + * to the service; the current state is due to the way + * VE handles this particular parameter + * - HTTPProxy : HTTP proxy to use (optional) + * - parsoidCompat : whether to parse URL as if they were meant for Parsoid + * boolean (optional) + */ + public function __construct( array $params ) { + // set up defaults and merge them with the given params + $mparams = array_merge( array( + 'url' => 'http://localhost:7231', + 'domain' => 'localhost', + 'timeout' => 100, + 'forwardCookies' => false, + 'HTTPProxy' => null, + 'parsoidCompat' => false + ), $params ); + // ensure the correct domain format + $mparams['domain'] = preg_replace( + '/^(https?:\/\/)?([^\/:]+?)(\/|:\d+\/?)?$/', + '$2', + $mparams['domain'] + ); + parent::__construct( $mparams ); + } + + public function onRequests( array $reqs, Closure $idGenFunc ) { + + if ( $this->params['parsoidCompat'] ) { + return $this->onParsoidRequests( $reqs, $idGenFunc ); + } + + $result = array(); + foreach ( $reqs as $key => $req ) { + // replace /local/ with the current domain + $req['url'] = preg_replace( '/^\/local\//', '/' . $this->params['domain'] . '/', $req['url'] ); + // and prefix it with the service URL + $req['url'] = $this->params['url'] . $req['url']; + // set the appropriate proxy, timeout and headers + if ( $this->params['HTTPProxy'] ) { + $req['proxy'] = $this->params['HTTPProxy']; + } + if ( $this->params['timeout'] != null ) { + $req['reqTimeout'] = $this->params['timeout']; + } + if ( $this->params['forwardCookies'] ) { + $req['headers']['Cookie'] = $this->params['forwardCookies']; + } + $result[$key] = $req; + } + + return $result; + + } + + /** + * Remaps Parsoid requests to Restbase paths + */ + public function onParsoidRequests( array $reqs, Closure $idGeneratorFunc ) { + + $result = array(); + foreach ( $reqs as $key => $req ) { + $parts = explode( '/', $req['url'] ); + list( + $targetWiki, // 'local' + $version, // 'v1' + $reqType // 'page' or 'transform' + ) = $parts; + if ( $targetWiki !== 'local' ) { + throw new Exception( "Only 'local' target wiki is currently supported" ); + } elseif ( $reqType !== 'page' && $reqType !== 'transform' ) { + throw new Exception( "Request type must be either 'page' or 'transform'" ); + } + $req['url'] = $this->params['url'] . '/' . $this->params['domain'] . '/v1/' . $reqType . '/'; + if ( $reqType === 'page' ) { + $title = $parts[3]; + if ( $parts[4] !== 'html' ) { + throw new Exception( "Only 'html' output format is currently supported" ); + } + $req['url'] .= 'html/' . $title; + if ( isset( $parts[5] ) ) { + $req['url'] .= '/' . $parts[5]; + } elseif ( isset( $req['query']['oldid'] ) && $req['query']['oldid'] ) { + $req['url'] .= '/' . $req['query']['oldid']; + unset( $req['query']['oldid'] ); + } + } elseif ( $reqType === 'transform' ) { + // from / to transform + $req['url'] .= $parts[3] . '/to/' . $parts[5]; + // the title + if ( isset( $parts[6] ) ) { + $req['url'] .= '/' . $parts[6]; + } + // revision id + if ( isset( $parts[7] ) ) { + $req['url'] .= '/' . $parts[7]; + } elseif ( isset( $req['body']['oldid'] ) && $req['body']['oldid'] ) { + $req['url'] .= '/' . $req['body']['oldid']; + unset( $req['body']['oldid'] ); + } + if ( $parts[4] !== 'to' ) { + throw new Exception( "Part index 4 is not 'to'" ); + } + if ( $parts[3] === 'html' & $parts[5] === 'wikitext' ) { + if ( !isset( $req['body']['html'] ) ) { + throw new Exception( "You must set an 'html' body key for this request" ); + } + } elseif ( $parts[3] == 'wikitext' && $parts[5] == 'html' ) { + if ( !isset( $req['body']['wikitext'] ) ) { + throw new Exception( "You must set a 'wikitext' body key for this request" ); + } + if ( isset( $req['body']['body'] ) ) { + $req['body']['bodyOnly'] = $req['body']['body']; + unset( $req['body']['body'] ); + } + } else { + throw new Exception( "Transformation unsupported" ); + } + } + // set the appropriate proxy, timeout and headers + if ( $this->params['HTTPProxy'] ) { + $req['proxy'] = $this->params['HTTPProxy']; + } + if ( $this->params['timeout'] != null ) { + $req['reqTimeout'] = $this->params['timeout']; + } + if ( $this->params['forwardCookies'] ) { + $req['headers']['Cookie'] = $this->params['forwardCookies']; + } + $result[$key] = $req; + } + + return $result; + + } + +} -- 2.20.1