service-model
Version:
An object oriented web service framework inspired by Windows Communication Foundation.
176 lines (175 loc) • 6.2 kB
JavaScript
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var events_1 = require("events");
var message_1 = require("../message");
var requestHandler_1 = require("./requestHandler");
var httpStatusCode_1 = require("../httpStatusCode");
var nullLogger_1 = require("../nullLogger");
/**
* Responsible for dispatching service requests.
*
* <uml>
* hide members
* hide circle
* DispatcherFactory .> RequestDispatcher
* EventEmitter <|.. RequestDispatcher
* RequestDispatcher *-- DispatchService : services
* RequestDispatcher *- Logger : logger
* </uml>
*
* ### Events
* * `closing` - Triggered when [[close]] is called on the RequestDispatcher. This event indicates that the dispatcher is
* no longer accepting new requests.
* * `closed` - Triggered after the RequestedDispatched closes. This event indicates that all pending requests have
* completed or timed out.
* * `error` - Trigger if an unhandled exception is raised in an operation. This event is only relevant if an
* [[OperationContext]] is created.
*
*/
var RequestDispatcher = (function (_super) {
__extends(RequestDispatcher, _super);
function RequestDispatcher() {
_super.apply(this, arguments);
/**
* Timeout in milliseconds for close operation to complete. Any requests that have not completed before
* the timeout are aborted.
*/
this.closeTimeout = 30000;
/**
* List of services in the dispatcher.
*/
this.services = [];
/**
* Logger to use that conforms to [bunyan](https://www.npmjs.com/package/bunyan)'s logger interface.
*/
this.logger = nullLogger_1.NullLogger.instance;
/**
* @hidden
*/
this._requestCount = 0;
}
/**
* Dispatches a request.
* @param request The request to dispatch.
*/
RequestDispatcher.prototype.dispatch = function (request) {
var _this = this;
if (this._closing) {
request.reply(message_1.Message.createReply(httpStatusCode_1.HttpStatusCode.ServiceUnavailable, "Service is currently unavailable."));
return;
}
var endpoint = this._chooseEndpoint(request.message);
if (!endpoint) {
request.reply(message_1.Message.createReply(httpStatusCode_1.HttpStatusCode.NotFound, "Endpoint not found."));
}
else {
var handler = new requestHandler_1.RequestHandler(endpoint, request);
this._addRequest(handler);
handler.process(function () { return _this._removeRequest(handler); });
}
};
/**
* Validates that the dispatcher is correctly configured.
*/
RequestDispatcher.prototype.validate = function () {
this.services.forEach(function (service) { return service.validate(); });
};
/**
* Closes the dispatcher. If any requests do not complete within 'closeTimeout', they are aborted.
* @param callback Optional. Called after dispatcher is closed.
*/
RequestDispatcher.prototype.close = function (callback) {
var _this = this;
if (callback) {
if (this._closed) {
callback();
return;
}
this.on('closed', callback);
}
if (this._closing)
return;
this._closing = true;
this.emit('closing');
// if there are not any pending requests when close is called then close immediately.
if (this._requestCount == 0 && this._closing) {
this._closed = true;
this.emit('closed');
return;
}
this._closeTimer = setTimeout(function () {
_this.logger.warn("Timeout of %dms exceeded while closing dispatcher.", _this.closeTimeout);
var handler = _this._head;
while (handler) {
handler.abort();
handler = handler.next;
}
}, this.closeTimeout);
this._closeTimer.unref();
};
/**
* Chooses the appropriate endpoint for the request.
* @param message The request message.
* @hidden
*/
RequestDispatcher.prototype._chooseEndpoint = function (message) {
var max = -Infinity, match;
for (var i = 0; i < this.services.length; i++) {
var service = this.services[i];
for (var j = 0; j < service.endpoints.length; j++) {
var endpoint = service.endpoints[j];
if (endpoint.filter.match(message)) {
if (endpoint.filterPriority > max) {
max = endpoint.filterPriority;
match = endpoint;
}
}
}
}
return match;
};
/**
* Adds a request to the list of pending requests.
* @param handler The request handler.
* @hidden
*/
RequestDispatcher.prototype._addRequest = function (handler) {
if (this._head) {
handler.prev = this._tail;
this._tail = this._tail.next = handler;
}
else {
this._head = this._tail = handler;
}
this._requestCount++;
};
/**
* Removes a request from the list of pending requests.
* @param handler The request handler.
* @hidden
*/
RequestDispatcher.prototype._removeRequest = function (handler) {
if (handler.prev) {
handler.prev.next = handler.next;
}
else {
this._head = handler.next;
}
if (handler.next) {
handler.next.prev = handler.prev;
}
else {
this._tail = handler.prev;
}
this._requestCount--;
if (this._requestCount == 0 && this._closing) {
clearTimeout(this._closeTimer);
this.emit('closed');
}
};
return RequestDispatcher;
})(events_1.EventEmitter);
exports.RequestDispatcher = RequestDispatcher;