blob: 617df77ec7577a271de35d6b4160201712ee6866 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
var Deferred = require('./../lib/deferred');
var Promise = require('./../lib/promise');
var asyncCall = require('../lib/async-call');
var InspectableFunction = require('../lib/inspectable-function');
var vlog = require('./../lib/vlog');
var inspector = require('./../lib/arg-inspector');
var Invoker = require('./../invocation/invoker');
var defaultAuthorizer = require('../security/default-authorizer');
var actions = require('./../verror/actions');
var makeError = require('../verror/make-errors');
var ServerOption = require('./server-option');
var nextServerID = 1; // The ID for the next server.
/**
* @summary
* Server defines the interface for managing a collection of services.
* @description
* <p>Private Constructor, use
* [Runtime#newServer]{@link module:vanadium~Runtime#newServer} or
* [Runtime#newServerDispatchingServer]
* {@link module:vanadium~Runtime#newServerDispatchingServer}
* </p>
* @inner
* @constructor
* @memberof module:vanadium.rpc
*/
function Server(router) {
if (!(this instanceof Server)) {
return new Server(router);
}
this._router = router;
this._rootCtx = router._rootCtx;
this._handle = 0;
this.id = nextServerID++;
this.dispatcher = null;
this.serviceObjectHandles = {};
}
/**
* Stop gracefully stops all services on this Server.
* New calls are rejected, but any in-flight calls are allowed to complete.
* All published named are unmounted.
* @param {module:vanadium~voidCb} [cb] If provided, the function
* will be called on completion.
* @return {Promise<void>} Promise to be called when stop service completes or
* fails
*/
Server.prototype.stop = function(cb) {
return this._router.stopServer(this, cb);
};
/**
* Adds the specified name to the mount table for the object or dispatcher
* used to create this server.
* @public
* @param {string} name Name to publish.
* @param {module:vanadium~voidCb} [cb] If provided, the function
* will be called on completion.
* @return {Promise<void>} Promise to be called when operation completes or
* fails
*/
Server.prototype.addName = function(name, cb) {
return this._router.addName(name, this, cb);
};
/**
* Removes the specified name from the mount table.
* @public
* @param {string} name Name to remove.
* @param {function} [cb] If provided, the function will be called on
* completion. The only argument is an error if there was one.
* @return {Promise<void>} Promise to be called when operation completes or
* fails.
*/
Server.prototype.removeName = function(name, cb) {
return this._router.removeName(name, this, cb);
};
/*
* Initializes the JavaScript server by creating a server object on
* the WSPR side.
* @private
*/
Server.prototype._init = function(name, dispatcher,
serverOption, cb) {
this.serverOption = serverOption || new ServerOption();
this.dispatcher = dispatcher;
return this._router.newServer(name, this, cb);
};
/**
* @private
* @param {Number} handle The handle for the service.
* @return {Object} The invoker corresponding to the provided error.
*/
Server.prototype._getInvokerForHandle = function(handle) {
var result = this.serviceObjectHandles[handle];
delete this.serviceObjectHandles[handle];
return result.invoker;
};
/**
* Handles the authorization for an RPC.
* @private
* @param {Number} handle The handle for the authorizer.
* @param {module:vanadium.context.Context} ctx The ctx of the
* call.
* @param {module:vanadium.security~SecurityCall} call The security call.
* @return {Promise} A promise that will be fulfilled with the result.
*/
Server.prototype._handleAuthorization = function(handle, ctx, call) {
var handler = this.serviceObjectHandles[handle];
var authorizer = defaultAuthorizer;
if (handler && handler.authorizer) {
authorizer = handler.authorizer;
}
var def = new Deferred();
var inspectableAuthorizer = new InspectableFunction(authorizer);
asyncCall(ctx, null, inspectableAuthorizer, [], [ctx, call],
function(err) {
if (err) {
def.reject(err);
return;
}
def.resolve();
});
return def.promise;
};
var InvokeOnNonInvoker = makeError(
'v.io/core/javascript.InvokeOnNonInvoker', actions.NO_RETRY,
'{1:}{2:} trying to invoke on a non-invoker{:_}');
/**
* Handles the result of lookup and returns an error if there was any.
* @private
*/
Server.prototype._handleLookupResult = function(object) {
if (!object.hasOwnProperty('service')) {
// TODO(bjornick): Use the correct context here.
throw new InvokeOnNonInvoker(this._rootCtx);
}
object._handle = this._handle;
try {
object.invoker = new Invoker(object.service);
} catch (e) {
vlog.logger.error('lookup failed', e);
return e;
}
this.serviceObjectHandles[object._handle] = object;
this._handle++;
return null;
};
/*
* Perform the lookup call to the user code on the suffix and method passed in.
* @private
*/
Server.prototype._handleLookup = function(suffix) {
var self = this;
var def = new Deferred();
var argsNames = inspector(this.dispatcher).names;
var useCallback = argsNames.length >= 2;
var cb = function(err, val) {
if (err) {
def.reject(err);
} else {
def.resolve(val);
}
};
var result;
try {
result = this.dispatcher(suffix, cb);
} catch (e) {
def.reject(e);
vlog.logger.error(e);
return def.promise;
}
if (!useCallback) {
if (result === undefined) {
return def.promise.then(handleResult);
}
if (result instanceof Error) {
def.reject(result);
return def.promise;
}
def.resolve(result);
}
function handleResult(v) {
var err = self._handleLookupResult(v);
if (err) {
return Promise.reject(err);
}
return Promise.resolve(v);
}
return def.promise.then(handleResult);
};
/**
* Export the module
*/
module.exports = Server;