diff --git a/documentation.md b/documentation.md index fd497687d2..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 server = app.listen(8080); +var https = require('https'); +var server = https.createServer({ + key: fs.readFileSync('privatekey.pem'), + cert: fs.readFileSync('certificate.pem') +}, app).listen(443); -server.close(); +// 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 host = feathers().use(feathers.vhost('foo.com', app)); +var server = host.listen(8080); + +// Here we need to call app.setup because .listen on our virtal hosted +// app is never called +app.setup(server); ``` ### lookup @@ -138,6 +164,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. `app.service` does __not__ provide the Express `app.use` functionality and doesn't check the service object for valid methods. + ## 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..dccb7ff1ab 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,57 @@ describe('Feathers application', function () { }); }); }); + + 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"; + + var todoService = { + get: function (name, params, callback) { + callback(null, { + id: name, + description: "You have to do " + name + "!" + }); + } + }; + + var oldlog = console.log; + console.log = function () {}; + 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 }); + + console.log = oldlog; + + 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-----