diff --git a/CHANGES.md b/CHANGES.md
index 97405529..e1806b9c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,35 @@
+2016-02-26, Version 2.26.0
+==========================
+
+ * Add support for "file" return args (Miroslav Bajtoš)
+
+ * Fix handling of args with http.target:header (Miroslav Bajtoš)
+
+
+2016-02-09, Version 2.25.1
+==========================
+
+ * Back-port pull request #283 from strongloop/fix/afterError (Miroslav Bajtoš)
+
+ * Do not call "afterError" on success (Miroslav Bajtoš)
+
+
+2016-02-02, Version 2.25.0
+==========================
+
+ * Backport pull request #279 from strongloop/feature/phases (Miroslav Bajtoš)
+
+ * Add remotes.registerPhaseHandler (Miroslav Bajtoš)
+
+ * Add remote invocation phases (Miroslav Bajtoš)
+
+ * Simplify invokeMethodInContext (Miroslav Bajtoš)
+
+ * Extract ContextBase and ctx.getScope() (Miroslav Bajtoš)
+
+ * Customize XML root element in remoteMethod (David Cheung)
+
+
2016-01-11, Version 2.24.0
==========================
@@ -531,8 +563,6 @@
2014-06-13, Version 2.0.0-beta4
===============================
- * 2.0.0-beta4 (Miroslav Bajtoš)
-
* Add more classes to JSDocs; some doc cleanup. (crandmck)
* !fixup use strongloop/node.js literal convention (Ritchie Martori)
@@ -593,8 +623,6 @@
2014-06-03, Version 2.0.0-beta3
===============================
- * 2.0.0-beta3 (Miroslav Bajtoš)
-
* Exclude body-parser from the browser bundle (Miroslav Bajtoš)
* Remove duplicate dep (Raymond Feng)
diff --git a/Gruntfile.js b/Gruntfile.js
index 7d60b876..15bcbbed 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/*global module:false*/
module.exports = function(grunt) {
diff --git a/LICENSE.md b/LICENSE.md
index 35f5f930..165f8601 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,9 +1,210 @@
-Copyright (c) 2013-2015 StrongLoop, Inc.
+Copyright (c) IBM Corp. 2013,2016. All Rights Reserved.
+Node module: strong-remoting
+This project is licensed under the Artistic License 2.0, full text below.
-strong-remoting uses a dual license model.
+--------
-You may use this library under the terms of the [Artistic 2.0 license][],
-or under the terms of the [StrongLoop Subscription Agreement][].
+The Artistic License 2.0
-[Artistic 2.0 license]: http://opensource.org/licenses/Artistic-2.0
-[StrongLoop Subscription Agreement]: http://strongloop.com/license
+Copyright (c) 2000-2006, The Perl Foundation.
+
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10) Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11) If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14) Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+--------
diff --git a/example/before-after.js b/example/before-after.js
index e119f620..c7ac3ddc 100644
--- a/example/before-after.js
+++ b/example/before-after.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
// create a set of shared classes
var remotes = require('../').create();
diff --git a/example/documentation/index.js b/example/documentation/index.js
index 4baef5a5..b8eea3ac 100644
--- a/example/documentation/index.js
+++ b/example/documentation/index.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var http = require('http');
var remotes = require('./remotes');
var meta = require('../../ext/meta');
diff --git a/example/documentation/remotes/contract-class.js b/example/documentation/remotes/contract-class.js
index 3283d868..e2280b56 100644
--- a/example/documentation/remotes/contract-class.js
+++ b/example/documentation/remotes/contract-class.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
// This example shows using the helper for a type in a "definitive" fashion.
var helper = require('../../../').extend(module.exports);
var clshelper;
diff --git a/example/documentation/remotes/contract.js b/example/documentation/remotes/contract.js
index f0c91bd2..52f016e1 100644
--- a/example/documentation/remotes/contract.js
+++ b/example/documentation/remotes/contract.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var helper = require('../../../').extend(module.exports);
/**
diff --git a/example/documentation/remotes/index.js b/example/documentation/remotes/index.js
index fd8324a2..21c17f59 100644
--- a/example/documentation/remotes/index.js
+++ b/example/documentation/remotes/index.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var remotes = require('../../../').create();
/**
diff --git a/example/documentation/remotes/simple-class.js b/example/documentation/remotes/simple-class.js
index d4b8e735..950c9ef9 100644
--- a/example/documentation/remotes/simple-class.js
+++ b/example/documentation/remotes/simple-class.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
// This example shows using the helper for a type in a "post-definition" style.
var helper = require('../../../').extend(module.exports);
diff --git a/example/documentation/remotes/simple.js b/example/documentation/remotes/simple.js
index 7057073b..4902bcc9 100644
--- a/example/documentation/remotes/simple.js
+++ b/example/documentation/remotes/simple.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
// This helper adds methods to a module that we assume will be added to the remotes.
// TODO(schoon) - Make this _the_ API, not a "helper".
// TODO(schoon) - Document EVERYTHING
diff --git a/example/remote-fs.js b/example/remote-fs.js
index 4eebd787..e234b847 100644
--- a/example/remote-fs.js
+++ b/example/remote-fs.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
// create a set of shared classes
var remotes = require('../').create();
diff --git a/example/root.js b/example/root.js
index e12d1fd6..e2ecdef3 100644
--- a/example/root.js
+++ b/example/root.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
// create a set of shared classes
var remotes = require('../').create();
diff --git a/example/shared-class.js b/example/shared-class.js
index c67f5e12..104e4acd 100644
--- a/example/shared-class.js
+++ b/example/shared-class.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var express = require('express');
// define a vanilla JavaScript class
diff --git a/example/simple-types.js b/example/simple-types.js
index f6110483..3278c902 100644
--- a/example/simple-types.js
+++ b/example/simple-types.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
// create a set of shared classes
var remotes = require('../').create();
diff --git a/example/simple.js b/example/simple.js
index fc28ae4f..74227a96 100644
--- a/example/simple.js
+++ b/example/simple.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
// create a set of shared classes
var remoting = require('../');
var SharedClass = remoting.SharedClass
diff --git a/example/socket-io/client.js b/example/socket-io/client.js
index 6501cbd8..99e829f5 100644
--- a/example/socket-io/client.js
+++ b/example/socket-io/client.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var Remotes = require('../../client/js/client');
var SocketIOAdapter = require('../../client/js/socket-io-adapter');
var remotes = Remotes.connect('http://localhost:3000', SocketIOAdapter);
diff --git a/example/socket-io/server.js b/example/socket-io/server.js
index 168b12d6..bbe9460e 100644
--- a/example/socket-io/server.js
+++ b/example/socket-io/server.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
// create a set of shared classes
var remotes = require('../../').create();
diff --git a/example/streams.js b/example/streams.js
index afe5c09f..5551a740 100644
--- a/example/streams.js
+++ b/example/streams.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
// faux remote stream
var destination = process.stdout;
var fs = require('fs');
diff --git a/ext/meta.js b/ext/meta.js
index 9227b553..266143a3 100644
--- a/ext/meta.js
+++ b/ext/meta.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/**
* Expose the `Meta` plugin.
*/
diff --git a/index.js b/index.js
index 4f896e10..a727b1f9 100644
--- a/index.js
+++ b/index.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/**
* remotes ~ public api
*/
diff --git a/lib/context-base.js b/lib/context-base.js
new file mode 100644
index 00000000..b41d2e7a
--- /dev/null
+++ b/lib/context-base.js
@@ -0,0 +1,31 @@
+// Copyright IBM Corp. 2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
+'use strict';
+
+var EventEmitter = require('events').EventEmitter;
+var inherits = require('util').inherits;
+
+module.exports = ContextBase;
+
+/**
+ * A base class for all Context instances
+ */
+function ContextBase(method) {
+ EventEmitter.call(this);
+
+ this.method = method;
+}
+
+inherits(ContextBase, EventEmitter);
+
+ContextBase.prototype.getScope = function() {
+ // Static methods are invoked on the constructor (this = constructor fn)
+ // Prototype methods are invoked on the instance (this = instance)
+ var method = this.method;
+ return this.instance ||
+ method.ctor ||
+ method.sharedMethod && method.sharedMethod.ctor;
+};
diff --git a/lib/dynamic.js b/lib/dynamic.js
index 2438da28..fa86bc12 100644
--- a/lib/dynamic.js
+++ b/lib/dynamic.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/**
* Expose `Dynamic`.
*/
diff --git a/lib/exports-helper.js b/lib/exports-helper.js
index e47073d7..2983242a 100644
--- a/lib/exports-helper.js
+++ b/lib/exports-helper.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/*!
* Expose `ExportsHelper`.
*/
diff --git a/lib/http-context.js b/lib/http-context.js
index 27ce93ee..9fb80577 100644
--- a/lib/http-context.js
+++ b/lib/http-context.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/*!
* Expose `HttpContext`.
*/
@@ -8,13 +13,15 @@ module.exports = HttpContext;
* Module dependencies.
*/
-var EventEmitter = require('events').EventEmitter;
var debug = require('debug')('strong-remoting:http-context');
var util = require('util');
var inherits = util.inherits;
var assert = require('assert');
+var ContextBase = require('./context-base');
var Dynamic = require('./dynamic');
var js2xmlparser = require('js2xmlparser');
+var SharedMethod = require('./shared-method');
+
var DEFAULT_SUPPORTED_TYPES = [
'application/json', 'application/javascript', 'application/xml',
'text/javascript', 'text/xml',
@@ -43,6 +50,8 @@ var SSEClient = require('sse').Client;
*/
function HttpContext(req, res, method, options) {
+ ContextBase.call(this, method);
+
this.req = req;
this.res = res;
this.method = method;
@@ -71,11 +80,7 @@ function HttpContext(req, res, method, options) {
}
}
-/*!
- * Inherit from `EventEmitter`.
- */
-
-inherits(HttpContext, EventEmitter);
+inherits(HttpContext, ContextBase);
HttpContext.prototype.createStream = function() {
var streamsDesc = this.method.streams;
@@ -395,28 +400,33 @@ HttpContext.prototype.invoke = function(scope, method, fn, isCtor) {
};
HttpContext.prototype.setReturnArgByName = function(name, value) {
+ var ARG_WAS_HANDLED = true;
var returnDesc = this.method.getReturnArgDescByName(name);
var result = this.result;
var res = this.res;
if (!returnDesc) {
- return debug('warning: cannot set return value for arg' +
+ debug('warning: cannot set return value for arg' +
' (%s) without description!', name);
+ return;
}
if (returnDesc.root) {
- this.result = value;
- } else if (returnDesc.http) {
+ // TODO(bajtos) call SharedMethod's convertToBasicRemotingType here?
+ this.resultType = typeof returnDesc.type === 'string' ?
+ returnDesc.type.toLowerCase() : returnDesc.type;
+ return;
+ }
+
+ if (returnDesc.http) {
switch (returnDesc.http.target) {
case 'status':
res.status(value);
- break;
+ return ARG_WAS_HANDLED;
case 'header':
res.set(returnDesc.http.header || name, value);
- break;
+ return ARG_WAS_HANDLED;
}
- } else {
- result[name] = value;
}
};
@@ -433,19 +443,26 @@ function toJSON(input) {
}
}
-function toXML(input) {
+function toXML(input, options) {
var xml;
+ var xmlDefaultOptions = { declaration : true };
+ var xmlOptions = util._extend(xmlDefaultOptions, options);
if (input && typeof input.toXML === 'function') {
xml = input.toXML();
} else {
- if (input) {
+ if (typeof input == 'string') {
+ // this is a hack to handle js2xmlparser throws syntax error on string
+ // wraps string from "a string" to "\"a string\""
+ input = JSON.stringify(input);
+ } else if (typeof input == 'object') {
// Trigger toJSON() conversions
input = toJSON(input);
}
if (Array.isArray(input)) {
input = { result: input };
}
- xml = js2xmlparser('response', input, {
+ xml = js2xmlparser(xmlOptions.wrapperElement || 'response', input, {
+ declaration : { include : xmlOptions.declaration },
prettyPrinting: {
indentString: ' '
},
@@ -465,7 +482,11 @@ HttpContext.prototype.shouldReturnEventStream = function() {
var format = query._format;
var acceptable = req.accepts('text/event-stream');
- return (format === 'event-stream') || acceptable;
+ var returnEventStream = (format === 'event-stream') || acceptable;
+ if (returnEventStream) {
+ this.res.setHeader('Content-Encoding', 'x-no-compression');
+ }
+ return returnEventStream;
};
HttpContext.prototype.respondWithEventStream = function(stream) {
@@ -501,13 +522,14 @@ function sendBodyJsonp(res, data) {
res.jsonp(data);
}
-function sendBodyXml(res, data) {
+function sendBodyXml(res, data, method) {
if (data === null) {
res.header('Content-Length', '7');
res.send('');
} else if (data) {
try {
- var xml = toXML(data);
+ var xmlOptions = method.returns[0].xml || {};
+ var xml = toXML(data, xmlOptions);
res.send(xml);
} catch (e) {
res.status(500).send(e + '\n' + data);
@@ -629,13 +651,24 @@ HttpContext.prototype.done = function(cb) {
res.header('Content-Type', operationResults.contentType);
}
if (dataExists) {
- operationResults.sendBody(res, data);
+ if (this.resultType !== 'file') {
+ operationResults.sendBody(res, data, method);
+ res.end();
+ } else if (Buffer.isBuffer(data) || typeof(data) === 'string') {
+ res.end(data);
+ } else if (data.pipe) {
+ data.pipe(res);
+ } else {
+ var valueType = SharedMethod.getType(data);
+ var msg = 'Cannot create a file response from "' + valueType + '"';
+ return cb(new Error(msg));
+ }
} else {
if (res.statusCode === undefined || res.statusCode === 200) {
res.statusCode = 204;
}
+ res.end();
}
- res.end();
cb();
};
diff --git a/lib/http-invocation.js b/lib/http-invocation.js
index d4333994..f45c6d32 100644
--- a/lib/http-invocation.js
+++ b/lib/http-invocation.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/*!
* Expose `HttpInvocation`.
*/
@@ -159,7 +164,8 @@ HttpInvocation.prototype._processArg = function(req, verb, query, accept) {
HttpInvocation.prototype.createRequest = function() {
var method = this.method;
- var verb = method.getHttpMethod();
+ var endpoint = method.getEndpoints()[0];
+ var verb = endpoint.verb;
var req = this.req = {method: verb || 'GET'};
var accepts = method.accepts;
var ctorAccepts = null;
@@ -168,7 +174,7 @@ HttpInvocation.prototype.createRequest = function() {
var auth = this.auth;
// initial url is the format
- req.url = this.base + method.getFullPath();
+ req.url = this.base + endpoint.fullPath;
var parsedUrl = urlUtil.parse(req.url);
diff --git a/lib/jsonrpc-adapter.js b/lib/jsonrpc-adapter.js
index 986b7446..2b3bb512 100644
--- a/lib/jsonrpc-adapter.js
+++ b/lib/jsonrpc-adapter.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/*!
* Expose `JsonRpcAdapter`.
*/
diff --git a/lib/remote-objects.js b/lib/remote-objects.js
index cda747ce..7c4d2f78 100644
--- a/lib/remote-objects.js
+++ b/lib/remote-objects.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/*!
* Expose `RemoteObjects`.
*/
@@ -10,13 +15,16 @@ module.exports = RemoteObjects;
var EventEmitter = require('eventemitter2').EventEmitter2;
var debug = require('debug')('strong-remoting:remotes');
+var deprecated = require('depd')('strong-remoting');
var util = require('util');
var urlUtil = require('url');
var inherits = util.inherits;
var assert = require('assert');
var Dynamic = require('./dynamic');
var SharedClass = require('./shared-class');
+var SharedMethod = require('./shared-method');
var ExportsHelper = require('./exports-helper');
+var PhaseList = require('loopback-phase').PhaseList;
// require the rest adapter for browserification
// TODO(ritch) remove this somehow...?
@@ -53,6 +61,8 @@ function RemoteObjects(options) {
this.options = options || {};
this.exports = this.options.exports || {};
this._classes = {};
+
+ this._setupPhases();
}
/*!
@@ -381,6 +391,29 @@ RemoteObjects.prototype.afterError = function(methodMatch, fn) {
this.on('afterError.' + methodMatch, fn);
};
+RemoteObjects.prototype.registerPhaseHandler = function(phaseName,
+ methodNameWildcard,
+ handler) {
+ var pattern = methodNameWildcard.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
+ // single star matches one segment only
+ .replace(/(^|\.)\*($|\.)/g, '$1[^.]*$2')
+ // double-start match one or more segments
+ .replace(/(^|\.)\*\*($|\.)/g, '$1.*$2');
+ var matcher = new RegExp('^' + pattern + '$');
+
+ debug('registerPhaseHandler(%j) -> pattern %j',
+ methodNameWildcard,
+ pattern);
+
+ this.phases.registerHandler(phaseName, function matchHandler(ctx, next) {
+ if (matcher.test(ctx.method.stringName)) {
+ handler(ctx, next);
+ } else {
+ next();
+ }
+ });
+};
+
/*!
* Create a middleware style emit that supports wildcards.
*/
@@ -599,42 +632,64 @@ RemoteObjects.prototype._executeAuthorizationHook = function(ctx, cb) {
}
};
+RemoteObjects.prototype._setupPhases = function() {
+ var self = this;
+ self.phases = new PhaseList();
+ var auth = self.phases.add('auth');
+ var invoke = self.phases.add('invoke');
+
+ auth.use(function phaseAuthorization(ctx, next) {
+ self._executeAuthorizationHook(ctx, next);
+ });
+
+ invoke.before(function phaseBeforeInvoke(ctx, next) {
+ self.execHooks('before', ctx.method, ctx.getScope(), ctx, next);
+ });
+
+ invoke.use(function phaseInvoke(ctx, next) {
+ ctx.invoke(ctx.getScope(), ctx.method, function(err, result) {
+ if (!err) ctx.result = result;
+ next(err);
+ });
+ });
+
+ invoke.after(function phaseAfterInvoke(ctx, next) {
+ self.execHooks('after', ctx.method, ctx.getScope(), ctx, next);
+ });
+};
+
/**
* Invoke the given shared method using the supplied context.
* Execute registered before/after hooks.
+ *
* @param {Object} ctx
* @param {Object} method
* @param {function(Error=)} cb
*/
RemoteObjects.prototype.invokeMethodInContext = function(ctx, method, cb) {
var self = this;
+ var scope = ctx.getScope();
- var scope = this.getScope(ctx, method);
-
- self._executeAuthorizationHook(ctx, function(err) {
- if (err) return triggerErrorAndCallBack(err);
-
- self.execHooks('before', method, scope, ctx, function(err) {
- if (err) return triggerErrorAndCallBack(err);
+ if (cb === undefined && typeof method === 'function') {
+ // the new API with two arguments
+ cb = method;
+ method = ctx.method;
+ } else {
+ // backwards compatibility: invokeMethodInContext(ctx, method, cb)
+ // TODO remove in v3.0
+ assert.equal(method, ctx.method);
+ deprecated('invokeMethodInContext(ctx, method, cb) is deprecated.' +
+ 'Pass the method as ctx.method instead.');
+ }
- ctx.invoke(scope, method, function(err, result) {
- if (err) return triggerErrorAndCallBack(err);
+ self.phases.run(ctx, function interceptInvocationErrors(err) {
+ if (!err) return cb();
- ctx.result = result;
- self.execHooks('after', method, scope, ctx, function(err) {
- if (err) return triggerErrorAndCallBack(err);
- cb();
- });
- });
- });
- });
-
- function triggerErrorAndCallBack(err) {
ctx.error = err;
self.execHooks('afterError', method, scope, ctx, function(hookErr) {
cb(hookErr || err);
});
- }
+ });
};
/**
@@ -644,11 +699,10 @@ RemoteObjects.prototype.invokeMethodInContext = function(ctx, method, cb) {
*/
RemoteObjects.prototype.getScope = function(ctx, method) {
- // Static methods are invoked on the constructor (this = constructor fn)
- // Prototype methods are invoked on the instance (this = instance)
- return ctx.instance ||
- method.ctor ||
- method.sharedMethod && method.sharedMethod.ctor;
+ deprecated('remoteObjects.getScope(ctx, method) is deprecated, ' +
+ 'use ctx.getScope() instead');
+ assert.equal(ctx.method, method);
+ return ctx.getScope();
};
/**
diff --git a/lib/rest-adapter.js b/lib/rest-adapter.js
index 88367017..21c623e3 100644
--- a/lib/rest-adapter.js
+++ b/lib/rest-adapter.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/*!
* Expose `RestAdapter`.
*/
@@ -11,6 +16,7 @@ RestAdapter.RestMethod = RestMethod;
* Module dependencies.
*/
+var deprecated = require('depd')('strong-remoting');
var EventEmitter = require('events').EventEmitter;
var debug = require('debug')('strong-remoting:rest-adapter');
var util = require('util');
@@ -21,6 +27,7 @@ var bodyParser = require('body-parser');
var cors = require('cors');
var async = require('async');
var HttpInvocation = require('./http-invocation');
+var ContextBase = require('./context-base');
var HttpContext = require('./http-context');
var json = bodyParser.json;
@@ -116,8 +123,9 @@ RestAdapter.prototype.invoke = function(method, ctorArgs, args, callback) {
var invocation = new HttpInvocation(
restMethod, ctorArgs, args, this.connection, remotes.auth
);
- var ctx = { req: invocation.createRequest() };
- var scope = remotes.getScope(ctx, restMethod);
+ var ctx = new ContextBase(restMethod);
+ ctx.req = invocation.createRequest();
+ var scope = ctx.getScope();
remotes.execHooks('before', restMethod, scope, ctx, function(err) {
if (err) { return callback(err); }
invocation.invoke(function(err) {
@@ -368,22 +376,40 @@ RestAdapter.errorHandler = function(options) {
res.statusCode = err.statusCode || err.status || 500;
}
- debug('Error in %s %s: %s', req.method, req.url, err.stack);
- var data = {
- name: err.name,
- status: res.statusCode,
- message: err.message || 'An unknown error occurred'
- };
+ if (Array.isArray(err)) {
+ var details = err.map(function(it) {
+ var data = generateResponseError(it);
+ delete data.statusCode;
+ return data;
+ });
- for (var prop in err) {
- data[prop] = err[prop];
+ var msg = 'Failed with multiple errors, see `details` for more information.';
+ err = new Error(msg);
+ err.details = details;
}
- data.stack = err.stack;
- if (process.env.NODE_ENV === 'production' || options.disableStackTrace) {
- delete data.stack;
+ res.send({ error: generateResponseError(err) });
+
+ function generateResponseError(error) {
+ debug('Error in %s %s: %s', req.method, req.url, error.stack);
+
+ var data = {
+ name: error.name,
+ status: res.statusCode,
+ message: error.message || 'An unknown error occurred',
+ };
+
+ for (var prop in error) {
+ data[prop] = error[prop];
+ }
+
+ data.stack = error.stack;
+ if (process.env.NODE_ENV === 'production' || options.disableStackTrace) {
+ delete data.stack;
+ }
+
+ return data;
}
- res.send({ error: data });
}
};
};
@@ -437,18 +463,18 @@ RestAdapter.prototype._invokeMethod = function(ctx, method, next) {
if (method.rest.before) {
steps.push(function invokeRestBefore(cb) {
debug('Invoking rest.before for ' + ctx.methodString);
- method.rest.before.call(remotes.getScope(ctx, method), ctx, cb);
+ method.rest.before.call(ctx.getScope(), ctx, cb);
});
}
steps.push(
- this.remotes.invokeMethodInContext.bind(this.remotes, ctx, method)
+ this.remotes.invokeMethodInContext.bind(this.remotes, ctx)
);
if (method.rest.after) {
steps.push(function invokeRestAfter(cb) {
debug('Invoking rest.after for ' + ctx.methodString);
- method.rest.after.call(remotes.getScope(ctx, method), ctx, cb);
+ method.rest.after.call(ctx.getScope(), ctx, cb);
});
}
@@ -595,11 +621,30 @@ RestMethod.prototype.acceptsSingleBodyArgument = function() {
getTypeString(accepts.type) == 'object' || false;
};
+RestMethod.prototype.getEndpoints = function() {
+ var self = this;
+ return this.routes.map(function(route) {
+ var verbResult;
+ var verb = route.verb;
+ if (verb == 'all') {
+ verbResult = 'POST';
+ } else if (verb == 'del') {
+ verbResult = 'DELETE';
+ } else {
+ verbResult = verb.toUpperCase();
+ }
+ return {
+ verb: verbResult,
+ fullPath: joinPaths(self.restClass.getPath(), route.path),
+ };
+ });
+};
+
RestMethod.prototype.getHttpMethod = function() {
- var verb = this.routes[0].verb;
- if (verb == 'all') return 'POST';
- if (verb == 'del') return 'DELETE';
- return verb.toUpperCase();
+ // deprecate message to let the users know what they were using
+ // was retuning just the first route's verb
+ deprecated('getHttpMethod() is deprecated, use getEndpoints()[0].verb instead.');
+ return this.getEndpoints()[0].verb;
};
RestMethod.prototype.getPath = function() {
@@ -607,7 +652,10 @@ RestMethod.prototype.getPath = function() {
};
RestMethod.prototype.getFullPath = function() {
- return joinPaths(this.restClass.getPath(), this.getPath());
+ // deprecate message to let the users know what they were using
+ // was retuning just the first route's path
+ deprecated('getFullPath() is deprecated, use getEndpoints()[0].fullPath instead.');
+ return this.getEndpoints()[0].fullPath;
};
function getTypeString(ctorOrName) {
diff --git a/lib/shared-class.js b/lib/shared-class.js
index c03d1801..7f945a8d 100644
--- a/lib/shared-class.js
+++ b/lib/shared-class.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/*!
* Expose `SharedClass`.
*/
diff --git a/lib/shared-method.js b/lib/shared-method.js
index c473c0fa..95c8fcb1 100644
--- a/lib/shared-method.js
+++ b/lib/shared-method.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/*!
* Expose `SharedMethod`.
*/
@@ -71,6 +76,10 @@ var assert = require('assert');
* either a single argument as an object or an ordered set of arguments as an array.
* The `err` argument is assumed; do not specify. NOTE: Can have the same properties as
* `accepts`, except for `http.target`.
+ *
+ * Additionally, one of the callback arguments can have `type: 'file'` and
+ * `root:true`, in which case this argument is sent in the raw form as
+ * a response body. Allowed values: `String`, `Buffer` or `ReadableStream`
* @property {Boolean} [shared] Whether the method is shared. Default is `true`.
* @property {Number} [status] The default status code.
* @end
@@ -384,6 +393,7 @@ function convertToBasicRemotingType(type) {
case 'boolean':
case 'buffer':
case 'object':
+ case 'file':
case 'any':
return type;
case 'array':
@@ -520,9 +530,13 @@ SharedMethod.toResult = function(returns, raw, ctx) {
return false;
}
+ if (ctx && ctx.setReturnArgByName(item.name || item.arg, raw[index])) {
+ return false;
+ }
+
if (item.root) {
- result = convert(raw[index]);
- if (ctx) ctx.setReturnArgByName(item.name || item.arg, raw[index]);
+ var isFile = convertToBasicRemotingType(item.type) === 'file';
+ result = isFile ? raw[index] : convert(raw[index]);
return false;
}
@@ -530,10 +544,16 @@ SharedMethod.toResult = function(returns, raw, ctx) {
});
returns.forEach(function(item, index) {
- var value = convert(raw[index]);
var name = item.name || item.arg;
+ if (convertToBasicRemotingType(item.type) === 'file') {
+ console.warn('%s: discarded non-root return argument %s of type "file"',
+ this.stringName,
+ name);
+ return;
+ }
+
+ var value = convert(raw[index]);
result[name] = value;
- if (ctx) ctx.setReturnArgByName(name, value);
});
return result;
diff --git a/lib/socket-io-adapter.js b/lib/socket-io-adapter.js
index 3e9bbfd8..e03e7935 100644
--- a/lib/socket-io-adapter.js
+++ b/lib/socket-io-adapter.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/**
* Expose `SocketIOAdapter`.
*/
diff --git a/lib/socket-io-context.js b/lib/socket-io-context.js
index c7e7cf00..66333444 100644
--- a/lib/socket-io-context.js
+++ b/lib/socket-io-context.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/**
* Expose `SocketIOContext`.
*/
diff --git a/package.json b/package.json
index d257e76d..48e5ec83 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"Remoting",
"REST"
],
- "version": "2.24.0",
+ "version": "2.26.0",
"scripts": {
"pretest": "grunt jscs jshint",
"test": "mocha"
@@ -17,11 +17,13 @@
"body-parser": "^1.12.4",
"cors": "^2.6.0",
"debug": "^2.2.0",
+ "depd": "^1.1.0",
"eventemitter2": "^0.4.14",
"express": "4.x",
"inflection": "^1.7.1",
"jayson": "^1.2.0",
"js2xmlparser": "^0.1.9",
+ "loopback-phase": "^1.3.0",
"mux-demux": "^3.7.9",
"qs": "^2.4.2",
"request": "^2.55.0",
diff --git a/test/auth.test.js b/test/auth.test.js
index c4f0ad25..0b765fb4 100644
--- a/test/auth.test.js
+++ b/test/auth.test.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2015,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var auth = require('http-auth');
var crypto = require('crypto');
var expect = require('chai').expect;
diff --git a/test/authorize-hook.test.js b/test/authorize-hook.test.js
index 5c0f4017..870a9fd3 100644
--- a/test/authorize-hook.test.js
+++ b/test/authorize-hook.test.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2015,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var expect = require('chai').expect;
var express = require('express');
var RemoteObjects = require('../');
diff --git a/test/e2e/e2e-server.js b/test/e2e/e2e-server.js
index e574e2a9..1ad680a8 100644
--- a/test/e2e/e2e-server.js
+++ b/test/e2e/e2e-server.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
// this server should be started before running tests in the e2e directory
var path = require('path');
var FIXTURES = path.join(__dirname, 'fixtures');
diff --git a/test/e2e/fixtures/remotes.js b/test/e2e/fixtures/remotes.js
index fc5e8def..faaa05bc 100644
--- a/test/e2e/fixtures/remotes.js
+++ b/test/e2e/fixtures/remotes.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var RemoteObjects = require('../../../');
var remotes = module.exports = RemoteObjects.create();
diff --git a/test/e2e/fixtures/user.js b/test/e2e/fixtures/user.js
index 37b7dfb5..8b0826fb 100644
--- a/test/e2e/fixtures/user.js
+++ b/test/e2e/fixtures/user.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var debug = require('debug')('test user');
module.exports = User;
diff --git a/test/e2e/smoke.test.js b/test/e2e/smoke.test.js
index 08c96afa..25d71272 100644
--- a/test/e2e/smoke.test.js
+++ b/test/e2e/smoke.test.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var RemoteObjects = require('../../');
var expect = require('chai').expect;
var REMOTE_URL = 'http://localhost:3000';
diff --git a/test/helpers/shared-objects-factory.js b/test/helpers/shared-objects-factory.js
index 07cc4540..ae70cb6b 100644
--- a/test/helpers/shared-objects-factory.js
+++ b/test/helpers/shared-objects-factory.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
/* Copyright (c) 2013 StrongLoop, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
diff --git a/test/helpers/test-server.js b/test/helpers/test-server.js
index 4a2e54d2..32dc79bd 100644
--- a/test/helpers/test-server.js
+++ b/test/helpers/test-server.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
module.exports = function(callback) {
};
diff --git a/test/http-context.test.js b/test/http-context.test.js
index 303b7a7b..14bc67d8 100644
--- a/test/http-context.test.js
+++ b/test/http-context.test.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2015,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var request = require('supertest');
var HttpContext = require('../lib/http-context');
var SharedMethod = require('../lib/shared-method');
diff --git a/test/http-invocation.test.js b/test/http-invocation.test.js
index fc4f6108..807b88d7 100644
--- a/test/http-invocation.test.js
+++ b/test/http-invocation.test.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var assert = require('assert');
var HttpInvocation = require('../lib/http-invocation');
var SharedMethod = require('../lib/shared-method');
diff --git a/test/jsonrpc.test.js b/test/jsonrpc.test.js
index 3cb792b7..87a2417c 100644
--- a/test/jsonrpc.test.js
+++ b/test/jsonrpc.test.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var assert = require('assert');
var RemoteObjects = require('../');
var express = require('express');
diff --git a/test/karma.conf.js b/test/karma.conf.js
index ae16d6a7..a48ce3b1 100644
--- a/test/karma.conf.js
+++ b/test/karma.conf.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
// Karma configuration
// http://karma-runner.github.io/0.12/config/configuration-file.html
diff --git a/test/phase-handlers.test.js b/test/phase-handlers.test.js
new file mode 100644
index 00000000..88bd81e9
--- /dev/null
+++ b/test/phase-handlers.test.js
@@ -0,0 +1,136 @@
+// Copyright IBM Corp. 2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
+var expect = require('chai').expect;
+var express = require('express');
+var RemoteObjects = require('../');
+var User = require('./e2e/fixtures/user');
+
+describe('phase handlers', function() {
+ var server, remotes, clientRemotes;
+
+ beforeEach(function setupServer(done) {
+ var app = express();
+ remotes = RemoteObjects.create();
+ remotes.exports.User = User;
+ app.use(function(req, res, next) {
+ // always build a new handler to pick new methods added by tests
+ remotes.handler('rest')(req, res, next);
+ });
+ server = app.listen(0, '127.0.0.1', done);
+ });
+
+ beforeEach(function setupClient() {
+ clientRemotes = RemoteObjects.create();
+ clientRemotes.exports.User = User;
+ var url = 'http://127.0.0.1:' + server.address().port;
+ clientRemotes.connect(url, 'rest');
+ });
+
+ afterEach(function teardownServer(done) {
+ server.close(done);
+ });
+
+ it('has built-in phases "auth" and "invoke"', function() {
+ expect(remotes.phases.getPhaseNames()).to.eql(['auth', 'invoke']);
+ });
+
+ it('invokes phases in the correct order', function(done) {
+ var phasesRun = [];
+ var pushNameAndNext = function(name) {
+ return function(ctx, next) { phasesRun.push(name); next(); };
+ };
+
+ remotes.phases.find('auth').use(pushNameAndNext('phaseHandler-auth'));
+ var invokePhase = remotes.phases.find('invoke');
+ invokePhase.before(pushNameAndNext('phaseHandler-invoke:before'));
+ invokePhase.use(pushNameAndNext('phaseHandler-invoke:use'));
+ invokePhase.after(pushNameAndNext('phaseHandler-invoke:after'));
+
+ remotes.authorization = pushNameAndNext('hook-authorization');
+ remotes.before('**', pushNameAndNext('hook-remotes.before'));
+ remotes.after('**', pushNameAndNext('hook-remotes.after'));
+
+ User.pushName = function(cb) { phasesRun.push('invoke method'); cb(); };
+ User.pushName.shared = true;
+
+ invokeRemote('User.pushName', function(err) {
+ if (err) return done(err);
+ expect(phasesRun).to.eql([
+ 'hook-authorization',
+ 'phaseHandler-auth',
+ 'hook-remotes.before',
+ 'phaseHandler-invoke:before',
+ 'invoke method',
+ 'phaseHandler-invoke:use',
+ 'hook-remotes.after',
+ 'phaseHandler-invoke:after',
+ ]);
+ done();
+ });
+ });
+
+ describe('registerPhaseHandler', function() {
+ var handlersRun;
+
+ beforeEach(function() {
+ User.static = function(cb) { cb(); };
+ User.static.shared = true;
+
+ User.prototype.proto = function(cb) { cb(); };
+ User.prototype.proto.shared = true;
+
+ handlersRun = [];
+ function register(wildcard) {
+ remotes.registerPhaseHandler('invoke', wildcard, function(ctx, next) {
+ handlersRun.push(wildcard);
+ next();
+ });
+ }
+
+ register('**');
+ register('User.**');
+
+ register('User.*');
+ register('User.static');
+ register('User.proto'); // does not exist
+
+ register('User.prototype.*');
+ register('User.prototype.proto');
+ register('User.prototype.static'); // does not exist
+ });
+
+ it('matches static methods using wildcards', function(done) {
+ invokeRemote('User.static', function(err) {
+ if (err) return done(err);
+ expect(handlersRun).to.eql([
+ '**',
+ 'User.**',
+ 'User.*',
+ 'User.static',
+ ]);
+ done();
+ });
+ });
+
+ it('matches prototype methods using wildcards', function(done) {
+ invokeRemote('User.prototype.proto', function(err) {
+ if (err) return done(err);
+ expect(handlersRun).to.eql([
+ '**',
+ 'User.**',
+ 'User.prototype.*',
+ 'User.prototype.proto',
+ ]);
+ done();
+ });
+ });
+ });
+
+ function invokeRemote(method, callback) {
+ var args = [];
+ clientRemotes.invoke(method, args, callback);
+ }
+});
diff --git a/test/rest-adapter.test.js b/test/rest-adapter.test.js
index 24bac8f7..8ed49fe0 100644
--- a/test/rest-adapter.test.js
+++ b/test/rest-adapter.test.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var assert = require('assert');
var HttpInvocation = require('../lib/http-invocation');
var extend = require('util')._extend;
@@ -8,6 +13,7 @@ var SharedClass = require('../lib/shared-class');
var SharedMethod = require('../lib/shared-method');
var expect = require('chai').expect;
var factory = require('./helpers/shared-objects-factory.js');
+function NOOP() {}
describe('RestAdapter', function() {
var remotes;
@@ -213,6 +219,8 @@ describe('RestAdapter', function() {
});
describe('getHttpMethod', function() {
+ ignoreDeprecationsInThisBlock();
+
it('returns POST for `all`', function() {
var method = givenRestStaticMethod({ http: { verb: 'all'} });
expect(method.getHttpMethod()).to.equal('POST');
@@ -240,6 +248,8 @@ describe('RestAdapter', function() {
});
describe('getFullPath', function() {
+ ignoreDeprecationsInThisBlock();
+
it('returns class path + method path', function() {
var method = givenRestStaticMethod(
{ http: { path: '/a-method' } },
@@ -250,6 +260,37 @@ describe('RestAdapter', function() {
});
});
+ describe('getEndpoints', function() {
+ it('should return verb and fullPath for multiple paths', function() {
+ var method = givenRestStaticMethod({ http: [
+ { verb: 'DEL', path: '/testMethod1' },
+ { verb: 'PUT', path: '/testMethod2' },
+ ] });
+
+ var expectedEndpoints = [
+ {
+ fullPath: '/testClass/testMethod1',
+ verb: 'DELETE',
+ }, {
+ fullPath: '/testClass/testMethod2',
+ verb: 'PUT',
+ },
+ ];
+
+ expect(method.getEndpoints()).to.eql(expectedEndpoints);
+ });
+
+ it('should return verb and fullPath for single path', function() {
+ var method = givenRestStaticMethod({ http: { verb: 'all' }});
+ expect(method.getEndpoints()).to.eql([
+ {
+ verb: 'POST',
+ fullPath: '/testClass/testMethod',
+ },
+ ]);
+ });
+ });
+
function givenRestStaticMethod(methodConfig, classConfig) {
var name = 'testMethod';
methodConfig = extend({ shared: true }, methodConfig);
@@ -439,3 +480,13 @@ describe('RestAdapter', function() {
function someFunc() {
}
+
+function ignoreDeprecationsInThisBlock() {
+ before(function() {
+ process.on('deprecation', NOOP);
+ });
+
+ after(function() {
+ process.removeListener('deprecation', NOOP);
+ });
+}
diff --git a/test/rest.browser.test.js b/test/rest.browser.test.js
index f9561757..33f77192 100644
--- a/test/rest.browser.test.js
+++ b/test/rest.browser.test.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var assert = require('assert');
var extend = require('util')._extend;
var inherits = require('util').inherits;
diff --git a/test/rest.test.js b/test/rest.test.js
index d73986d6..41461575 100644
--- a/test/rest.test.js
+++ b/test/rest.test.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var assert = require('assert');
var extend = require('util')._extend;
var inherits = require('util').inherits;
@@ -7,6 +12,7 @@ var request = require('supertest');
var expect = require('chai').expect;
var factory = require('./helpers/shared-objects-factory.js');
var Promise = global.Promise || require('bluebird');
+var Readable = require('stream').Readable;
var ACCEPT_XML_OR_ANY = 'application/xml,*/*;q=0.8';
var TEST_ERROR = new Error('expected test error');
@@ -1359,6 +1365,121 @@ describe('strong-remoting-rest', function() {
});
});
+ it('should allow customized xml root element', function(done) {
+ var method = givenSharedStaticMethod(
+ function bar(cb) {
+ cb(null, {a: 1, b: 2});
+ },
+ {
+ returns: {
+ arg: 'data', type: 'object', root: true,
+ xml: { wrapperElement: 'foo'}
+ },
+ http: { path: '/' }
+ }
+ );
+ request(app).get(method.classUrl)
+ .set('Accept', 'application/xml')
+ .set('Content-Type', 'application/json')
+ .send()
+ .expect('Content-Type', /xml/)
+ .expect(200, function(err, res) {
+ expect(res.text).to.equal(
+ '\n' +
+ '\n ' +
+ '1\n ' +
+ '2\n' +
+ '');
+ done(err, res);
+ });
+ });
+
+ it('should allow xml declaration to be disabled', function(done) {
+ var method = givenSharedStaticMethod(
+ function bar(cb) {
+ cb(null, {a: 1, b: 2});
+ },
+ {
+ returns: {
+ arg: 'data', type: 'object', root: true,
+ xml: { declaration : false }
+ },
+ http: { path: '/' }
+ }
+ );
+ request(app).get(method.classUrl)
+ .set('Accept', 'application/xml')
+ .set('Content-Type', 'application/json')
+ .send()
+ .expect('Content-Type', /xml/)
+ .expect(200, function(err, res) {
+ expect(res.text).to.equal(
+ '\n ' +
+ '1\n ' +
+ '2\n' +
+ '');
+ done(err, res);
+ });
+ });
+
+ it('should allow string results to output as xml', function(done) {
+ var method = givenSharedStaticMethod(
+ function bar(cb) {
+ var stringResult = 'a quick brown fox jumps over the lazy dog';
+ cb(null, stringResult);
+ },
+ {
+ returns: {
+ root: true,
+ xml: { wrapperElement : 'text' }
+ },
+ http: { path: '/' }
+ }
+ );
+ request(app).get(method.classUrl)
+ .set('Accept', 'application/xml')
+ .set('Content-Type', 'application/json')
+ .send()
+ .expect('Content-Type', /xml/)
+ .expect(200, function(err, res) {
+ expect(res.text).to.equal(
+ '\n' +
+ 'a quick brown fox jumps over the lazy dog' +
+ '');
+ done(err, res);
+ });
+ });
+
+ it('should handle UTF-8 & special & reserved characters', function(done) {
+ var method = givenSharedStaticMethod(
+ function bar(cb) {
+ var stringA = 'foo\xC1\xE1\u0102\u03A9asd><=$~!@#$%^&*()-_=+/.,;\'"[]{}?';
+ cb(null, {a: stringA});
+ },
+ {
+ returns: {
+ arg: 'data', type: 'object', root: true,
+ xml: { wrapperElement: false }
+ },
+ http: { path: '/' }
+ }
+ );
+ request(app).get(method.classUrl)
+ .set('Accept', 'application/xml')
+ .set('Content-Type', 'application/json')
+ .send()
+ .expect('Content-Type', /xml/)
+ .expect(200, function(err, res) {
+ expect(res).to.be.utf8;
+ expect(res.text).to.equal(
+ '\n' +
+ '\n ' +
+ 'fooÁáĂΩasd><=$~!@#$%^&*()-_=+/.,;'"[]{}?\n' +
+ '');
+ done();
+ });
+ });
+
it('should produce xml from json objects with toXML()', function(done) {
var method = givenSharedStaticMethod(
function bar(a, cb) {
@@ -1461,6 +1582,70 @@ describe('strong-remoting-rest', function() {
.end(expectErrorResponseContaining({message: 'an error'}, done));
});
+ it('should return 500 if an array of errors is thrown', function(done) {
+ var testError = new Error('expected test error');
+ var errArray = [testError, testError];
+
+ function method(error) {
+ return givenSharedStaticMethod(function(cb) {
+ cb(error);
+ });
+ }
+
+ request(app).get(method(testError).url)
+ .set('Accept', 'application/json')
+ .expect(500)
+ .end(function(err, res) {
+ if (err) return done(err);
+ var expectedDetail = res.body.error;
+ delete expectedDetail.statusCode;
+
+ request(app).get(method(errArray).url)
+ .set('Accept', 'application/json')
+ .expect(500)
+ .end(function(err, res) {
+ if (err) return done(err);
+ var error = res.body.error;
+ expect(error).to.have.property('message').that.match(/multiple errors/);
+ expect(error).to.include.keys('details');
+ expect(error.details).to.include(expectedDetail);
+ done();
+ });
+ });
+ });
+
+ it('should return 500 if an array of errors is thrown', function(done) {
+ var testError = new Error('expected test error');
+ var errArray = [testError, testError];
+
+ function method(error) {
+ return givenSharedStaticMethod(function(cb) {
+ cb(error);
+ });
+ }
+
+ request(app).get(method(testError).url)
+ .set('Accept', 'application/json')
+ .expect(500)
+ .end(function(err, res) {
+ if (err) return done(err);
+ var expectedDetail = res.body.error;
+ delete expectedDetail.statusCode;
+
+ request(app).get(method(errArray).url)
+ .set('Accept', 'application/json')
+ .expect(500)
+ .end(function(err, res) {
+ if (err) return done(err);
+ var error = res.body.error;
+ expect(error).to.have.property('message').that.match(/multiple errors/);
+ expect(error).to.include.keys('details');
+ expect(error.details).to.include(expectedDetail);
+ done();
+ });
+ });
+ });
+
it('should return 500 if an error string is thrown', function(done) {
remotes.shouldThrow = {
bar: function(fn) {
@@ -1931,20 +2116,47 @@ describe('strong-remoting-rest', function() {
describe('result args as headers', function() {
it('sets the header using the callback arg', function(done) {
- var val = 'foobar';
+ var A_STRING_VALUE = 'foobar';
var method = givenSharedStaticMethod(
function fn(input, cb) {
- cb(null, input);
+ cb(null, input, input);
},
{
accepts: {arg: 'input', type: 'string'},
- returns: {arg: 'output', type: 'string', http: { target: 'header' } }
+ returns: [
+ {arg: 'value', type: 'string' },
+ {arg: 'output', type: 'string', http: { target: 'header' } }
+ ]
}
);
- json(method.url + '?input=' + val)
- .expect('output', val)
- .expect(200, done);
+ json(method.url + '?input=' + A_STRING_VALUE)
+ .expect(200)
+ .expect('output', A_STRING_VALUE)
+ .expect({ value: A_STRING_VALUE })
+ .end(done);
});
+
+ it('sets the header using the callback arg - root arg', function(done) {
+ var A_STRING_VALUE = 'foobar';
+ var method = givenSharedStaticMethod(
+ function fn(input, cb) {
+ cb(null, { value: input }, input);
+ },
+ {
+ accepts: {arg: 'input', type: 'string'},
+ returns: [
+ {arg: 'value', type: 'object', root: true },
+ {arg: 'output', type: 'string', http: { target: 'header' } }
+ ]
+ }
+ );
+ json(method.url + '?input=' + A_STRING_VALUE)
+ .expect(200)
+ .expect('output', A_STRING_VALUE)
+ .expect({ value: A_STRING_VALUE })
+ .end(done);
+ });
+
it('sets the custom header using the callback arg', function(done) {
var val = 'foobar';
var method = givenSharedStaticMethod(
@@ -1966,6 +2178,72 @@ describe('strong-remoting-rest', function() {
});
});
+ describe('returns type "file"', function() {
+ var METHOD_SIGNATURE = {
+ returns: [
+ { arg: 'body', type: 'file', root: true },
+ { arg: 'Content-Type', type: 'string', http: { target: 'header' } },
+ ],
+ };
+
+ it('should send back Buffer body', function(done) {
+ var method = givenSharedStaticMethod(
+ function(cb) { cb(null, new Buffer('some-text'), 'text/plain'); },
+ METHOD_SIGNATURE);
+
+ return request(app).get(method.url)
+ .expect(200)
+ .expect('Content-Type', /^text\/plain/)
+ .expect('some-text')
+ .end(done);
+ });
+
+ it('should send back String body', function(done) {
+ var method = givenSharedStaticMethod(
+ function(cb) { cb(null, 'some-text', 'text/plain'); },
+ METHOD_SIGNATURE);
+
+ return request(app).get(method.url)
+ .expect(200)
+ .expect('Content-Type', /^text\/plain/)
+ .expect('some-text')
+ .end(done);
+ });
+
+ it('should send back Stream body', function(done) {
+ var method = givenSharedStaticMethod(
+ function(cb) {
+ var stream = new Readable();
+ stream.push('some-text');
+ stream.push(null); // EOF
+ cb(null, stream, 'text/plain');
+ },
+ METHOD_SIGNATURE);
+
+ return request(app).get(method.url)
+ .expect(200)
+ .expect('Content-Type', /^text\/plain/)
+ .expect('some-text')
+ .end(done);
+ });
+
+ it('should fail for unsupported value type', function(done) {
+ var method = givenSharedStaticMethod(
+ function(cb) { cb(null, [1, 2]); },
+ METHOD_SIGNATURE);
+
+ return request(app).get(method.url)
+ .expect(500)
+ .expect('Content-Type', /json/)
+ .end(function(err, res) {
+ if (err) return done(err);
+ expect(res.body).to.have.property('error');
+ expect(res.body.error.message).to.match(/array/);
+ done();
+ });
+ });
+ });
+
it('returns correct error response body', function(done) {
function TestError() {
Error.captureStackTrace(this, TestError);
@@ -2615,6 +2893,24 @@ describe('strong-remoting-rest', function() {
});
});
+ it('is not called on success', function(done) {
+ var hookCalled = false;
+ var method = givenSharedStaticMethod(function(cb) {
+ cb();
+ });
+
+ objects.afterError(method.name, function(ctx, next) {
+ hookCalled = true;
+ next();
+ });
+
+ json(method.url).end(function(err) {
+ if (err) return done(err);
+ expect(hookCalled, 'hookCalled').to.equal(false);
+ done();
+ });
+ });
+
function verifyErrorHookIsCalled(method, expectedError, done) {
var hookContext = 'hook not called';
diff --git a/test/shared-class.test.js b/test/shared-class.test.js
index 3bd48426..d659a9fa 100644
--- a/test/shared-class.test.js
+++ b/test/shared-class.test.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var assert = require('assert');
var extend = require('util')._extend;
var expect = require('chai').expect;
diff --git a/test/shared-method.test.js b/test/shared-method.test.js
index 52c3199f..0527a62f 100644
--- a/test/shared-method.test.js
+++ b/test/shared-method.test.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var assert = require('assert');
var extend = require('util')._extend;
var expect = require('chai').expect;
diff --git a/test/streams.js b/test/streams.js
index 41bc4180..e2f7e534 100644
--- a/test/streams.js
+++ b/test/streams.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2013,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var assert = require('assert');
var RemoteObjects = require('../');
var expect = require('chai').expect;
@@ -32,24 +37,33 @@ describe('strong-remoting', function() {
.expect('Content-Type', /json/);
}
- it('should stream the file output', function(done) {
+ function createSteam() {
remotes.fs = fs;
fs.createReadStream.shared = true;
fs.createReadStream.accepts = [
{ arg: 'path', type: 'string' },
- { arg: 'encoding', type: 'string' }
+ { arg: 'encoding', type: 'string' },
];
- fs.createReadStream.returns = {arg: 'res', type: 'stream'};
+ fs.createReadStream.returns = { arg: 'res', type: 'stream' };
fs.createReadStream.http = {
verb: 'get',
- // path: '/fs/createReadStream',
pipe: {
- dest: 'res'
- }
+ dest: 'res',
+ },
};
+ }
+ it('should stream the file output', function(done) {
+ createSteam();
json('get', '/fs/createReadStream?path=' + __dirname + '/data/foo.json&encoding=utf8')
- .expect({bar: 'baz'}, done);
+ .expect({ bar: 'baz' }, done);
+ });
+
+ it('should stream the file output with no compression', function(done) {
+ createSteam();
+ request(app).get('/fs/createReadStream')
+ .expect('Content-Encoding', 'x-no-compression');
+ done();
});
});
diff --git a/test/type.test.js b/test/type.test.js
index e9486260..0e7d8573 100644
--- a/test/type.test.js
+++ b/test/type.test.js
@@ -1,3 +1,8 @@
+// Copyright IBM Corp. 2014,2016. All Rights Reserved.
+// Node module: strong-remoting
+// This file is licensed under the Artistic License 2.0.
+// License text available at https://opensource.org/licenses/Artistic-2.0
+
var assert = require('assert');
var Dynamic = require('../lib/dynamic');
var RemoteObjects = require('../');