Santhosh has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/377739 )
Change subject: Refactor the API router to ES6 class ...................................................................... Refactor the API router to ES6 class In follow ups, we are trying to make the router a simple path to handler mapper and handlers in seperate files. This refactoring will help us to provide v2 of apis in future without much code duplication. Change-Id: I10009261b311c80fdea4c300199b73a44880cbe4 --- M lib/routes/v1.js 1 file changed, 281 insertions(+), 223 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/services/cxserver refs/changes/39/377739/1 diff --git a/lib/routes/v1.js b/lib/routes/v1.js index 77fb8d1..3f29352 100644 --- a/lib/routes/v1.js +++ b/lib/routes/v1.js @@ -1,274 +1,332 @@ 'use strict'; -var app, router, registry; +let registry; const sUtil = require( '../util' ), jwt = require( 'jsonwebtoken' ), CXConfig = require( '../Config.js' ); -/** - * The main router object - */ -router = sUtil.router(); +class Routes { + constructor( app, registry ) { + this.app = app; + this.registry = registry; + this.router = sUtil.router(); + if ( !this.app ) { throw new Error( 'Missing app property' ); } + this.registerRoutes(); + } -router.get( '/page/:language/:title/:revision?', function ( req, res ) { - var sourceLanguage = req.params.language, - title = req.params.title, - revision = req.params.revision, - CXSegmenter = require( __dirname + '/../segmentation/CXSegmenter.js' ), - PageLoader = require( __dirname + '/../pageloader/PageLoader.js' ), - pageloader = new PageLoader( app ); + /** + * routes definitions + */ + get routes() { + return { + '/page/:language/:title/:revision?': this.fetchPage.bind( this ), + 'POST /mt/:from/:to/:provider?': this.machineTranslate.bind( this ), + '/dictionary/:word/:from/:to/:provider?': this.dictionary.bind( this ), + '/list/tool/:tool': this.listTool.bind( this ), + '/list/pair/:from/:to': this.listToolForLanguagePair.bind( this ), + '/list/languagepairs': this.listLanguagePairs.bind( this ), + '/list/:tool/:from?/:to?': this.listToolForLanguagePairsAndTool.bind( this ), + 'POST /translate/:from/:to/:provider?': this.translate.bind( this ) + }; + } - return pageloader.load( title, sourceLanguage, revision ).then( - function ( response ) { - var segmenter, segmentedContent; + /** + * It goes through each route defition, figures out the verb/action to use + * (default: get), the function to call to handle the request and registers + * the whole for the given path. + */ + registerRoutes() { + var routes = this.routes; + + Object.keys( routes ).forEach( ( path ) => { + let parts = path.split( ' ' ); + let verb = parts[ 1 ] ? parts[ 0 ] : 'get'; + path = parts[ 1 ] || parts[ 0 ]; + verb = verb.toLowerCase(); + this.router[ verb ]( path, routes[ path ] ); + } ); + } + + fetchPage( req, res ) { + var sourceLanguage = req.params.language, + title = req.params.title, + revision = req.params.revision, + CXSegmenter = require( __dirname + '/../segmentation/CXSegmenter.js' ), + PageLoader = require( __dirname + '/../pageloader/PageLoader.js' ), + pageloader = new PageLoader( this.app ); + + return pageloader.load( title, sourceLanguage, revision ).then( + function ( response ) { + var segmenter, segmentedContent; + + try { + this.app.logger.log( 'debug', 'Fetch page', { + title: title, + sourceLanguage: sourceLanguage + } ); + segmenter = new CXSegmenter( response.body, sourceLanguage ); + segmenter.segment(); + segmentedContent = segmenter.getSegmentedContent(); + this.app.logger.log( 'debug', 'Segment page', { + title: title, + sourceLanguage: sourceLanguage + } ); + } catch ( error ) { + res.status( 500 ).end( `Page ${sourceLanguage} : ${title} could not be fetched or segmented: ` + error.toString() ); + } + res.send( { + sourceLanguage: sourceLanguage, + title: title, + revision: response.revision, + segmentedContent: segmentedContent + } ); + this.app.logger.log( 'debug', 'Page sent' ); + }, + function ( error ) { + res.status( 404 ).end( 'Page ' + sourceLanguage + ':' + title + ' could not be found. ' + error.toString() ); + } + ); + } + + /** + * Get the appropriate machine translation service client + * @param {Request} req request object + * @param {Response} res response object + * @return {mtClient} + */ + getMTClient( req, res ) { + var mtClients, mtClient, provider, + authzToken, jwtConfig, + from = req.params.from, + to = req.params.to; + + provider = this.registry.getValidProvider( from, to, 'mt', req.params.provider ); + + if ( !provider ) { + res.status( 404 ).end( 'Provider not supported' ); + return; + } + + mtClients = require( __dirname + '/../mt/' ); + if ( mtClients[ provider ] === undefined ) { + res.status( 500 ).end( 'Provider not found' ); + return; + } + + mtClient = new mtClients[ provider ]( this.app ); + + if ( mtClient.requiresAuthorization() ) { + if ( !req.headers || !req.headers.authorization ) { + res.status( 403 ).end( 'Authorization header is missing' ); + return; + } + + authzToken = req.headers.authorization; + jwtConfig = this.app.conf.jwt; try { - app.logger.log( 'debug', 'Fetch page', { - title: title, - sourceLanguage: sourceLanguage + jwt.verify( authzToken, jwtConfig.secret, { + algorithms: jwtConfig.algorithms } ); - segmenter = new CXSegmenter( response.body, sourceLanguage ); - segmenter.segment(); - segmentedContent = segmenter.getSegmentedContent(); - app.logger.log( 'debug', 'Segment page', { - title: title, - sourceLanguage: sourceLanguage - } ); - } catch ( error ) { - res.status( 500 ).end( 'Page ' + sourceLanguage + ':' + - title + ' could not be fetched or segmented: ' + error.toString() ); + } catch ( err ) { + res.status( 403 ).end( 'Authorization header is not valid: ' + err ); + return; } - res.send( { - sourceLanguage: sourceLanguage, - title: title, - revision: response.revision, - segmentedContent: segmentedContent - } ); - app.logger.log( 'debug', 'Page sent' ); - }, - function ( error ) { - res.status( 404 ).end( 'Page ' + sourceLanguage + ':' + title + ' could not be found. ' + error.toString() ); } - ); -} ); - -router.get( '/mt/:from/:to/:provider?', function ( req, res ) { - res.status( 405 ).end( 'Request must be posted' ); -} ); - -function getMTClient( req, res ) { - var mtClients, mtClient, provider, - authzToken, jwtConfig, - from = req.params.from, - to = req.params.to; - - provider = registry.getValidProvider( from, to, 'mt', req.params.provider ); - - if ( !provider ) { - res.status( 404 ).end( 'Provider not supported' ); - return; + return mtClient; } - mtClients = require( __dirname + '/../mt/' ); - if ( mtClients[ provider ] === undefined ) { - res.status( 500 ).end( 'Provider not found' ); - return; - } + /** + * Machine translation api handler + * @param {Request} req request object + * @param {Response} res response object + * @return {Promise} + */ + machineTranslate( req, res ) { + var mtClient, sourceHtml, + from = req.params.from, + to = req.params.to; - mtClient = new mtClients[ provider ]( app ); + mtClient = this.getMTClient( req, res ); - if ( mtClient.requiresAuthorization() ) { - if ( !req.headers || !req.headers.authorization ) { - res.status( 403 ).end( 'Authorization header is missing' ); + if ( !mtClient ) { return; } - authzToken = req.headers.authorization; - jwtConfig = app.conf.jwt; + // We support setting html as body or as body.html. But body.html is the recommended way. + // The other way will be removed soon. + sourceHtml = [ '<div>', req.body.html || req.rawBody, '</div>' ].join( '' ); + return mtClient.translate( from, to, sourceHtml ).then( + ( data ) => { + res.json( { + contents: data + } ); + }, + ( error ) => { + res.status( 500 ).end( error.toString() ); + this.app.logger.log( 'error', 'MT processing error: ' + error.stack ); + } + ); + } + /** + * Dictionary api handler + * @param {Request} req request object + * @param {Response} res response object + * @return {Promise} + */ + dictionary( req, res ) { + var dictClients, provider, dictClient, + word = req.params.word, + from = req.params.from, + to = req.params.to; - try { - jwt.verify( authzToken, jwtConfig.secret, { - algorithms: jwtConfig.algorithms - } ); - } catch ( err ) { - res.status( 403 ).end( 'Authorization header is not valid: ' + err ); + provider = this.registry.getValidProvider( from, to, 'dictionary', req.params.provider ); + + if ( !provider ) { + res.status( 404 ).end( 'Dictionary provider invalid or missing' ); return; } - } - return mtClient; -} -router.post( '/mt/:from/:to/:provider?', function ( req, res ) { - var mtClient, sourceHtml, - from = req.params.from, - to = req.params.to; + dictClients = require( __dirname + '/../dictionary/' ); + dictClient = new dictClients[ provider ]( this.app ); - mtClient = getMTClient( req, res ); - - if ( !mtClient ) { - return; + return dictClient.getTranslations( word, from, to ).then( + ( data ) => { + res.send( data ); + }, + ( error ) => { + res.status( 500 ).end( error.toString() ); + this.app.logger.log( 'error', 'Dictionary lookup error: (%s)', error.toString() ); + } + ); } - // We support setting html as body or as body.html. But body.html is the recommended way. - // The other way will be removed soon. - sourceHtml = [ '<div>', req.body.html || req.rawBody, '</div>' ].join( '' ); - return mtClient.translate( from, to, sourceHtml ).then( - function ( data ) { - res.json( { - contents: data - } ); - }, - function ( error ) { - res.status( 500 ).end( error.toString() ); - app.logger.log( 'error', 'MT processing error: ' + error.stack ); - } - ); -} ); + /** + * Get a list of all language pairs that tool supports. + * @param {Request} req request object + * @param {Response} res response object + */ + listTool( req, res ) { + var result, + tool = req.params.tool; -router.get( '/dictionary/:word/:from/:to/:provider?', function ( req, res ) { - var dictClients, provider, dictClient, - word = req.params.word, - from = req.params.from, - to = req.params.to; - - provider = registry.getValidProvider( from, to, 'dictionary', req.params.provider ); - - if ( !provider ) { - res.status( 404 ).end( 'Dictionary provider invalid or missing' ); - return; - } - - dictClients = require( __dirname + '/../dictionary/' ); - dictClient = new dictClients[ provider ]( app ); - - return dictClient.getTranslations( word, from, to ).then( - function ( data ) { - res.send( data ); - }, - function ( error ) { - res.status( 500 ).end( error.toString() ); - app.logger.log( 'error', 'Dictionary lookup error: (%s)', error.toString() ); - } - ); -} ); - -/** - * Get a list of all language pairs that tool supports. - */ -router.get( '/list/tool/:tool', function ( req, res ) { - var result, - tool = req.params.tool; - - if ( tool === 'mt' ) { - result = registry.MTPairs; - } - if ( tool === 'dictionary' ) { - result = registry.DictionaryPairs; - } - - if ( !result ) { - res.status( 404 ).end( 'Unknown tool' ); - return; - } - - res.json( result ); -} ); - -/** - * Lists the available tools for a language pair. - */ -router.get( '/list/pair/:from/:to', function ( req, res ) { - var result, - from = req.params.from, - to = req.params.to; - - result = registry.getToolSet( from, to ); - - res.json( result ); -} ); - -/** - * Get a list of all language pairs. - */ -router.get( '/languagepairs', function ( req, res ) { - res.json( registry.LanguagePairs ); -} ); - -/** - * Get a list of all language pairs. - */ -router.get( '/list/languagepairs', function ( req, res ) { - res.json( registry.LanguagePairs ); -} ); - -router.get( '/list/:tool/:from?/:to?', function ( req, res ) { - var toolset, result = {}, - tool = req.params.tool, - from = req.params.from, - to = req.params.to; - - if ( from && to ) { - toolset = registry.getToolSet( from, to ); - result[ tool ] = toolset[ tool ]; - result[ 'default' ] = toolset.default; - } else if ( tool ) { if ( tool === 'mt' ) { - result = registry.MTPairs; + result = this.registry.MTPairs; } if ( tool === 'dictionary' ) { - result = registry.DictionaryPairs; + result = this.registry.DictionaryPairs; } - } - res.json( result ); -} ); -router.post( '/translate/:from/:to/:provider?', function ( req, res ) { - var mtClient, sourceHtml, machineTranslationRequest, - from = req.params.from, - to = req.params.to; - - if ( req.params.provider ) { - mtClient = getMTClient( req, res ); - if ( !mtClient ) { - // With explicit provider, if not MT Client found, it is an error. + if ( !result ) { + res.status( 404 ).end( 'Unknown tool' ); return; } + + res.json( result ); } - sourceHtml = req.body.html; + /** + * Lists the available tools for a language pair. + * @param {Request} req request object + * @param {Response} res response object + */ + listToolForLanguagePair( req, res ) { + var result, + from = req.params.from, + to = req.params.to; - if ( !mtClient ) { - machineTranslationRequest = Promise.resolve( sourceHtml ); - } else { - machineTranslationRequest = mtClient.translate( from, to, sourceHtml ); + result = this.registry.getToolSet( from, to ); + + res.json( result ); } - return machineTranslationRequest.then( ( translatedHTML ) => { - var CXAdapter = require( __dirname + '/../Adapter' ); + /** + * Get a list of all language pairs. + * @param {Request} req request object + * @param {Response} res response object + */ + listLanguagePairs( req, res ) { + res.json( this.registry.LanguagePairs ); + } - app.conf.mtClient = mtClient; + /** + * @param {Request} req request object + * @param {Response} res response object + */ + listToolForLanguagePairsAndTool( req, res ) { + var toolset, result = {}, + tool = req.params.tool, + from = req.params.from, + to = req.params.to; - return new CXAdapter( from, to, app ) - .adapt( translatedHTML ) - .then( ( adaptedDoc ) => { - res.json( { - contents: adaptedDoc.getHtml() + if ( from && to ) { + toolset = this.registry.getToolSet( from, to ); + result[ tool ] = toolset[ tool ]; + result[ 'default' ] = toolset.default; + } else if ( tool ) { + if ( tool === 'mt' ) { + result = this.registry.MTPairs; + } + if ( tool === 'dictionary' ) { + result = this.registry.DictionaryPairs; + } + } + res.json( result ); + } + + /** + * @param {Request} req request object + * @param {Response} res response object + * @return {Promise} + */ + translate( req, res ) { + var mtClient, sourceHtml, machineTranslationRequest, + from = req.params.from, + to = req.params.to; + + if ( req.params.provider ) { + mtClient = this.getMTClient( req, res ); + if ( !mtClient ) { + // With explicit provider, if not MT Client found, it is an error. + return; + } + } + + sourceHtml = req.body.html; + + if ( !mtClient ) { + machineTranslationRequest = Promise.resolve( sourceHtml ); + } else { + machineTranslationRequest = mtClient.translate( from, to, sourceHtml ); + } + + return machineTranslationRequest.then( ( translatedHTML ) => { + var CXAdapter = require( __dirname + '/../Adapter' ); + + this.app.conf.mtClient = mtClient; + + return new CXAdapter( from, to, this.app ) + .adapt( translatedHTML ) + .then( ( adaptedDoc ) => { + res.json( { + contents: adaptedDoc.getHtml() + } ); + }, ( error ) => { + res.status( 500 ).end( error.stack ); + this.app.logger.log( 'error', 'MT processing error: ' + error.stack ); } ); - }, ( error ) => { - res.status( 500 ).end( error.stack ); - app.logger.log( 'error', 'MT processing error: ' + error.stack ); - } ); - } ); -} ); + } ); + } +} -module.exports = function ( appObj ) { - app = appObj; - registry = registry || new CXConfig( app ); +module.exports = ( appObj ) => { + registry = registry || new CXConfig( appObj ); + const routes = new Routes( appObj, registry ); return { path: '/v1/', /* eslint camelcase:off */ api_version: 1, - router: router, + router: routes.router, skip_domain: true }; }; -- To view, visit https://gerrit.wikimedia.org/r/377739 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I10009261b311c80fdea4c300199b73a44880cbe4 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/services/cxserver Gerrit-Branch: master Gerrit-Owner: Santhosh <santhosh.thottin...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits