Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 39 additions & 9 deletions documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
55 changes: 32 additions & 23 deletions lib/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,34 @@ 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';
};

// 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
Expand All @@ -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;
}
};
3 changes: 1 addition & 2 deletions lib/feathers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
12 changes: 6 additions & 6 deletions lib/providers/socketio.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -45,7 +45,7 @@ module.exports = function (config) {
config.call(this, io);
}

return httpServer;
return oldResult;
};

app.providers.push(function (path, service) {
Expand Down
56 changes: 55 additions & 1 deletion test/application.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down Expand Up @@ -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);
});
});
});
});
});
16 changes: 16 additions & 0 deletions test/resources/certificate.pem
Original file line number Diff line number Diff line change
@@ -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-----
12 changes: 12 additions & 0 deletions test/resources/certrequest.csr
Original file line number Diff line number Diff line change
@@ -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-----
15 changes: 15 additions & 0 deletions test/resources/privatekey.pem
Original file line number Diff line number Diff line change
@@ -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-----