Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * UCAPI Client: Create the UCAPI Traverson API client.
- */
- // initialize service name
- var serviceName = 'UCAPI';
- // load helpers
- var helpers = require('./components/helpers');
- // load logger
- var logger = require('./components/log');
- // load constants
- var constants = require('./components/constants');
- // used to create urls
- var url = require('url');
- // used to make requests
- var requestLib = require('request');
- // define errors
- var errors;
- // define config
- var config;
- // define error handler
- var handleUcapiErrorResponse;
- // init UCAPI Cookie; cached and used for subsequent requests; the purpose of this, is to avoid authenticating
- // with username and password for every request, as the logging operation has a high overhead cost
- var ucapiCookie = {};
- // load traverson
- var traverson = require('traverson');
- var JsonHalAdapter = require('traverson-hal');
- traverson.registerMediaType(JsonHalAdapter.mediaType, JsonHalAdapter);
- /**
- * Initialize config
- * @param initConfig Configuration for UCAPI Connection
- * @param initErrors Errors Module Instance
- */
- var init = function (initConfig, initErrors) {
- config = initConfig;
- errors = initErrors;
- // load ucapi error handler
- var ucapiErrorHandler = require('./components/ucapiErrorHandler');
- // init error handler
- ucapiErrorHandler.init(__dirname + '/config/ucapiClientErrorMap.json', errors);
- // get error handling function
- handleUcapiErrorResponse = ucapiErrorHandler.handleUcapiErrorResponse;
- };
- /**
- * UCAPIClient: create UCAPI Traverson Client
- * @param instanceConfig Object with instance configuration. It should contain at least credentials and log properties
- * credentials: {
- * username: 'exampleUser'
- * password: '3x@mpleP@5sw0rd'
- * }
- * If not sent the config object from initialization will be used
- * @constructor
- */
- var UCAPIClient = function (instanceConfig, dataSet) {
- // get instance config merging the one received with the config that module was initialized with
- instanceConfig = helpers.mergeObject(instanceConfig || {}, config.ucapi);
- // cache instance logger
- var log = instanceConfig.log;
- // client options
- var clientOptions = {
- timeout: instanceConfig.requestTimeout || 10000,
- headers: {
- // Add Auth Header
- 'Authorization': 'Basic ' + new Buffer(instanceConfig.credentials.username + ':' + instanceConfig.credentials.password).toString('base64'),
- // Send transaction / request IDs to UCAPI (they can collect it in their logs too)
- 'Transaction-Id': log.data.transactionId,
- 'Parent-Request-Id': log.data.requestId
- },
- // this info will be used by logger
- // we could send logger itself, but traverson destroys it when making clone of data
- '_loggerData': log.data,
- // set username as cookie id on each instance of Request to prevent event's callback to reference closure properties
- '_cookieId': instanceConfig.credentials.username
- };
- // add SSL settings
- if (instanceConfig.https && instanceConfig.https.enabled) {
- clientOptions = helpers.mergeObject(clientOptions, instanceConfig.https);
- }
- // create ucapi resource path
- var ucapiPath = instanceConfig.relativeUrl || instanceConfig.path;
- // set UCAPI base url, if a relativeUrl is not set then the entry point will be the base of the traversal set in config
- var UCAPIBaseURL = url.format({
- protocol: (instanceConfig.https && instanceConfig.https.enabled) ? 'https' : 'http',
- host: `${instanceConfig.hostname}:${instanceConfig.port}`,
- pathname: ucapiPath
- });
- // initialize traversal
- var traversal = traverson
- .from(UCAPIBaseURL)
- .preferEmbeddedResources()
- .jsonHal()
- .withRequestOptions(clientOptions);
- // Modify request module's prototype to add logging to all requests and responses
- // Because traverson uses 'request' module to make calls, this will add logging to all requests
- // NOTE: this adds listeners on all requests using 'request' module, e.g. Logging helper
- // however, since this._loggerData is not populated in Logging helper's, it's not affected
- var request = traversal.getRequestLibrary();
- var requestPrototype = request.Request.prototype;
- // proto is modified only once
- if (!requestPrototype._init) {
- // override init function
- requestPrototype._init = requestPrototype.init;
- requestPrototype.init = function () {
- // HTTP request is initiated
- this.on('request', function (req) {
- if (this._loggerData) {
- var requestLogger = logger.getLogger('client-requests', this._loggerData);
- // copy body into request
- req.body = this.body;
- requestLogger.info('Sending HTTP request', {'http.req': req});
- }
- });
- // Response is completed
- this.on('complete', function (res) {
- if (this._loggerData) {
- // check if UCAPI sends the login cookie, and cache it
- // in case the cookie is malformed, UCAPI will send another one, which will then be re-cached
- if(res.headers['set-cookie']) {
- ucapiCookie[this._cookieId] = res.headers['set-cookie'][0];
- }
- var responseLogger = logger.getLogger('client-responses', this._loggerData);
- responseLogger.info('Received HTTP response', {'http.res': res});
- }
- });
- // Response is redirected
- this.on('redirect', function () {
- if (this._loggerData) {
- var responseLogger = logger.getLogger('client-requests', this._loggerData);
- responseLogger.info('Redirected HTTP response');
- }
- });
- // Request failed (not sent)
- this.on('error', function (e) {
- if (this._loggerData) {
- var log = logger.getLogger(null, this._loggerData);
- // copy body into request
- this.req.body = this.body;
- log.error('Failed to send request: ' + e.stack, { 'http.req': this.req });
- }
- });
- return requestPrototype._init.apply(this, arguments);
- };
- }
- // overwrite newRequest function
- traversal.newRequest = function () {
- // call the function that we overwrite
- var newRequestResult = Object.getPrototypeOf(traversal).newRequest.apply(traversal);
- if(ucapiCookie[instanceConfig.credentials.username]) {
- // init request with the (cached) UCAPI cookie, to avoid re-authentication (which is a costly operation)
- newRequestResult.addRequestOptions({
- headers: { 'Cookie': ucapiCookie[instanceConfig.credentials.username] }
- });
- }
- /**
- * Analize traverson error and build a standard error
- * @param error
- * @returns {Object}
- */
- function getHandledError(error) {
- var response = {};
- // check for timeout
- // for timeout traverson doesn't return a comprehensive error... currently just checking for aborted flag
- if (error.aborted) {
- response.statusCode = 504;
- response.body = errors.getError(
- 'EXTERNAL_API_REQUEST_TIMEOUT',
- {
- 'serviceName': serviceName,
- 'timeout': clientOptions.timeout
- }
- );
- }
- // check for host unreachable or refused connection
- else if (error.code == 'EHOSTUNREACH' || error.code == 'ENOTFOUND' || error.code == 'ECONNREFUSED') {
- response.statusCode = 502;
- response.body = errors.getError(
- 'EXTERNAL_API_CONNECTION_ERROR',
- {
- 'serviceName': serviceName,
- 'message': error.code
- }
- );
- }
- // check correct http response as traverson might return it as an error
- else if (error.name == 'HTTPError' && error.httpStatus) {
- response.statusCode = error.httpStatus;
- response.body = error.body;
- }
- // other error which is not handled separately
- else {
- response.statusCode = 502;
- response.body = errors.getError(
- 'EXTERNAL_API_SERVER_ERROR',
- {
- 'serviceName': serviceName,
- 'message': error.message ? error.message : (error.code ? error.code : 'null response')
- }
- );
- }
- return response;
- }
- // overwrite getResource function (for logging)
- newRequestResult.getResource = function (callback) {
- // pre execution callback
- var preCallback = function (err, response, traversal) {
- var link = newRequestResult.links[newRequestResult.links.length - 1].value;
- // check error
- if (err) {
- log.warn('Failed to retrieve resource from link', link, ':', err.message);
- // handled error
- err = getHandledError(err);
- } else {
- // log the response of the function
- log.info('Retrieved resource from', link);
- }
- // execute the initial callback
- callback(err, response, traversal);
- };
- // call the function that we overwrite
- Object.getPrototypeOf(newRequestResult).getResource.call(newRequestResult, preCallback);
- };
- // overwrite http methods function
- var httpMethods = ['get', 'put', 'post', 'delete'];
- httpMethods.forEach(function (method) {
- newRequestResult[method] = function () {
- // get function arguments
- var args = Array.prototype.slice.call(arguments);
- // callback is expected to be last argument of the function
- // cache it to use in preCallback
- var callback = args.pop();
- // pre execution callback
- var preCallback = function (error, response, traversal) {
- // check for error
- if (error) {
- // handled error
- response = getHandledError(error);
- } else {
- // parse the response.body to JSON
- if (response.body) {
- try {
- response.jsonBody = JSON.parse(response.body);
- // check if the response contains an error
- if (response.jsonBody.error) {
- response = handleUcapiErrorResponse(response.jsonBody.error, dataSet);
- }
- } catch (e) {
- // return parse error response in case json parsing fails
- response.body = errors.getError(
- 'EXTERNAL_API_PARSE_ERROR',
- {
- 'serviceName': serviceName,
- 'message': e.message
- }
- );
- response.statusCode = 500;
- // log the parsing problem
- log.error('Error parsing UCAPI JSON response:', e.message, { 'http.res': response });
- }
- }
- }
- // call the callback
- callback(error, response, traversal);
- };
- // add preCallback as the callback to traverson function
- args.push(preCallback);
- // call the function that we overwrite and return the result of it
- return Object.getPrototypeOf(newRequestResult)[method].apply(newRequestResult, args);
- };
- });
- return newRequestResult;
- };
- /**
- * Used for following an embedded resource based on a given array of resource names.
- * It traverses only embedded resources.
- * It works with array resources too, for example:
- * array[$all] gets all items from an embedded array resource and stops
- * array[0] gets first item from an embedded array and continues to the next resource name in the array
- * @param source Source object to traverse and look for desired resource
- * @param links Array of resource names to traverse and fetch the resource
- * @returns {*} Returns null in case the resource is not found, otherwise returns the resource reference
- */
- this.followResource = function (source, links) {
- if (!source.hasOwnProperty('_embedded')) {
- return null;
- }
- // double check if links property is array
- links = Array.isArray(links) ? links : [links];
- // target is source object at first iteration over link entries, if source doesn't have any embedded the search stops
- var target = source;
- // traverse source
- for (var i = 0; i < links.length; i++) {
- // at every link entry iteration we check precedent target resource for the desired embedded resource
- var embedded = target._embedded;
- var linkEntry = links[i];
- // check if link entry is an array
- var arrayIndex = linkEntry.indexOf('[');
- if (arrayIndex !== -1) {
- // cache array reference
- var arrayRef = embedded[linkEntry.slice(0, arrayIndex)] || [];
- // check if is an array of all elements
- if (linkEntry.indexOf('[$all]') !== -1) {
- // fetch the desired resource and return the function immediately
- target = arrayRef;
- break;
- }
- // if the resource is an item of the array update target resource and continue to the next link
- var arrayItem = arrayRef[linkEntry.slice(arrayIndex + 1, linkEntry.indexOf(']'))];
- if (!arrayItem) {
- return null;
- }
- // update target resource
- target = arrayItem;
- } else {
- // look for desired resource in embedded
- var objectRef = embedded[linkEntry];
- if (!objectRef) {
- return null;
- }
- // update target resource
- target = objectRef;
- }
- }
- return target;
- };
- /**
- * Send Media File multipart request to UCAPI
- * @param options {*} containing the following properties:
- * requestBody (any properties that will be mapped in the multipart content,
- * mediaFileId (id to use for searching the file in media file service and stream it to UCAPI)
- * method (request's method)
- * @param callback Callback to execute after the request has been completed (err, response, responseBody)
- */
- this.sendMediaFileMultipartRequest = function (options, callback) {
- options = options || {};
- // merge client options with the options given as function parameter
- options = helpers.mergeObject(options, clientOptions);
- // create media file url
- var mediaFileUrl = url.format({
- protocol: (config.https && config.https.enabled) ? 'https' : 'http',
- host: `${config.gateway.hostname}:${config.gateway.port}`,
- pathname: `${constants.urls.mediaFile}/uploads`
- });
- // create request options
- var requestOptions = helpers.mergeObject(clientOptions, {
- url: UCAPIBaseURL,
- formData: options.requestBody
- });
- // add the media file in the request body and set it to response writable stream from media file service request
- requestOptions.formData.mediaFile = requestLib.get(`${mediaFileUrl}/${options.mediaFileId}`, {
- auth: {
- bearer: require('./authValidationClient').generateJWTBearerSimpleToken(config.authentication, instanceConfig.log)
- }
- });
- // send multipart request to UCAPI with the file contents streaming from media file service
- requestLib[options.method.toLowerCase()](requestOptions, callback);
- };
- // set prototype of traversal
- Object.setPrototypeOf(this, traversal);
- };
- /**
- * @function getInstance Return an instance of the UCAPI Traverson Client
- * @returns {UCAPI Traverson Client}
- */
- function getInstance(instanceConfig, dataset) {
- dataset = dataset || {};
- return new UCAPIClient(instanceConfig, dataset);
- }
- // Exports creating a Wit eFax client
- module.exports = {
- init: init,
- getInstance: getInstance
- };
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement