service-model
Version:
An object oriented web service framework inspired by Windows Communication Foundation.
201 lines (200 loc) • 8.79 kB
JavaScript
var domain_1 = require("domain");
var message_1 = require("../message");
var httpStatusCode_1 = require("../httpStatusCode");
var faultError_1 = require("../faultError");
var callbackUtil_1 = require("../common/callbackUtil");
var operationContext_1 = require("../operationContext");
/**
* @hidden
*/
var RequestHandler = (function () {
function RequestHandler(endpoint, request) {
this.message = request.message;
this._endpoint = endpoint;
this._request = request;
}
/**
* Process the request.
* @param callback Called when processing completes.
*/
RequestHandler.prototype.process = function (callback) {
var _this = this;
if (this._callback) {
throw new Error("Process called multiple times.");
}
if (!callback) {
throw new Error("Missing required argument 'callback'.");
}
this._callback = callback;
if (!this._endpoint.service.createOperationContext) {
this._handleRequest();
}
else {
var d = domain_1.create();
d.on('error', function (err) { return _this._handleUncaughtException(err); });
d.run(function () {
var context = new operationContext_1.OperationContext();
context.requestContext = _this._request;
operationContext_1.OperationContext.current = context;
_this._handleRequest();
});
}
};
RequestHandler.prototype.addListener = function (event, listener) {
this._request.addListener(event, listener);
return this;
};
RequestHandler.prototype.removeListener = function (event, listener) {
this._request.removeListener(event, listener);
return this;
};
RequestHandler.prototype._handleRequest = function () {
var _this = this;
// Process outside current execution path. This ensures that we have full control over errors. For example,
// Express includes a try/catch in it's request handler which would catch any sync errors and bypass our
// error handling.
process.nextTick(function () {
_this._afterReceiveRequest();
var operation = _this._endpoint.chooseOperation(_this.message);
if (!operation) {
_this._handleError(new faultError_1.FaultError(undefined, "Unable to choose operation.", "UnknownOperation", httpStatusCode_1.HttpStatusCode.NotFound));
return;
}
operation.formatter.deserializeRequest(_this.message, function (err, args) {
if (err)
return _this._handleError(err);
_this._invoke(operation, args);
});
});
};
/**
* Reply to the request.
* @param message The message to send as a reply.
*/
RequestHandler.prototype.reply = function (message) {
if (this._finished)
return;
this._finished = true;
this._beforeSendReply(message);
this._request.reply(message);
this._callback();
};
/**
* Abort the request without normal error handling.
*/
RequestHandler.prototype.abort = function () {
if (this._finished)
return;
this._finished = true;
this._request.abort();
this._callback();
};
RequestHandler.prototype._afterReceiveRequest = function () {
var messageInspectors = this._endpoint.messageInspectors;
if (!messageInspectors || messageInspectors.length == 0)
return;
this._correlationStates = new Array(messageInspectors.length);
for (var i = 0; i < messageInspectors.length; i++) {
this._correlationStates[i] = messageInspectors[i].afterReceiveRequest(this.message);
}
};
RequestHandler.prototype._beforeSendReply = function (reply) {
if (!this._correlationStates)
return;
var messageInspectors = this._endpoint.messageInspectors;
for (var i = 0; i < messageInspectors.length; i++) {
messageInspectors[i].beforeSendReply(reply, this._correlationStates[i]);
}
};
RequestHandler.prototype._invoke = function (operation, args) {
var _this = this;
var instance = this._endpoint.service.instanceProvider.getInstance(this.message, this._request);
operation.invoker.invoke(instance, args, function (err, result) {
if (err)
return _this._handleError(err);
// No need to serialize the reply if this is a one way message.
if (operation.isOneWay)
return;
operation.formatter.serializeReply(result, function (err, message) {
if (err)
return _this._handleError(err);
// if we are using an operation context then merge in an outgoing headers
if (_this._endpoint.service.createOperationContext) {
message.headers.merge(operationContext_1.OperationContext.current.outgoingHeaders);
}
_this.reply(message);
});
});
// If this is a one-way operation then reply immediately. We don't wait for the callback from invoke. Note
// that this means that errors as a result of the invoke will not be reported to the client.
if (operation.isOneWay) {
this.reply(message_1.Message.createReply(httpStatusCode_1.HttpStatusCode.Ok));
}
};
RequestHandler.prototype._handleUncaughtException = function (err) {
// Emit the error from the dispatcher.
this._endpoint.service.dispatcher.emit('error', err);
// The default behavior will crash the process immediately once the error is emitted. However, if the host
// handles the event and gives us a chane to shutdown, process the error.
if (this._errored) {
// The uncaught exception occurred while in the error handler. Nothing else we can do but abort.
this._endpoint.service.dispatcher.logger.trace(err, "Uncaught exception in error handler. Aborting request.");
this.abort();
}
else {
// Give error handlers a chance to process the error for logging, etc.
this._handleError(err);
}
};
RequestHandler.prototype._handleError = function (err) {
var _this = this;
// Make sure the error handlers are just executed once. For example, _handleError could be called multiple
// times if an exception occurs which is caught by the domain and passed to _handleError and then the timeout
// for the operation invoker fires.
if (this._errored || this._finished)
return;
this._errored = true;
var step = -1;
var next = function (e) {
var handler = _this._endpoint.errorHandlers[++step];
if (handler) {
handler.handleError(e, _this, callbackUtil_1.onlyOnce(next));
}
else {
// We've reached the end of the error handler chain. Send a reply.
_this._sendFault(e);
}
};
next(err);
};
RequestHandler.prototype._sendFault = function (err) {
var _this = this;
var fault;
if (err instanceof faultError_1.FaultError) {
fault = err;
}
else {
// If the error is not a FaultError, then create one. By default we return a generic message that does not
// include any details about the error.
if (!this._endpoint.includeErrorDetailInFault) {
fault = new faultError_1.FaultError(null, "The server was unable to process the request due to an internal error.", "InternalError");
}
else {
// If we get an object for err that not a real Error, then at least call toString() to try to get a
// message to display.
fault = new faultError_1.FaultError(err.stack, err.message || err.toString(), "InternalError");
}
}
this._endpoint.faultFormatter.serializeFault(fault, function (err, message) {
// If we get an error while trying to serialize the fault then we can't reply with the error so abort request and log the error.
if (err) {
_this._endpoint.service.dispatcher.logger.error({ err: err, endpoint: _this._endpoint }, "Error serializing fault. Aborting request.");
_this.abort();
return;
}
_this.reply(message);
});
};
return RequestHandler;
})();
exports.RequestHandler = RequestHandler;