Skip to content

Commit

Permalink
A cleaner way of supporting multiple API hosts
Browse files Browse the repository at this point in the history
  • Loading branch information
spikebrehm committed May 27, 2013
1 parent 2057e92 commit 4d5e611
Show file tree
Hide file tree
Showing 14 changed files with 132 additions and 46 deletions.
37 changes: 31 additions & 6 deletions server/middleware/apiProxy.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
var server, utils, _, getApiHost;
var server, utils, _;

_ = require('underscore');
utils = require('../utils');
server = require('../server');
getApiHost = require('../utils').getApiHost;

/*
/**
* The separator used in the path. Incoming paths can look like:
* /-/path/to/resource
* /api-name/-/path/to/resource
*/
var separator = '/-/';

/**
* Middleware handler for intercepting API routes.
*/
module.exports = function(apiHostsMap) {
module.exports = apiProxy;

function apiProxy(apiHostsMap) {
return function(req, res, next) {
var api;

api = _.pick(req, 'path', 'query', 'method', 'body');
api.apiHost = getApiHost(api.path, apiHostsMap);
api = _.pick(req, 'query', 'method', 'body');

api.path = apiProxy.getApiPath(req.path);
api.api = apiProxy.getApiName(req.path);

server.dataAdapter.request(req, api, {
convertErrorCode: false
Expand All @@ -31,3 +41,18 @@ module.exports = function(apiHostsMap) {
});
};
};

apiProxy.getApiPath = function getApiPath(path) {
var sepIndex = path.indexOf(separator),
substrIndex = sepIndex === -1 ? 0 : sepIndex + separator.length - 1;
return path.substr(substrIndex);
};

apiProxy.getApiName = function getApiName(path) {
var sepIndex = path.indexOf(separator),
apiName = null;
if (sepIndex > 0) {
apiName = path.substr(1, sepIndex - 1);
}
return apiName;
};
20 changes: 1 addition & 19 deletions server/utils.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
var utils, _, fs, Backbone;
var utils, _;

_ = require('underscore');
Backbone = require('backbone');
fs = require('fs');

utils = module.exports = {};

Expand All @@ -18,19 +16,3 @@ utils.isErrorStatus = function(statusCode, options) {
return statusCode >= 400 && statusCode < 600;
}
};

utils.getApiHost = function(path, apiHostsMap) {
var extractParamNamesRe = /:(\w+)/g,
apiHost = null,
r;

_.each( apiHostsMap, function(urls, host) {
_.each(urls, function(url) {
url = url.substring(0, url.indexOf('?')) || url,
r = Backbone.Router.prototype._routeToRegExp(url);
if (r.exec(path)){ return (apiHost = host); }
});
});

return apiHost;
};
2 changes: 0 additions & 2 deletions shared/base/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@ module.exports = Super.extend({

getUrl: syncer.getUrl,

getApiHost: syncer.getApiHost,

/*
* Instance method to store the collection and its models.
*/
Expand Down
2 changes: 0 additions & 2 deletions shared/base/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ module.exports = Super.extend({

getUrl: syncer.getUrl,

getApiHost: syncer.getApiHost,

/*
* Instance method to store in the modelStore.
*/
Expand Down
16 changes: 10 additions & 6 deletions shared/syncer.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,18 @@ function clientSync(method, model, options) {
}

function serverSync(method, model, options) {
var api, apiHost, data, urlParts, verb;
var api, data, urlParts, verb;

data = _.clone(options.data);
options.url = this.getUrl(options.url, false, data);
verb = methodMap[method];
urlParts = options.url.split('?');
apiHost = this.getApiHost(options.apiHost);

api = {
method: verb,
path: urlParts[0],
query: qs.parse(urlParts[1]) || {},
apiHost: apiHost,
api: _.result(this, 'api'),
body: {}
};

Expand Down Expand Up @@ -118,13 +117,18 @@ syncer.getUrl = function getUrl(url, clientPrefix, params) {
params = params || {};
url = url || _.result(this, 'url');
if (clientPrefix && !~url.indexOf('://')) {
url = "/api" + url;
url = syncer.formatClientUrl(url, _.result(this, 'api'));
}
return syncer.interpolateParams(this, url, params);
};

syncer.getApiHost = function getApiHost(apiHost) {
return apiHost || _.result(this, 'apiHost');
syncer.formatClientUrl = function(url, api) {
var prefix = '/api';
if (api) {
prefix += '/' + api;
}
prefix += '/-';
return prefix + url;
};

/*
Expand Down
Empty file.
Empty file.
Empty file.
3 changes: 0 additions & 3 deletions test/fixtures/walkableDir/readme.md

This file was deleted.

Empty file removed test/fixtures/walkableDir/wd_1.js
Empty file.
Empty file removed test/fixtures/walkableDir/wd_2.js
Empty file.
35 changes: 35 additions & 0 deletions test/server/middleware/apiProxy.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
var _ = require('underscore'),
apiProxy = require('./../../../server/middleware/apiProxy'),
should = require('should');

describe('apiProxy', function() {

describe('getApiPath', function() {
it('should support no separator', function() {
should.equal(apiProxy.getApiPath("/some/path/to/resource"), "/some/path/to/resource");
});

it('should support a separator but no api name', function() {
should.equal(apiProxy.getApiPath("/-/path/to/resource"), "/path/to/resource");
});

it('should support a separator with api name', function() {
should.equal(apiProxy.getApiPath("/api-name/-/path/to/resource"), "/path/to/resource");
});
});

describe('getApiName', function() {
it('should support no separator', function() {
should.equal(apiProxy.getApiName("/some/path/to/resource"), null);
});

it('should support a separator but no api name', function() {
should.equal(apiProxy.getApiName("/-/path/to/resource"), null);
});

it('should support a separator with api name', function() {
should.equal(apiProxy.getApiName("/api-name/-/path/to/resource"), "api-name");
});
});

});
48 changes: 48 additions & 0 deletions test/shared/base/model.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,52 @@ describe('BaseModel', function() {
stored.should.eql(attrs);
});
});

describe('getUrl', function() {
it('should support string URL', function() {
var Model = this.MyModel.extend({
url: '/path/to/model/:id'
});
var model = new Model({id: 33}, {app: this.app});
model.getUrl().should.eql('/path/to/model/33');
});

it('should support function URL', function() {
var Model = this.MyModel.extend({
url: function() {
return '/path/to/model/:id';
}
});
var model = new Model({id: 33}, {app: this.app});
model.getUrl(null).should.eql('/path/to/model/33');
});

it('should support client-side URL', function() {
var Model = this.MyModel.extend({
url: '/path/to/model/:id'
});
var model = new Model({id: 33}, {app: this.app});
model.getUrl(null, true).should.eql('/api/-/path/to/model/33');
});

it('should support specifying an API as string', function() {
var Model = this.MyModel.extend({
url: '/path/to/model/:id',
api: 'api-name'
});
var model = new Model({id: 33}, {app: this.app});
model.getUrl(null, true).should.eql('/api/api-name/-/path/to/model/33');
});

it('should support specifying an API as function', function() {
var Model = this.MyModel.extend({
url: '/path/to/model/:id',
api: function() {
return 'api-name';
}
});
var model = new Model({id: 33}, {app: this.app});
model.getUrl(null, true).should.eql('/api/api-name/-/path/to/model/33');
});
});
});
15 changes: 7 additions & 8 deletions test/shared/syncer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,14 @@ describe('syncer', function() {
});
});

describe('getApiHost', function() {
it("should return the correct apiHost", function () {
this.model = new Backbone.Model({
apiHost: 'foo'
});
describe('formatClientUrl', function() {
it("should support default api", function() {
syncer.formatClientUrl('/path/to/resource').should.eql('/api/-/path/to/resource');
});

syncer.getApiHost.call(this.model.attributes).should.eql('foo');
syncer.getApiHost('bar').should.eql('bar');
it("should support specifying an api", function() {
syncer.formatClientUrl('/path/to/resource', 'api-name').should.eql('/api/api-name/-/path/to/resource');
});
});

});

0 comments on commit 4d5e611

Please sign in to comment.