From 0d87bf00eab1d8dc256f59ff47bf5937663fa9d4 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Fri, 27 Sep 2013 09:27:25 -0600 Subject: [PATCH 01/18] Updating documentation once more. --- documentation.md | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/documentation.md b/documentation.md index 1468708c88..535adec640 100644 --- a/documentation.md +++ b/documentation.md @@ -1,6 +1,6 @@ ## Introduction -Feathers sits right on top of Express, one of the most popular web frameworks for NodeJS. If you are not familiar with Express head over to the [Express Guides](http://expressjs.com/guide.html). Feathers works the exact same way except that `var app = require('express')();` is replaced with `var app = require('feathers')()`. The small differences and additional functionality available is outline in the following documentation. +Feathers sits right on top of Express, one of the most popular web frameworks for [NodeJS](http://nodejs.org/). If you are not familiar with Express head over to the [Express Guides](http://expressjs.com/guide.html) to get an idea. Feathers works the exact same way except that `var app = require('express')();` is replaced with `var app = require('feathers')()`. The small differences and additional functionality available is outline in the following documentation. ## Configuration @@ -41,9 +41,9 @@ Once the server has been started with `app.listen()` the SocketIO object is avai ```js var app = feathers(); -app.use('/todos', { +app.use('/my/todos', { setup: function(app, path) { - // path -> 'todos' + // path -> 'my/todos' } }); @@ -54,7 +54,7 @@ server.close(); ### lookup -`app.lookup(path)` returns the wrapped service object for the given path. Note that the returned object will provide the same methods and functionality as the original service but actually is a new object with additional functionality added (most notably it is possible to listen to service events). `path` can be the service name with or without leading and trailing slashes. +`app.lookup(path)` returns the wrapped service object for the given path. Note that Feathers internally creates a new object from each registered service. This means that the object returned by `lookup` will provide the same methods and functionality as the original service but also functionality added by Feathers (most notably it is possible to listen to service events). `path` can be the service name with or without leading and trailing slashes. ```js app.use('/my/todos', { @@ -99,7 +99,7 @@ app.use('/todos', { ## Services -A service can be any JavaScript object that offers one or more of the `find`, `get`, `create`, `update`, `remove` and `setup` service methods: +A service can be any JavaScript object that offers one or more of the `find`, `get`, `create`, `update`, `remove` and `setup` service methods with the following signatures: ```js var myService = { @@ -112,7 +112,7 @@ var myService = { } ``` -All callbacks follow the `function(error, data)` NodeJS convention. `params` can contain any additional parameters, for example the currently authenticated user. REST service calls set `params.query` with the query parameters (e.g. a query string like `?status=active&type=user` becomes `{ status: "active", type: "user" }`). +All callbacks follow the `function(error, data)` NodeJS convention. `params` can contain any additional parameters, for example the currently authenticated user. REST service calls set `params.query` with the query parameters (e.g. a query string like `?status=active&type=user` becomes `{ query: { status: "active", type: "user" } }`). ### find @@ -167,7 +167,7 @@ __SocketIO__ ```js socket.emit('todo::create', { description: 'I really have to iron' -}, function(error, data) { +}, {}, function(error, data) { }); ``` @@ -227,7 +227,7 @@ var myService = { get: function(name, params, callback) { this.todo.get('take out trash', {}, function(error, todo) { - callback(null, { + callback(error, { name: name, todo: todo }); @@ -266,7 +266,7 @@ var myService = { ## Events -Any registered service will be automatically turned into an event emitter that emits events when a resource has changed, that is a `create`, `update` or `remove` service call returned successfully. It is therefore possible to bind to the below events via `app.lookup(servicename).on()` and, if enabled, all events will also broadcast to all connected SocketIO clients in the form of ` `. +Any registered service will be automatically turned into an event emitter that emits events when a resource has changed, that is a `create`, `update` or `remove` service call returned successfully. It is therefore possible to bind to the below events via `app.lookup(servicename).on()` and, if enabled, all events will also broadcast to all connected SocketIO clients in the form of ` `. Note that the service path will always be stripped of leading and trailing slashes regardless of how it has been registered (e.g. `/my/service/` will become `my/service`). ### created @@ -310,16 +310,12 @@ __SocketIO__ The `updated` event will be published with the callback data when a service `update` calls back successfully. ```js -app.use('/todos', { +app.use('/my/todos/', { update: function(id, data, params, callback) { callback(null, data); } }); -app.lookup('/todos').on('updated', function(todo) { - console.log('Updated todo', todo); -}); - app.listen(8000); ``` @@ -330,15 +326,15 @@ __SocketIO__ ``` @@ -376,9 +372,9 @@ __SocketIO__ ## Why? -We know... Oh God another NodeJS framework! Yes we are also very tired of seeing all these NodeJS frameworks. All the rails clones are getting a bit boring and really aren't taking advantage of the real strengths of NodeJS. We wanted to take a different approach than every other framework we have seen, because we believe that data is core to the web and should be the core focus of web applications. +We know... Oh God another NodeJS framework! We really didn't want to add another name to the long list of NodeJS web frameworks but also wanted to explore a different approach than any other framework we have seen. We strongly believe that data is the core of the web and should be the focus of web applications. -We also think that your data resources can and should be encapsulated in such a way that they can be ultra scalable and self contained. The MVC pattern works well but it is becoming antiquated in today's web. Frankly you don't need it and they tend to become bloated. +We also think that your data resources can and should be encapsulated in such a way that they can be scalable, easily testable and self contained. The classic web MVC pattern used to work well but is becoming antiquated in today's web. With that being said there are some amazing frameworks already out there and we wanted to leverage the ideas that have been put into them, which is why Feathers is built on top of [Express](http://expressjs.com) and is inspired in part by [Sails](http://sailsjs.org), [Flatiron](http://flatironjs.org) and [Derby](http://derbyjs.com). From e0c17c33282ea19df426616c03c2762f0908185c Mon Sep 17 00:00:00 2001 From: David Luecke Date: Fri, 27 Sep 2013 09:32:19 -0600 Subject: [PATCH 02/18] Adding docs for req.feathers --- documentation.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/documentation.md b/documentation.md index 535adec640..1a87a8cec8 100644 --- a/documentation.md +++ b/documentation.md @@ -6,8 +6,26 @@ Feathers sits right on top of Express, one of the most popular web frameworks fo ### REST -Exposing services through a RESTful JSON interface is enabled by default. If you only want to use SocketIO -call `app.disabled('feathers rest')` _before_ registering any services. +Exposing services through a RESTful JSON interface is enabled by default. If you only want to use SocketIO call `app.disabled('feathers rest')` _before_ registering any services. + +To set service parameters in a middleware, just attach it to the `req.feathers` object which will become the params for any resulting service call: + +```js +app.use(function(req, res) { + req.feathers.data = 'Hello world'; +}); + +app.use('/todos', { + get: function(name, params, callback) { + console.log(params.data); // -> 'Hello world' + callback(null, { + id: name, + params: params, + description: "You have to do " + name + "!" + }); + } +}); +``` ### SocketIO From 83ba2d5771d53b5573958cda3a240221562aa8a5 Mon Sep 17 00:00:00 2001 From: Steffen Bruchmann Date: Thu, 12 Dec 2013 04:19:15 +0100 Subject: [PATCH 03/18] Remove middleware: connect.bodyParser() Because `connect.bodyParser()` is deprecated and will be removed in Connect 3, this commit removes it from the stack and uses `express.urlencoded()` and `express.json()` instead. Visit the [Connect Wiki][wiki] for more information. [wiki]: https://github.com/senchalabs/connect/wiki/Connect-3.0 --- lib/feathers.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/feathers.js b/lib/feathers.js index da03addfcd..84134e24a0 100644 --- a/lib/feathers.js +++ b/lib/feathers.js @@ -18,7 +18,8 @@ function createApplication() { Proto.mixin(Application, app); app.init(); // Add REST provider by default, can always be disabled using app.disable('feathers rest') - app.use(express.bodyParser()).configure(providers.rest()); + app.use(express.urlencoded()); + app.use(express.json()).configure(providers.rest()); return app; } From 40f62b92b09c12ed9d208d67db1c7a4d83da3650 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Thu, 2 Jan 2014 15:59:42 -0700 Subject: [PATCH 04/18] Add .npmignore, closes #30 --- .npmignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000000..62c893550a --- /dev/null +++ b/.npmignore @@ -0,0 +1 @@ +.idea/ \ No newline at end of file From 37be8a79abc31659584a361de4282a35cde63ca9 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Fri, 3 Jan 2014 16:20:23 -0700 Subject: [PATCH 05/18] Updating Rubberduck to 1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fac14292a8..899c4618b9 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "dependencies": { "uberproto": "~ 1.1.0", "express": "~ 3.4.0", - "rubberduck": "~0.2.0", + "rubberduck": "~1.x", "underscore": "~1.5.0", "socket.io": "~0.9.0" }, From c544515d952f2ae449bfa8d32cd49cca4750c62e Mon Sep 17 00:00:00 2001 From: David Luecke Date: Fri, 3 Jan 2014 16:25:28 -0700 Subject: [PATCH 06/18] Remove SocketIO default configuration (closes #19). --- documentation.md | 25 ++++++++++++++++++++++++- lib/providers/socketio.js | 15 +-------------- test/application.test.js | 4 +++- test/providers/socketio.test.js | 4 +++- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/documentation.md b/documentation.md index 1a87a8cec8..0c6af971bf 100644 --- a/documentation.md +++ b/documentation.md @@ -29,7 +29,29 @@ app.use('/todos', { ### SocketIO -To expose services via [SocketIO](http://socket.io/) call `app.configure(feathers.socketio())`. It is also possible pass a `function(io) {}` when initializing the provider where `io` is the main SocketIO object so you can listen to custom events, change the configuration or add [authorization](https://github.com/LearnBoost/socket.io/wiki/Authorizing): +To expose services via [SocketIO](http://socket.io/) call `app.configure(feathers.socketio())`. It is also possible pass a `function(io) {}` when initializing the provider where `io` is the main SocketIO object. Since Feathers is only using the SocketIO default configuration, this is a good spot to initialize the [recommended production settings](https://github.com/LearnBoost/Socket.IO/wiki/Configuring-Socket.IO#recommended-production-settings): + +```js +app.configure(feathers.socketio(function(io) { + io.enable('browser client minification'); // send minified client + io.enable('browser client etag'); // apply etag caching logic based on version number + io.enable('browser client gzip'); // gzip the file + io.set('log level', 1); // reduce logging + + // enable all transports (optional if you want flashsocket support, please note that some hosting + // providers do not allow you to create servers that listen on a port different than 80 or their + // default port) + io.set('transports', [ + 'websocket' + , 'flashsocket' + , 'htmlfile' + , 'xhr-polling' + , 'jsonp-polling' + ]); +})); +``` + +This is also the place to listen to custom events or add [authorization](https://github.com/LearnBoost/socket.io/wiki/Authorizing): ```js app.configure(feathers.socketio(function(io) { @@ -41,6 +63,7 @@ app.configure(feathers.socketio(function(io) { }); io.set('authorization', function (handshakeData, callback) { + // Authorize using the /users service app.lookup('users').find({ username: handshakeData.username, password: handshakeData.password diff --git a/lib/providers/socketio.js b/lib/providers/socketio.js index d02e226ee7..5e2ed542cd 100644 --- a/lib/providers/socketio.js +++ b/lib/providers/socketio.js @@ -2,19 +2,6 @@ var _ = require('underscore'); var socketio = require('socket.io'); -var listen = function (httpServer) { - var io = socketio.listen(httpServer); - - io.enable('browser client etag'); - io.set('log level', 0); - - io.set('transports', [ - 'xhr-polling', 'websocket', 'flashsocket', - 'htmlfile', 'jsonp-polling' - ]); - - return io; -}; module.exports = function (config) { return function () { @@ -30,7 +17,7 @@ module.exports = function (config) { return httpServer; } - var io = this.io = listen(httpServer); + var io = this.io = socketio.listen(httpServer); _.each(services, function (service, path) { // If the service emits events that we want to listen to (Event mixin) diff --git a/test/application.test.js b/test/application.test.js index f31c961903..53ebab07bb 100644 --- a/test/application.test.js +++ b/test/application.test.js @@ -63,7 +63,9 @@ describe('Feathers application', function () { var oldlog = console.log; console.log = function () {}; - var app = feathers().configure(feathers.socketio()).use('/todo', todoService); + var app = feathers().configure(feathers.socketio(function(io) { + io.set('log level', 0); + })).use('/todo', todoService); var server = app.listen(6999).on('listening', function () { console.log = oldlog; diff --git a/test/providers/socketio.test.js b/test/providers/socketio.test.js index daa59b77b4..95a7080c72 100644 --- a/test/providers/socketio.test.js +++ b/test/providers/socketio.test.js @@ -19,7 +19,9 @@ describe('SocketIO provider', function () { console.log = function () {}; server = feathers() - .configure(feathers.socketio()) + .configure(feathers.socketio(function(io) { + io.set('log level', 0); + })) .use('todo', todoService) .listen(7886); From 7b059e554d81fb56ff878236adbb332f56ab47e1 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Fri, 3 Jan 2014 16:27:49 -0700 Subject: [PATCH 07/18] Adding changelog for 0.3.0 --- documentation.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/documentation.md b/documentation.md index 0c6af971bf..fd497687d2 100644 --- a/documentation.md +++ b/documentation.md @@ -421,6 +421,12 @@ With that being said there are some amazing frameworks already out there and we ## Changelog +__0.3.0__ + +- Removed bad SocketIO configuration ([#19](https://github.com/feathersjs/feathers/issues/19)) +- Add .npmignore to not publish .idea folder ([#30](https://github.com/feathersjs/feathers/issues/30)) +- Remove middleware: connect.bodyParser() ([#27](https://github.com/feathersjs/feathers/pull/27)) + __0.2.0__ - Pre-initialize `req.feathers` in REST provider to set service parameters From a4141393591fb4b68810809eefa8312042eb6341 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Fri, 3 Jan 2014 19:40:58 -0700 Subject: [PATCH 08/18] Adding app.setup(server) and tests for SSL (#25). --- documentation.md | 4 +++ lib/application.js | 55 ++++++++++++++++++++-------------- lib/feathers.js | 3 +- lib/providers/socketio.js | 12 ++++---- test/application.test.js | 52 +++++++++++++++++++++++++++++++- test/resources/certificate.pem | 16 ++++++++++ test/resources/certrequest.csr | 12 ++++++++ test/resources/privatekey.pem | 15 ++++++++++ 8 files changed, 137 insertions(+), 32 deletions(-) create mode 100644 test/resources/certificate.pem create mode 100644 test/resources/certrequest.csr create mode 100644 test/resources/privatekey.pem diff --git a/documentation.md b/documentation.md index fd497687d2..9afa088cda 100644 --- a/documentation.md +++ b/documentation.md @@ -138,6 +138,10 @@ app.use('/todos', { }); ``` +### service + +`app.service([path], service)` is what is called internally by `app.use([path], service)` if a service object is being passed. Use it instead of `app.use([path], service)` if you want to be more explicit that you are registering a service. + ## Services A service can be any JavaScript object that offers one or more of the `find`, `get`, `create`, `update`, `remove` and `setup` service methods with the following signatures: diff --git a/lib/application.js b/lib/application.js index 60fa92faa5..5b87c54c21 100644 --- a/lib/application.js +++ b/lib/application.js @@ -18,6 +18,26 @@ module.exports = { }); }, + service: function(location, service) { + var protoService = Proto.extend(service); + var self = this; + + location = stripSlashes(location); + + // Add all the mixins + _.each(this.mixins, function (fn) { + fn.call(self, protoService); + }); + + // Run the provider functions to register the service + _.each(this.providers, function (provider) { + provider(location, protoService); + }); + + this.services[location] = protoService; + return this; + }, + use: function (location, service) { var hasServiceMethod = function (name) { return typeof service !== 'undefined' && typeof service[name] === 'function'; @@ -25,23 +45,7 @@ module.exports = { // Check for service (any object with at least one service method) if (_.some(this.methods, hasServiceMethod)) { - var protoService = Proto.extend(service); - var self = this; - - location = stripSlashes(location); - - // Add all the mixins - _.each(this.mixins, function (fn) { - fn.call(self, protoService); - }); - - // Run the provider functions to register the service - _.each(this.providers, function (provider) { - provider(location, protoService); - }); - - this.services[location] = protoService; - return this; + return this.service(location, service); } // Pass to the original express app @@ -52,15 +56,20 @@ module.exports = { return this.services[stripSlashes(location)]; }, - listen: function () { - var self = this; + setup: function() { // Setup each service (pass the app so that they can look up other services etc.) - _.each(self.services, function (service, path) { + _.each(this.services, function (service, path) { if (typeof service.setup === 'function') { - service.setup(self, path); + service.setup(this, path); } - }); + }.bind(this)); - return this._super.apply(this, arguments); + return this; + }, + + listen: function () { + var server = this._super.apply(this, arguments); + this.setup(server); + return server; } }; diff --git a/lib/feathers.js b/lib/feathers.js index 84134e24a0..c3d8bbef35 100644 --- a/lib/feathers.js +++ b/lib/feathers.js @@ -18,8 +18,7 @@ function createApplication() { Proto.mixin(Application, app); app.init(); // Add REST provider by default, can always be disabled using app.disable('feathers rest') - app.use(express.urlencoded()); - app.use(express.json()).configure(providers.rest()); + app.use(express.urlencoded()).use(express.json()).configure(providers.rest()); return app; } diff --git a/lib/providers/socketio.js b/lib/providers/socketio.js index 5e2ed542cd..e032c9178e 100644 --- a/lib/providers/socketio.js +++ b/lib/providers/socketio.js @@ -6,18 +6,18 @@ var socketio = require('socket.io'); module.exports = function (config) { return function () { var app = this; - var oldListen = app.listen; + var oldSetup = app.setup; var services = {}; app.enable('feathers socketio'); // Overwrite Expresss `listen` - app.listen = function () { - var httpServer = oldListen.apply(this, arguments); + app.setup = function (server) { + var oldResult = oldSetup.apply(this, arguments); if (app.disabled('feathers socketio')) { - return httpServer; + return oldResult; } - var io = this.io = socketio.listen(httpServer); + var io = this.io = socketio.listen(server); _.each(services, function (service, path) { // If the service emits events that we want to listen to (Event mixin) @@ -45,7 +45,7 @@ module.exports = function (config) { config.call(this, io); } - return httpServer; + return oldResult; }; app.providers.push(function (path, service) { diff --git a/test/application.test.js b/test/application.test.js index 53ebab07bb..c4f7669eb6 100644 --- a/test/application.test.js +++ b/test/application.test.js @@ -4,9 +4,10 @@ var assert = require('assert'); var Proto = require('uberproto'); var io = require('socket.io-client'); var request = require('request'); +var https = require('https'); +var fs = require('fs'); var feathers = require('../lib/feathers'); -var express = require('express'); describe('Feathers application', function () { it('registers service and looks it up with and without leading and trailing slashes', function () { @@ -85,4 +86,53 @@ describe('Feathers application', function () { }); }); }); + + it.only('REST and SocketIO with SSL server (#25)', function(done) { + // For more info on Reqest HTTPS settings see https://github.com/mikeal/request/issues/418 + // This needs to be set so that the SocektIO client can connect + process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + + var todoService = { + get: function (name, params, callback) { + callback(null, { + id: name, + description: "You have to do " + name + "!" + }); + } + }; + + var app = feathers().configure(feathers.socketio(function(io) { + io.set('log level', 0); + })).use('/secureTodos', todoService); + + var httpsServer = https.createServer({ + key: fs.readFileSync(__dirname + '/resources/privatekey.pem'), + cert: fs.readFileSync(__dirname + '/resources/certificate.pem'), + rejectUnauthorized: false, + requestCert: false + }, app).listen(7889); + + app.setup(httpsServer); + + httpsServer.on('listening', function() { + var socket = io.connect('https://localhost:7889', { secure: true, port: 7889 }); + + request({ + url: 'https://localhost:7889/secureTodos/dishes', + strictSSL: false, + rejectUnhauthorized : false + }, function (error, response, body) { + assert.ok(response.statusCode === 200, 'Got OK status code'); + var data = JSON.parse(body); + assert.equal(data.description, 'You have to do dishes!'); + + socket.emit('secureTodos::get', 'laundry', {}, function (error, data) { + assert.equal(data.description, 'You have to do laundry!'); + + socket.disconnect(); + httpsServer.close(done); + }); + }); + }); + }); }); diff --git a/test/resources/certificate.pem b/test/resources/certificate.pem new file mode 100644 index 0000000000..6735645071 --- /dev/null +++ b/test/resources/certificate.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICmzCCAgQCCQDugFqITnU/sDANBgkqhkiG9w0BAQUFADCBkTELMAkGA1UEBhMC +Q0ExEDAOBgNVBAgTB0FsYmVydGExEDAOBgNVBAcTB0NhbGdhcnkxETAPBgNVBAoT +CEZlYXRoZXJzMREwDwYDVQQLEwhGZWF0aGVyczETMBEGA1UEAxMKRmVhdGhlcnNK +UzEjMCEGCSqGSIb3DQEJARYUaGVsbG9AZmVhdGhlcnNqcy5jb20wHhcNMTQwMTA0 +MDIwNTUyWhcNMTQwMjAzMDIwNTUyWjCBkTELMAkGA1UEBhMCQ0ExEDAOBgNVBAgT +B0FsYmVydGExEDAOBgNVBAcTB0NhbGdhcnkxETAPBgNVBAoTCEZlYXRoZXJzMREw +DwYDVQQLEwhGZWF0aGVyczETMBEGA1UEAxMKRmVhdGhlcnNKUzEjMCEGCSqGSIb3 +DQEJARYUaGVsbG9AZmVhdGhlcnNqcy5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0A +MIGJAoGBALixfLwrvDDYAaaU62oycz8zwUpxCguyyXyhVDN/KMmP/I+HfkbcIrqj +tW0jbpRWiLhn5cw4K/cUTkfMj4AwaN5t2zq0FVFJdIddLxzuamyJLJFZfs5sPYWt +X6morPcu9RM7jwb3R1V852XjVWUj8neUAu7eUzKoSQ575kHsnKrdAgMBAAEwDQYJ +KoZIhvcNAQEFBQADgYEATVlxNPkSgkqBF4foUYNGnkvaiwhd88Mh/Ya3T3EnknF9 +Gz6KrlwWDDI8MkPmqabT2Ijg3LSec7WV+C8SETVFbWLOGV6N1ZVfodFzJ7EKMz5e +VvEIKnHfHpYOEa21E5u02+OfKahtW37eTEVmvcV67vYmW4HNa5QSZ5qfrrqcUhc= +-----END CERTIFICATE----- diff --git a/test/resources/certrequest.csr b/test/resources/certrequest.csr new file mode 100644 index 0000000000..41f83b2757 --- /dev/null +++ b/test/resources/certrequest.csr @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIB0jCCATsCAQAwgZExCzAJBgNVBAYTAkNBMRAwDgYDVQQIEwdBbGJlcnRhMRAw +DgYDVQQHEwdDYWxnYXJ5MREwDwYDVQQKEwhGZWF0aGVyczERMA8GA1UECxMIRmVh +dGhlcnMxEzARBgNVBAMTCkZlYXRoZXJzSlMxIzAhBgkqhkiG9w0BCQEWFGhlbGxv +QGZlYXRoZXJzanMuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4sXy8 +K7ww2AGmlOtqMnM/M8FKcQoLssl8oVQzfyjJj/yPh35G3CK6o7VtI26UVoi4Z+XM +OCv3FE5HzI+AMGjebds6tBVRSXSHXS8c7mpsiSyRWX7ObD2FrV+pqKz3LvUTO48G +90dVfOdl41VlI/J3lALu3lMyqEkOe+ZB7Jyq3QIDAQABoAAwDQYJKoZIhvcNAQEF +BQADgYEAFN1xm2Jc5EwDsiJwjUQkVCYLfAPz8FxLx8XCY7JugPCZWxeJ3w9C3Ymz +hET//7uxNg6q7EO9CI33vP5eOdI8oC8XQffh4GzCoSrmGrKpHSqVh3zN/rCoB4BY +f4nJofTka5iENjMdA0R8//Wp7F1u7xhriuxaRiZoFEPaCIsrvK4= +-----END CERTIFICATE REQUEST----- diff --git a/test/resources/privatekey.pem b/test/resources/privatekey.pem new file mode 100644 index 0000000000..3a39a23926 --- /dev/null +++ b/test/resources/privatekey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQC4sXy8K7ww2AGmlOtqMnM/M8FKcQoLssl8oVQzfyjJj/yPh35G +3CK6o7VtI26UVoi4Z+XMOCv3FE5HzI+AMGjebds6tBVRSXSHXS8c7mpsiSyRWX7O +bD2FrV+pqKz3LvUTO48G90dVfOdl41VlI/J3lALu3lMyqEkOe+ZB7Jyq3QIDAQAB +AoGAYCTkzf/mY3bOxSzYr9u7ardCc8IMfLKBeMNy1avoS6UM0Jqz/acy3P3DwCCl +u8qgOX68fWbwXBrR9UZjnVOWAvAgACS9bSTR4UxXuHve9YHf1s1Idm1Ck8CopiuY +0PTiuF7OJp6U7fc1RjO5F5tvSMuYbh+68Vpx9SQRfDHYqnECQQD1KnhSRDjLCfoB +lLfTew99W51OTx2NPRKRXwZH/YwlgRl/cAgJhdemah/AAavB6BUdqEXdiIviEHuT +UsfAXhf7AkEAwNrmEI3B4gtMRKJAsyWAKGFxDHuC9wGkhSxCVihQuxXtqEMX7Qnx +ucU9bRRtUgVPcOmFEtpPsI4e0wkTMg+ZBwJAPL+ERuYuqGjVcPTXw+g3Q1mjFddW +vDuI0UqZdNcnlddyaPhqlWl7sPmU2m/PjmGicdHTVfxSpPZumGenpUvrZwJAdodS +9QObEOmus1Qhfbljne3dhDV5FYTd77d3Aer/Syy8BzlNQDNnbKysBxmR4uI+o//x ++NdSOQnwKfYe5RqvCwJBAMfq911uzlD8Kd9s0n+MJe8b5/duYOtgPZvaIFWOWyNm +0aJE/VovVhk2JGvIU9kxdgt9O4N0x2XukS2hq7I1Xts= +-----END RSA PRIVATE KEY----- From 6a46c9b310f88ba93e23968cadcea38b8c012567 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Fri, 3 Jan 2014 19:59:33 -0700 Subject: [PATCH 09/18] Adding documentation for new methods. --- documentation.md | 46 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/documentation.md b/documentation.md index 9afa088cda..e628b3d757 100644 --- a/documentation.md +++ b/documentation.md @@ -78,19 +78,45 @@ Once the server has been started with `app.listen()` the SocketIO object is avai ### listen -`app.listen([port])` starts the application on the given port. Before calling the original [Express app.listen([port])](http://expressjs.com/api.html#app.listen) Feathers will initialize the SocketIO server (if set up) and call all services `setup(app, path)` methods in the order they have been registered. +`app.listen([port])` starts the application on the given port. It will first call the original [Express app.listen([port])](http://expressjs.com/api.html#app.listen), then run `app.setup(server)` (see below) with the server object and then return the server object. + +### setup + +`app.setup(server)` is used initialize all services by calling each services `.setup(app, path)` method (if available). +It will also use the `server` instance passed (e.g. through `http.createServer`) to set up SocketIO (if enabled) and any other provider that might require the server instance. + +Normally `app.setup` will be called automatically when starting the application via `app.listen([port])` but there are cases when you need to initialize the server separately: + +__HTTPS__ + +With your Feathers application initialized it is easy to set up an HTTPS REST and SocketIO server: ```js -var app = feathers(); -app.use('/my/todos', { - setup: function(app, path) { - // path -> 'my/todos' - } -}); +app.configure(feathers.socketio()).use('/todos', todoService); + +var https = require('https'); +var server = https.createServer({ + key: fs.readFileSync('privatekey.pem'), + cert: fs.readFileSync('certificate.pem') +}, app).listen(443); + +// Call app.setup to initialize all services and SocketIO +app.setup(server); +``` + +__Virtual Hosts__ + +You can use `feathers.vhost` (which is the same as [Express and Connect .vhost](http://www.senchalabs.org/connect/vhost.html)) to run your Feathers app on a virtual host: + +```js +app.use('/todos', todoService); -var server = app.listen(8080); +var host = feathers().use(feathers.vhost('foo.com', app)); +var server = host.listen(8080); -server.close(); +// Here we need to call app.setup because .listen on our virtal hosted +// app is never called +app.setup(server); ``` ### lookup @@ -140,7 +166,7 @@ app.use('/todos', { ### service -`app.service([path], service)` is what is called internally by `app.use([path], service)` if a service object is being passed. Use it instead of `app.use([path], service)` if you want to be more explicit that you are registering a service. +`app.service([path], service)` is what is called internally by `app.use([path], service)` if a service object is being passed. Use it instead of `app.use([path], service)` if you want to be more explicit that you are registering a service. `app.service` does __not__ provide the Express `app.use` functionality and doesn't check the service object for valid methods. ## Services From ae7f97efdf499a30e899acc01ff1ccaa8395611c Mon Sep 17 00:00:00 2001 From: David Luecke Date: Fri, 3 Jan 2014 20:01:34 -0700 Subject: [PATCH 10/18] Hide logs in tests, enable all application tests again. --- test/application.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/application.test.js b/test/application.test.js index c4f7669eb6..dccb7ff1ab 100644 --- a/test/application.test.js +++ b/test/application.test.js @@ -87,7 +87,7 @@ describe('Feathers application', function () { }); }); - it.only('REST and SocketIO with SSL server (#25)', function(done) { + it('REST and SocketIO with SSL server (#25)', function(done) { // For more info on Reqest HTTPS settings see https://github.com/mikeal/request/issues/418 // This needs to be set so that the SocektIO client can connect process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; @@ -101,6 +101,8 @@ describe('Feathers application', function () { } }; + var oldlog = console.log; + console.log = function () {}; var app = feathers().configure(feathers.socketio(function(io) { io.set('log level', 0); })).use('/secureTodos', todoService); @@ -117,6 +119,8 @@ describe('Feathers application', function () { httpsServer.on('listening', function() { var socket = io.connect('https://localhost:7889', { secure: true, port: 7889 }); + console.log = oldlog; + request({ url: 'https://localhost:7889/secureTodos/dishes', strictSSL: false, From a9ec0ecb35a701cb640f8a60b317c84e29a3d966 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Fri, 3 Jan 2014 20:14:19 -0700 Subject: [PATCH 11/18] Adding unused and undef to JSHint. --- .jshintrc | 2 ++ lib/mixins/event.js | 1 - test/application.test.js | 3 +-- test/providers/socketio.test.js | 2 -- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.jshintrc b/.jshintrc index 3dbb032182..8b243dadc4 100644 --- a/.jshintrc +++ b/.jshintrc @@ -6,5 +6,7 @@ "after": true, "exports": true }, + "unused": true, + "undef": true, "node": true } \ No newline at end of file diff --git a/lib/mixins/event.js b/lib/mixins/event.js index 1714159eef..eb1a066ddd 100644 --- a/lib/mixins/event.js +++ b/lib/mixins/event.js @@ -1,6 +1,5 @@ 'use strict'; -var Proto = require('uberproto'); var _ = require('underscore'); var rubberduck = require('rubberduck'); var EventEmitter = require('events').EventEmitter; diff --git a/test/application.test.js b/test/application.test.js index 53ebab07bb..aca88f055f 100644 --- a/test/application.test.js +++ b/test/application.test.js @@ -6,12 +6,11 @@ var io = require('socket.io-client'); var request = require('request'); var feathers = require('../lib/feathers'); -var express = require('express'); describe('Feathers application', function () { it('registers service and looks it up with and without leading and trailing slashes', function () { var dummyService = { - find: function (params, callback) { + find: function () { // No need to implement this } }; diff --git a/test/providers/socketio.test.js b/test/providers/socketio.test.js index 95a7080c72..c1c708f287 100644 --- a/test/providers/socketio.test.js +++ b/test/providers/socketio.test.js @@ -1,9 +1,7 @@ 'use strict'; -var assert = require('assert'); var feathers = require('../../lib/feathers'); var io = require('socket.io-client'); -var request = require('request'); var fixture = require('./service-fixture'); var todoService = fixture.Service; From 3a5ede5fbc320aa17863d75f4ed9ba29552670fd Mon Sep 17 00:00:00 2001 From: David Luecke Date: Sun, 5 Jan 2014 09:14:43 -0700 Subject: [PATCH 12/18] Slightly reorganizing SocketIO provider. --- lib/providers/socketio.js | 73 +++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/lib/providers/socketio.js b/lib/providers/socketio.js index e032c9178e..1b58258dca 100644 --- a/lib/providers/socketio.js +++ b/lib/providers/socketio.js @@ -2,53 +2,58 @@ var _ = require('underscore'); var socketio = require('socket.io'); +var Proto = require('uberproto'); -module.exports = function (config) { - return function () { +module.exports = function(config) { + return function() { var app = this; - var oldSetup = app.setup; var services = {}; app.enable('feathers socketio'); - // Overwrite Expresss `listen` - app.setup = function (server) { - var oldResult = oldSetup.apply(this, arguments); - if (app.disabled('feathers socketio')) { - return oldResult; - } - var io = this.io = socketio.listen(server); + // Monkey patch app.setup(server) + Proto.mixin({ + setup: function(server) { + var self = this; + var result = this._super.apply(this, arguments); - _.each(services, function (service, path) { - // If the service emits events that we want to listen to (Event mixin) - if (typeof service.on === 'function' && service._serviceEvents) { - _.each(service._serviceEvents, function (ev) { - service.on(ev, function (data) { - io.sockets.emit(path + ' ' + ev, data); - }); - }); + if (this.disabled('feathers socketio')) { + return result; } - }); - - io.sockets.on('connection', function (socket) { - _.each(services, function (service, path) { - _.each(app.methods, function (method) { - var name = path + '::' + method; - if (service[method]) { - socket.on(name, _.bind(service[method], service)); - } + + var io = this.io = socketio.listen(server); + + _.each(services, function(service, path) { + // If the service emits events that we want to listen to (Event mixin) + if (typeof service.on === 'function' && service._serviceEvents) { + _.each(service._serviceEvents, function(ev) { + service.on(ev, function(data) { + io.sockets.emit(path + ' ' + ev, data); + }); + }); + } + }); + + io.sockets.on('connection', function(socket) { + _.each(services, function(service, path) { + _.each(self.methods, function(method) { + var name = path + '::' + method; + if (service[method]) { + socket.on(name, service[method].bind(service)); + } + }); }); }); - }); - if(typeof config === 'function') { - config.call(this, io); - } + if (typeof config === 'function') { + config.call(this, io); + } - return oldResult; - }; + return result; + } + }, app); - app.providers.push(function (path, service) { + app.providers.push(function(path, service) { services[path] = service; }); }; From 44da7721f90a79c9cc5a1b6bd7c9269f7f23a880 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Sun, 5 Jan 2014 10:13:51 -0700 Subject: [PATCH 13/18] Primus provider (#28). --- lib/providers/index.js | 3 +- lib/providers/primus.js | 64 +++++++++++++++++++ package.json | 4 +- test/providers/primus.test.js | 113 ++++++++++++++++++++++++++++++++++ 4 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 lib/providers/primus.js create mode 100644 test/providers/primus.test.js diff --git a/lib/providers/index.js b/lib/providers/index.js index 278897d473..8b8e267ac7 100644 --- a/lib/providers/index.js +++ b/lib/providers/index.js @@ -2,5 +2,6 @@ module.exports = { rest: require('./rest'), - socketio: require('./socketio') + socketio: require('./socketio'), + primus: require('./primus') }; diff --git a/lib/providers/primus.js b/lib/providers/primus.js new file mode 100644 index 0000000000..1db4ade9e6 --- /dev/null +++ b/lib/providers/primus.js @@ -0,0 +1,64 @@ +'use strict'; + +var _ = require('underscore'); +var Proto = require('uberproto'); +var Primus = require('primus'); +var Emitter = require('primus-emitter'); + +module.exports = function(config, configurer) { + return function() { + var app = this; + var services = {}; + + app.enable('feathers primus'); + + // Monkey patch app.setup(server) + Proto.mixin({ + setup: function(server) { + var self = this; + var result = this._super.apply(this, arguments); + + if (this.disabled('feathers primus')) { + return result; + } + + var primus = this.primus = new Primus(server, config); + primus.use('emitter', Emitter); + + _.each(services, function(service, path) { + // If the service emits events that we want to listen to (Event mixin) + if (typeof service.on === 'function' && service._serviceEvents) { + _.each(service._serviceEvents, function(ev) { + service.on(ev, function(data) { + primus.forEach(function (spark) { + spark.send(path + ' ' + ev, data); + }); + }); + }); + } + }); + + primus.on('connection', function(spark) { + _.each(services, function(service, path) { + _.each(self.methods, function(method) { + var name = path + '::' + method; + if (service[method]) { + spark.on(name, service[method].bind(service)); + } + }); + }); + }); + + if (typeof configurer === 'function') { + configurer.call(this, primus); + } + + return result; + } + }, app); + + app.providers.push(function(path, service) { + services[path] = service; + }); + }; +}; diff --git a/package.json b/package.json index 899c4618b9..afab6a3629 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,9 @@ "express": "~ 3.4.0", "rubberduck": "~1.x", "underscore": "~1.5.0", - "socket.io": "~0.9.0" + "socket.io": "~0.9.0", + "primus-emitter": "~2.0.5", + "primus": "~1.5.1" }, "devDependencies": { "request": "~2.21.0", diff --git a/test/providers/primus.test.js b/test/providers/primus.test.js new file mode 100644 index 0000000000..457656e979 --- /dev/null +++ b/test/providers/primus.test.js @@ -0,0 +1,113 @@ +'use strict'; + +var feathers = require('../../lib/feathers'); +var fixture = require('./service-fixture'); +var todoService = fixture.Service; +var verify = fixture.verify; + +describe('Primus provider', function () { + var server, socket; + + before(function () { + server = feathers() + .configure(feathers.primus({ + transformer: 'socket.io' + }, function(primus) { + socket = new primus.Socket('http://localhost:7888'); + })) + .use('todo', todoService) + .listen(7888); + }); + + after(function (done) { + socket.socket.disconnect(); + server.close(done); + }); + + describe('CRUD', function () { + it('::find', function (done) { + socket.send('todo::find', {}, function (error, data) { + verify.find(data); + + done(error); + }); + }); + + it('::get', function (done) { + socket.send('todo::get', 'laundry', {}, function (error, data) { + verify.get('laundry', data); + + done(error); + }); + }); + + it('::create', function (done) { + var original = { + name: 'creating' + }; + + socket.send('todo::create', original, {}, function (error, data) { + verify.create(original, data); + + done(error); + }); + }); + + it('::update', function (done) { + var original = { + name: 'updating' + }; + + socket.send('todo::update', 23, original, {}, function (error, data) { + verify.update(23, original, data); + + done(error); + }); + }); + + it('::remove', function (done) { + socket.send('todo::remove', 11, {}, function (error, data) { + verify.remove(11, data); + + done(error); + }); + }); + }); + + describe('Events', function () { + it('created', function (done) { + var original = { + name: 'created event' + }; + + socket.on('todo created', function (data) { + verify.create(original, data); + done(); + }); + + socket.send('todo::create', original, {}, function () {}); + }); + + it('updated', function (done) { + var original = { + name: 'updated event' + }; + + socket.on('todo updated', function (data) { + verify.update(10, original, data); + done(); + }); + + socket.send('todo::update', 10, original, {}, function () {}); + }); + + it('removed', function (done) { + socket.on('todo removed', function (data) { + verify.remove(333, data); + done(); + }); + + socket.send('todo::remove', 333, {}, function () {}); + }); + }); +}); From ac8f7084272d7bc03e61745281cf66b6b0fb54fb Mon Sep 17 00:00:00 2001 From: David Luecke Date: Sun, 5 Jan 2014 11:21:05 -0700 Subject: [PATCH 14/18] Adding documentation to use Primus. --- documentation.md | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/documentation.md b/documentation.md index e628b3d757..9a98698cc9 100644 --- a/documentation.md +++ b/documentation.md @@ -74,6 +74,47 @@ app.configure(feathers.socketio(function(io) { Once the server has been started with `app.listen()` the SocketIO object is available as `app.io`. +### Primus + +[Primus](https://github.com/primus/primus) is a universal wrapper for real-time frameworks and allows you to transparently use Engine.IO, WebSockets, BrowserChannel, SockJS and Socket.IO. Set it up with `feathers.primus(configuration [, fn])` where `configuration` is the [Primus server configuration](https://github.com/primus/primus#getting-started) and `fn` an optional callback with the Primus server instance that can e.g. be used for setting up [authorization](https://github.com/primus/primus#authorization): + +```js +// Set up Primus with SockJS +app.configure(feathers.primus({ + transformer: 'sockjs' +}, function(primus) { + // Set up Primus authorization here + primus.authorize(function (req, done) { + var auth; + + try { auth = authParser(req.headers['authorization']) } + catch (ex) { return done(ex) } + + // Do some async auth check + authCheck(auth, done); + }); +})); +``` + +In the Browser you can connect like this: + +```html + + +``` + ## API ### listen From 7902526209957c8903fe4eef9b4cb169b0c17f4c Mon Sep 17 00:00:00 2001 From: David Luecke Date: Sun, 5 Jan 2014 11:53:34 -0700 Subject: [PATCH 15/18] Adding Node 0.11 to Travis. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2ca91f2895..83124bc848 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: node_js node_js: - "0.10" - - "0.8" \ No newline at end of file + - "0.8" + - "0.11" \ No newline at end of file From 44b633ffec36b8a9297d7c784f30bcef47696a23 Mon Sep 17 00:00:00 2001 From: David Luecke Date: Sun, 5 Jan 2014 11:55:45 -0700 Subject: [PATCH 16/18] Updating documentation for version 0.3.0 --- documentation.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentation.md b/documentation.md index 9a98698cc9..c3b728ca43 100644 --- a/documentation.md +++ b/documentation.md @@ -494,6 +494,8 @@ With that being said there are some amazing frameworks already out there and we __0.3.0__ +- Added [Primus](https://github.com/primus/primus) provider ([#34](https://github.com/feathersjs/feathers/pull/34)) +- `app.setup(server)` to support HTTPS (and other functionality that requires a custom server) ([#33](https://github.com/feathersjs/feathers/pull/33)) - Removed bad SocketIO configuration ([#19](https://github.com/feathersjs/feathers/issues/19)) - Add .npmignore to not publish .idea folder ([#30](https://github.com/feathersjs/feathers/issues/30)) - Remove middleware: connect.bodyParser() ([#27](https://github.com/feathersjs/feathers/pull/27)) From 28d32c338ad085766a0f1366fd3fe2c8ec5576bf Mon Sep 17 00:00:00 2001 From: David Luecke Date: Sun, 5 Jan 2014 11:59:45 -0700 Subject: [PATCH 17/18] Removing 0.11. Primus throws an error. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 83124bc848..43be7c3197 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,4 @@ language: node_js node_js: - "0.10" - "0.8" - - "0.11" \ No newline at end of file + # - "0.11" \ No newline at end of file From 7d94376d41e92dc83973c534ca87c238cd6353fb Mon Sep 17 00:00:00 2001 From: David Luecke Date: Sun, 5 Jan 2014 22:12:38 -0700 Subject: [PATCH 18/18] release 0.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index afab6a3629..fa0a0c5132 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "feathers", "description": "An ultra scalable, feather weight, data oriented framework", - "version": "0.2.0", + "version": "0.3.0", "homepage": "http://feathersjs.com", "repository": { "type": "git",