diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f978251 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,151 @@ +### Contributing ### + +Thank you for your interest in `loopback`, an open source project +administered by StrongLoop. + +Contributing to `loopback` is easy. In a few simple steps: + + * Ensure that your effort is aligned with the project's roadmap by + talking to the maintainers, especially if you are going to spend a + lot of time on it. + + * Make something better or fix a bug. + + * Adhere to code style outlined in the [Google C++ Style Guide][] and + [Google Javascript Style Guide][]. + + * Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/loopback) + + * Submit a pull request through Github. + + +### Contributor License Agreement ### + +``` + Individual Contributor License Agreement + + By signing this Individual Contributor License Agreement + ("Agreement"), and making a Contribution (as defined below) to + StrongLoop, Inc. ("StrongLoop"), You (as defined below) accept and + agree to the following terms and conditions for Your present and + future Contributions submitted to StrongLoop. Except for the license + granted in this Agreement to StrongLoop and recipients of software + distributed by StrongLoop, You reserve all right, title, and interest + in and to Your Contributions. + + 1. Definitions + + "You" or "Your" shall mean the copyright owner or the individual + authorized by the copyright owner that is entering into this + Agreement with StrongLoop. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to StrongLoop for inclusion in, + or documentation of, any of the products owned or managed by + StrongLoop ("Work"). For purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication + sent to StrongLoop or its representatives, including but not + limited to communication or electronic mailing lists, source code + control systems, and issue tracking systems that are managed by, + or on behalf of, StrongLoop for the purpose of discussing and + improving the Work, but excluding communication that is + conspicuously marked or otherwise designated in writing by You as + "Not a Contribution." + + 2. You Grant a Copyright License to StrongLoop + + Subject to the terms and conditions of this Agreement, You hereby + grant to StrongLoop and recipients of software distributed by + StrongLoop, a perpetual, worldwide, non-exclusive, no-charge, + royalty-free, irrevocable copyright license to reproduce, prepare + derivative works of, publicly display, publicly perform, + sublicense, and distribute Your Contributions and such derivative + works under any license and without any restrictions. + + 3. You Grant a Patent License to StrongLoop + + Subject to the terms and conditions of this Agreement, You hereby + grant to StrongLoop and to recipients of software distributed by + StrongLoop a perpetual, worldwide, non-exclusive, no-charge, + royalty-free, irrevocable (except as stated in this Section) + patent license to make, have made, use, offer to sell, sell, + import, and otherwise transfer the Work under any license and + without any restrictions. The patent license You grant to + StrongLoop under this Section applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contributions(s) alone or by combination of Your Contributions(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes a patent litigation against You or any other + entity (including a cross-claim or counterclaim in a lawsuit) + alleging that Your Contribution, or the Work to which You have + contributed, constitutes direct or contributory patent + infringement, any patent licenses granted to that entity under + this Agreement for that Contribution or Work shall terminate as + of the date such litigation is filed. + + 4. You Have the Right to Grant Licenses to StrongLoop + + You represent that You are legally entitled to grant the licenses + in this Agreement. + + If Your employer(s) has rights to intellectual property that You + create, You represent that You have received permission to make + the Contributions on behalf of that employer, that Your employer + has waived such rights for Your Contributions, or that Your + employer has executed a separate Corporate Contributor License + Agreement with StrongLoop. + + 5. The Contributions Are Your Original Work + + You represent that each of Your Contributions are Your original + works of authorship (see Section 8 (Submissions on Behalf of + Others) for submission on behalf of others). You represent that to + Your knowledge, no other person claims, or has the right to claim, + any right in any intellectual property right related to Your + Contributions. + + You also represent that You are not legally obligated, whether by + entering into an agreement or otherwise, in any way that conflicts + with the terms of this Agreement. + + You represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which You + are personally aware and which are associated with any part of + Your Contributions. + + 6. You Don't Have an Obligation to Provide Support for Your Contributions + + You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. + + 6. No Warranties or Conditions + + StrongLoop acknowledges that unless required by applicable law or + agreed to in writing, You provide Your Contributions on an "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER + EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES + OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR + FITNESS FOR A PARTICULAR PURPOSE. + + 7. Submission on Behalf of Others + + If You wish to submit work that is not Your original creation, You + may submit it to StrongLoop separately from any Contribution, + identifying the complete details of its source and of any license + or other restriction (including, but not limited to, related + patents, trademarks, and license agreements) of which You are + personally aware, and conspicuously marking the work as + "Submitted on Behalf of a Third-Party: [named here]". + + 8. Agree to Notify of Change of Circumstances + + You agree to notify StrongLoop of any facts or circumstances of + which You become aware that would make these representations + inaccurate in any respect. Email us at callback@strongloop.com. +``` + +[Google C++ Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml +[Google Javascript Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..29d7815 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +Copyright (c) 2013-2015 StrongLoop, Inc and other contributors. + +loopback uses a dual license model. + +You may use this library under the terms of the [MIT License][], +or under the terms of the [StrongLoop Subscription Agreement][]. + +[MIT License]: http://opensource.org/licenses/MIT +[StrongLoop Subscription Agreement]: http://strongloop.com/license diff --git a/README.md b/README.md index 0b7ff14..ae3896d 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,217 @@ -# loopback-example-connector +# loopback-example-connector (SOAP) -LoopBack connector examples. +The SOAP connector enables LoopBack applications to interact with +[SOAP](http://www.w3.org/TR/soap)-based web services described using +[WSDL](http://www.w3.org/TR/wsdl). This example app illustrates calling a [periodic table SOAP web service](http://www.webservicex.net/periodictable.asmx) from a LoopBack app, +where you define a remote method to call each SOAP web service operation. +This is a simple example of a LoopBack app "proxying" or intermediating a web service. -## Getting started +For more information, see the +SOAP connector documentation. -Switch to a branch to view the corresponding connector example in the table -below: +## Installation -Connector|Branch -:--|:-- -SOAP| +Clone this repo and install dependencies from npm: + + +```shell +$ git clone https://github.com/strongloop-community/loopback-example-connector.git -b soap +$ cd loopback-example-connector +$ npm install +``` + +## Run the example + +1. In the project root directory, run the app by entering this command: + ``` + node . + ``` + + You'll see this in the console: + ``` + Web server listening at: http://0.0.0.0:3000 + Browse your REST API at http://0.0.0.0:3000/explorer + ``` + +2. Open [API Explorer](http://0.0.0.0:3000/explorer) in your web browser. +3. Under the **periodicTable** model, you'll see two endpoints: `GET /periodicTables/GetAtomicNumber` and `GET /periodicTables/GetAtomicWeight`. +4. Click on `GetAtomicNumber` and in the **elementName** parameter field, enter the name of an element, for example, "Gold" or "Oxygen". In the **Response Body** field, you'll see an XML response from the web service that looks something like this (NOTE: line feeds have been added in the example below for readability, but in practice the response will be on a single line): + + ``` + { + "GetAtomicNumberResult": "\r\n \r\n + 26\r\n + Iron\r\n + Fe\r\n + 55.847\r\n + 3300\r\n + 7.9\r\n + 1.6400000000000001\r\n + 1.17\r\n + 1808\r\n + 7874\r\n
\r\n
" + } + ``` + +### Additional example code + +The app includes two other files `periodictable-ws.js` and `stock-ws.js` that illustrate other ways of programmatically calling a SOAP web service. NOTE: the [stock quote web service](http://www.webservicex.net/stockquote.asmx) may not be consistently available. + +## Recreate the app + +**Prerequisites**: If you haven't already done so, follow the [Installation instructions](http://loopback.io/doc/en/lb3/Installation.html) for the LoopBack CLI. In a nutshell: +``` +$ npm install -g loopback-cli +``` + +To create this app yourself, follow the steps in this section: +- [Scaffold the app](#scaffold-the-app) +- [Create a SOAP data source](#create-a-soap-data-source) +- [Create a periodicTable model](#create-a-periodictable-model) +- [Add remote methods](#add-remote-methods) +- [Try it out!](#try-it-out) + +### Scaffold the app + +Scaffold a new application. Enter this command: + +``` +$ lb app +``` + +When prompted, respond as follows: +1. `What's the name of your application?` +
Give the app any name you wish, for example "my-soap-demo". +The tool will create a directory with that name (`my-soap-demo`). +1. `Enter name of the directory to contain the project:` +
Press Enter to accept the default (directory has the same name as the app). +1. `Which version of LoopBack would you like to use?` +
Choose `3.x (current)` +1. `What kind of application do you have in mind?` +
Choose `empty-server (An empty LoopBack API, without any configured models or datasources) ` + +The tool will then scaffold the app and install all the dependencies from npm. + +### Create a SOAP data source + +Go into the app root directory: +``` +$ cd my-soap-demo +``` + +Use the [data source generator](http://loopback.io/doc/en/lb3/Data-source-generator.html) to add a SOAP data source to your application. Enter this command: + +```shell +$ lb datasource +``` + +When prompted, respond as follows: + +1. `Enter the datasource name:` +
Enter "soapDS". +1. ` Select the connector for soapDS:` +
Use your arrow key to scroll down to `SOAP webservices (supported by StrongLoop)` and press Enter. +1. `URL to the SOAP web service endpoint:` +
Copy and paste the URL of the periodic table web service: + ``` + http://www.webservicex.net/periodictable.asmx + ``` +1. `HTTP URL or local file system path to the WSDL file:` +
Copy and paste this URL of the periodic table web service WSDL: + ``` + http://www.webservicex.net/periodictable.asmx?WSDL + ``` +1. `Expose operations as REST APIs: (Y/n)` +
Press Enter to accept the default (yes). +1. `Maps WSDL binding operations to Node.js methods:` +
Copy and paste the stringified JSON below. The JSON defines the service, port, and operation for each operation that will be called in the SOAP service. + ``` + {"getAtomicWeight":{"service":"periodictable","port":"periodictableSoap","operation":"GetAtomicWeight"},"getAtomicNumber":{"service":"periodictable","port":"periodictableSoap","operation":"GetAtomicNumber"}} + ``` + NOTE: The JSON you enter must **not** have any line endings, that is, it must be on a single line. +1. `Install loopback-connector-soap@^3.0` +
Press Enter to install the connector from npm. + +The data source generator then creates an entry for the data source in the `server/datasources.json` file and installs all the necessary dependencies. + +### Create a periodicTable model + +Use the [model generator](http://loopback.io/doc/en/lb3/Model-generator.html) to add a model to represent the periodic Table web service. Enter this command: + +```shell +$ lb model +``` + +When prompted, respond as follows: + +1. `Enter the model name:`
Enter "periodicTable". +1. `Enter the model name: periodicTable`
Select `soapDS` that you previously created. +1. `Select model's base class` Select `Model`. +1. `Expose periodicTable via the REST API? (Y/n)`
Press Enter to accept the default (yes). +1. `Custom plural form (used to build REST URL):`
Press Enter for no custom plural. +1. `Common model or server only?`
Select `server` because this will be a server-only model. +1. `Property name:`
Press Enter when first prompted, because this model will not have any properties. + +The tool will create two files in the `server` directory: `periodicTable.json` and `periodicTable.js`. + +### Add remote methods + +Edit `server/models/periodic-table.js` and add the code shown below to the stubbed-out function. + +This code defines two functions, `Periodictable.getAtomicnumber` and `Periodictable.getAtomicweight` and adds them as remote methods to the `Periodictable` model, as described in [Remote methods](https://loopback.io/doc/en/lb3/Remote-methods.html). + +```javascript +'use strict'; + +module.exports = function(Periodictable) { + + // External PeriodTable WebService operation exposed as REST APIs through LoopBack + Periodictable.getAtomicnumber = function (elementName, cb) { + Periodictable.GetAtomicNumber({ElementName: elementName || 'Copper'}, function (err, response) { + var result = response; + cb(err, result); + }); + }; + + // External PeriodTable WebService operation exposed as REST APIs through LoopBack + Periodictable.getAtomicweight = function(elementName, callback) { + Periodictable.GetAtomicWeight({ElementName: elementName || 'Copper'}, function (err, response) { + var result = response; + callback(err, result); + }); + } + + // Map to REST/HTTP + Periodictable.remoteMethod( + 'getAtomicnumber', { + accepts: [ + {arg: 'elementName', type: 'string', required: true, + http: {source: 'query'}} + ], + returns: {arg: 'result', type: 'object', root: true}, + http: {verb: 'get', path: '/GetAtomicNumber'} + } + ); + + Periodictable.remoteMethod( + 'getAtomicweight', { + accepts: [ + {arg: 'elementName', type: 'string', required: true, + http: {source: 'query'}} + ], + returns: {arg: 'result', type: 'object', root: true}, + http: {verb: 'get', path: '/GetAtomicWeight'} + } + ); + +}; +``` + +### Try it out! + +Now, run your app: +``` +node . +``` + +Follow the same steps [as before](#run-the-example) and your app should behave the same as the one in this repository. diff --git a/package.json b/package.json new file mode 100644 index 0000000..931b6ed --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "loopback-example-connector", + "version": "1.0.0", + "main": "server/server.js", + "description": "LoopBack SOAP connector example", + "repository": { + "type": "git", + "url": "git+https://github.com/strongloop/loopback-example-connector.git" + }, + "bugs": { + "url": "https://github.com/strongloop/loopback-example-connector/issues" + }, + "homepage": "https://github.com/strongloop/loopback-example-connector#readme", + "engines": { + "node": ">=4" + }, + "scripts": { + "lint": "eslint .", + "start": "node .", + "posttest": "npm run lint && nsp check" + }, + "dependencies": { + "compression": "^1.0.3", + "cors": "^2.5.2", + "helmet": "^1.3.0", + "loopback": "^3.0.0", + "loopback-boot": "^2.6.5", + "loopback-component-explorer": "^4.0.0", + "loopback-connector-soap": "^3.0.1", + "serve-favicon": "^2.0.1", + "strong-error-handler": "^2.0.0" + }, + "devDependencies": { + "eslint": "^3.17.1", + "eslint-config-loopback": "^8.0.0", + "nsp": "^2.1.0" + }, + "author": "StrongLoop ", + "license": "MIT" +} diff --git a/periodictable-ws.js b/periodictable-ws.js new file mode 100644 index 0000000..22d9d53 --- /dev/null +++ b/periodictable-ws.js @@ -0,0 +1,37 @@ +var loopback = require('loopback'); +app = loopback(); +ds = app.datasources['soapDS']; + +var ds = loopback.createDataSource('soap', + { + connector: require('loopback-connector-soap'), + wsdl: 'http://www.webservicex.net/periodictable.asmx?WSDL' // The url to WSDL + }); + +// Unfortunately, the methods from the connector are mixed in asynchronously +// This is a hack to wait for the methods to be injected +ds.once('connected', function () { + + // Set up a before-execute hook to dump out the request object + ds.connector.observe('before execute', function(ctx, next) { + //console.log('Http Request: ', ctx.req); + next(); + }); + + // Create the model + var PeriodictableService = ds.createModel('PeriodictableService', {}); + + console.log(' \n Response from external WebService, http://www.webservicex.net/periodictable.asmx'); + PeriodictableService.GetAtomicNumber({ElementName: 'Iron'}, function (err, response) { + console.log('\n GetAtomicNumber for Iron: \n %j', response); + }); + + PeriodictableService.GetAtomicWeight({ElementName: 'Gold'}, function (err, response) { + console.log('\n GetAtomicWeight for Gold: \n %j', response); + }); + + PeriodictableService.GetElementSymbol({ElementName: 'Silver'}, function (err, response) { + console.log('\n GetElementSymbol for Silver: \n %j', response); + }); + +}); diff --git a/server/boot/root.js b/server/boot/root.js new file mode 100644 index 0000000..6adce90 --- /dev/null +++ b/server/boot/root.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function(server) { + // Install a `/` route that returns server status + var router = server.loopback.Router(); + router.get('/', server.loopback.status()); + server.use(router); +}; diff --git a/server/component-config.json b/server/component-config.json new file mode 100644 index 0000000..f36959a --- /dev/null +++ b/server/component-config.json @@ -0,0 +1,5 @@ +{ + "loopback-component-explorer": { + "mountPath": "/explorer" + } +} diff --git a/server/config.json b/server/config.json new file mode 100644 index 0000000..d371cd2 --- /dev/null +++ b/server/config.json @@ -0,0 +1,22 @@ +{ + "restApiRoot": "/api", + "host": "0.0.0.0", + "port": 3000, + "remoting": { + "context": false, + "rest": { + "handleErrors": false, + "normalizeHttpPath": false, + "xml": false + }, + "json": { + "strict": false, + "limit": "100kb" + }, + "urlencoded": { + "extended": true, + "limit": "100kb" + }, + "cors": false + } +} diff --git a/server/datasources.json b/server/datasources.json new file mode 100644 index 0000000..e344673 --- /dev/null +++ b/server/datasources.json @@ -0,0 +1,21 @@ +{ + "soapDS": { + "url": "http://www.webservicex.net/periodictable.asmx", + "name": "soapDS", + "wsdl": "http://www.webservicex.net/periodictable.asmx?WSDL", + "remotingEnabled": true, + "operations": { + "getAtomicWeight": { + "service": "periodictable", + "port": "periodictableSoap", + "operation": "GetAtomicWeight" + }, + "getAtomicNumber": { + "service": "periodictable", + "port": "periodictableSoap", + "operation": "GetAtomicNumber" + } + }, + "connector": "soap" + } +} diff --git a/server/middleware.development.json b/server/middleware.development.json new file mode 100644 index 0000000..071c11a --- /dev/null +++ b/server/middleware.development.json @@ -0,0 +1,10 @@ +{ + "final:after": { + "strong-error-handler": { + "params": { + "debug": true, + "log": true + } + } + } +} diff --git a/server/middleware.json b/server/middleware.json new file mode 100644 index 0000000..cde21fe --- /dev/null +++ b/server/middleware.json @@ -0,0 +1,50 @@ +{ + "initial:before": { + "loopback#favicon": {} + }, + "initial": { + "compression": {}, + "cors": { + "params": { + "origin": true, + "credentials": true, + "maxAge": 86400 + } + }, + "helmet#xssFilter": {}, + "helmet#frameguard": { + "params": [ + "deny" + ] + }, + "helmet#hsts": { + "params": { + "maxAge": 0, + "includeSubdomains": true + } + }, + "helmet#hidePoweredBy": {}, + "helmet#ieNoOpen": {}, + "helmet#noSniff": {}, + "helmet#noCache": { + "enabled": false + } + }, + "session": {}, + "auth": {}, + "parse": {}, + "routes": { + "loopback#rest": { + "paths": [ + "${restApiRoot}" + ] + } + }, + "files": {}, + "final": { + "loopback#urlNotFound": {} + }, + "final:after": { + "strong-error-handler": {} + } +} diff --git a/server/model-config.json b/server/model-config.json new file mode 100644 index 0000000..d189583 --- /dev/null +++ b/server/model-config.json @@ -0,0 +1,20 @@ +{ + "_meta": { + "sources": [ + "loopback/common/models", + "loopback/server/models", + "../common/models", + "./models" + ], + "mixins": [ + "loopback/common/mixins", + "loopback/server/mixins", + "../common/mixins", + "./mixins" + ] + }, + "periodicTable": { + "dataSource": "soapDS", + "public": true + } +} diff --git a/server/models/periodic-table.js b/server/models/periodic-table.js new file mode 100644 index 0000000..7908b45 --- /dev/null +++ b/server/models/periodic-table.js @@ -0,0 +1,44 @@ +'use strict'; + +module.exports = function(Periodictable) { + + // External PeriodTable WebService operation exposed as REST APIs through LoopBack + Periodictable.getAtomicnumber = function (elementName, cb) { + Periodictable.GetAtomicNumber({ElementName: elementName || 'Copper'}, function (err, response) { + var result = response; + cb(err, result); + }); + }; + + // External PeriodTable WebService operation exposed as REST APIs through LoopBack + Periodictable.getAtomicweight = function(elementName, callback) { + Periodictable.GetAtomicWeight({ElementName: elementName || 'Copper'}, function (err, response) { + var result = response; + callback(err, result); + }); + } + + // Map to REST/HTTP + Periodictable.remoteMethod( + 'getAtomicnumber', { + accepts: [ + {arg: 'elementName', type: 'string', required: true, + http: {source: 'query'}} + ], + returns: {arg: 'result', type: 'object', root: true}, + http: {verb: 'get', path: '/GetAtomicNumber'} + } + ); + + Periodictable.remoteMethod( + 'getAtomicweight', { + accepts: [ + {arg: 'elementName', type: 'string', required: true, + http: {source: 'query'}} + ], + returns: {arg: 'result', type: 'object', root: true}, + http: {verb: 'get', path: '/GetAtomicWeight'} + } + ); + +}; diff --git a/server/models/periodic-table.json b/server/models/periodic-table.json new file mode 100644 index 0000000..b1536ad --- /dev/null +++ b/server/models/periodic-table.json @@ -0,0 +1,13 @@ +{ + "name": "periodicTable", + "base": "Model", + "idInjection": true, + "options": { + "validateUpsert": true + }, + "properties": {}, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} diff --git a/server/server.js b/server/server.js new file mode 100644 index 0000000..2ae5f41 --- /dev/null +++ b/server/server.js @@ -0,0 +1,30 @@ +'use strict'; + +var loopback = require('loopback'); +var boot = require('loopback-boot'); + +var app = module.exports = loopback(); + +app.start = function() { + // start the web server + return app.listen(function() { + app.emit('started'); + var baseUrl = app.get('url').replace(/\/$/, ''); + console.log('Web server listening at: %s', baseUrl); + if (app.get('loopback-component-explorer')) { + var explorerPath = app.get('loopback-component-explorer').mountPath; + console.log('Browse your REST API at %s%s', baseUrl, explorerPath); + } + + }); +}; + +// Bootstrap the application, configure models, datasources and middleware. +// Sub-apps like REST API are mounted via boot scripts. +boot(app, __dirname, function(err) { + if (err) throw err; + + // start the server if `$ node server.js` + if (require.main === module) + app.start(); +}); diff --git a/stock-ws.js b/stock-ws.js new file mode 100644 index 0000000..09c5cd2 --- /dev/null +++ b/stock-ws.js @@ -0,0 +1,41 @@ +var loopback = require('loopback'); + +var ds = loopback.createDataSource('soap', + { + connector: require('loopback-connector-soap'), + wsdl: 'http://www.webservicex.net/stockquote.asmx?WSDL', // The url to WSDL + url: 'http://www.webservicex.net/stockquote.asmx', // The service endpoint + + // Map SOAP service/port/operation to Node.js methods + operations: { + // The key is the method name + stockQuote: { + service: 'StockQuote', // The WSDL service name + port: 'StockQuoteSoap', // The WSDL port name + operation: 'GetQuote' // The WSDL operation name + }, + // The key is the method name + stockQuote12: { + service: 'StockQuote', // The WSDL service name + port: 'StockQuoteSoap12', // The WSDL port name + operation: 'GetQuote' // The WSDL operation name + } + } + }); + +// Unfortunately, the methods from the connector are mixed in asynchronously +// This is a hack to wait for the methods to be injected +ds.once('connected', function () { +// Create the model + var StockQuote = ds.createModel('StockQuote', {}); + + StockQuote.stockQuote({symbol: 'IBM'}, function (err, response) { + console.log('Response: ', response); + }); + + StockQuote.stockQuote12({symbol: 'FB'}, function (err, response) { + console.log('Response: ', response); + }); + + +});