Skip to content

Commit

Permalink
Add basic implementation of node-compatible http client
Browse files Browse the repository at this point in the history
  • Loading branch information
alechenninger committed Apr 19, 2015
1 parent bf1b4ba commit 974ea7f
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 44 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

A [lightblue](https://github.com/lightblue-platform) client written in Javascript.

Conceivably one day useful for:
Write...
- Node.JS apps talking to a Lightblue REST service
- Client side apps communicating with a server that forwards requests to a Lightblue REST service

At the moment this is really just a rough sketch of an idea and will change drastically.

# Install

`bower install lightblue.js --save`
Expand All @@ -18,18 +16,20 @@ At the moment this is really just a rough sketch of an idea and will change dras

# Usage

Use browserify `require` or commonjs `define`, or just include dist/lightblue.min.js and use the namespace `lightblue`.
Use browserify `require` or requirejs `define`, or just include dist/lightblue.min.js and use the namespace `lightblue`.

For frontend angular applications, use the `lightblue` module (TODO: document).

## Imports:

```javascript
// Plain old HTML
// No module framework (use window.lightblue)
<script src="lightblue.min.js"></script>

// NodeJS or Browserify
// NodeJS or Browserify or CommonJS
var lightblue = require("./lightblue.min.js");

// CommonJS and RequireJS work too but I don't have an example
// RequireJS works too but I don't have an example
```

## Construct a find request:
Expand Down
43 changes: 39 additions & 4 deletions lib/clientutil.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,47 @@ exports.resolve = function() {
.filter(notEmpty)
.map(trimSlashes)
.join("/");
}
};

exports.isEmpty = function(s) {
return typeof s === "undefined" || s === "";
};

function ifDefined(it, otherwise) {
return isDefined(it) ? it : otherwise;
}

function isDefined(it) {
return typeof it !== "undefined";
}

function isObject(it) {
return typeof it === "object";
}

exports.ifDefined = function(it, otherwise) {
return (typeof it !== "undefined") ? it : otherwise;
}
var assertArg = {
isInstance: function(arg, type, name) {
if (!(arg instanceof type)) {
throw new Error("Expected instanceof " + type + " but " + name + " was "
+ isObject(arg) ? arg.constructor : "undefined");
}

return arg;
},
isNotBlankString: function(arg, name) {
if (typeof arg !== "string") {
throw new Error("Expected a string but " + name + " was " + typeof arg);
}

if (arg.trim().length === 0) {
throw new Error("Expected non-blank string but " + name + " was empty.");
}

return arg;
}
};

exports.assertArg = assertArg;
exports.ifDefined = ifDefined;
exports.isDefined = isDefined;
exports.isObject = isObject;
24 changes: 16 additions & 8 deletions lib/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,43 @@ module.exports = LightblueDataClient;
* deployed under a particular context (like /rest/data), this must be
* included in the host.
*/
function LightblueDataClient(host) {
this.host = host;
function LightblueDataClient(httpClient, host) {
this._httpClient = httpClient;
this._host = host;
}

LightblueDataClient.prototype._execute = function(request) {
return this._httpClient.execute(request);
}

LightblueDataClient.prototype.find = function(config) {
return this._execute(new FindRequest(this.host, config));
return this._execute(new FindRequest(this._host, config));
};

LightblueDataClient.prototype.insert = function(config) {
return new InsertRequest(this.host, config);
return new InsertRequest(this._host, config);
};

LightblueDataClient.prototype.update = function(config) {
return new UpdateRequest(this.host, config);
return new UpdateRequest(this._host, config);
};

LightblueDataClient.prototype.save = function(config) {
return new SaveRequest(this.host, config);
return new SaveRequest(this._host, config);
};

LightblueDataClient.prototype.delete = function(config) {
return new DeleteRequest(this.host, config);
return new DeleteRequest(this._host, config);
};

/**
* @param {Object} config The config object for the request.
*/
function FindRequest(host, config) {
var url = resolve(host, "find", config.entity, config.version);
var url = new Url(host, {
path: resolve("find", config.entity, config.version)
});

var body = {
objectType: config.entity,
version: config.version,
Expand Down
40 changes: 28 additions & 12 deletions lib/http.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
var assertArg = require("./clientutil.js").assertArg;

/**
* @constructor
* @param {String} method Http method, case insensitive.
* @param {Url} url
* @param {String=} body The request body.
*/
exports.HttpRequest = function(method, url, body) {
this.method = (typeof method === "string") ? method.toLowerCase() : "";
this.method = assertArg.isNotBlankString(method, "method");
this.body = (typeof body === "string") ? body : "";
this.url = assertArg.isInstance(url, Url, "url");

// derived properties
this.METHOD = this.method.toUpperCase();
this.body = body;
this.url = url;
this.href = url.toString();
}
};

/**
* @constructor
Expand All @@ -22,14 +26,26 @@ exports.HttpRequest = function(method, url, body) {
exports.Url = Url;

function Url(host, options) {
this.host = host;

if (options instanceof Object) {
this.port = options.port || 80;
this.path = options.path || "/";
}
if (!/https?:\/\/[^\s]+$/.test(host)) {
throw new Error("Invalid url host syntax: " + host);
}

this.host = host;

var port, path;

if (options instanceof Object) {
port = options.port || 80;
path = options.path || "/";
} else {
port = 80;
path = "/";
}

this.port = port;
this.path = path;
}

Url.prototype.toString = function() {
return this.host + ":" + this.port + this.path;
}
return this.host + ":" + this.port + this.path;
};
7 changes: 5 additions & 2 deletions lib/metadata.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
var util = require("./clientutil");
var RestRequest = require("./rest").RestRequest;
var resolve = require("./clientutil").resolve;
var http = require("./http");

var HttpRequest = http.HttpRequest;
var Url = http.Url;

module.exports = LightblueMetadataClient;

Expand Down
76 changes: 65 additions & 11 deletions lib/node_http.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,80 @@ function NodeHttpClient(options) {
this._execSsl = https.request;

if (options instanceof Object) {
this._auth = ifDefined(options.auth, null);
//this.
this._auth = ifDefined(options.auth, undefined);
// TODO: Support ssl certs
}
}

/**
* @param {RestRequest} req
* @param {HttpRequest} requestOpts
*/
NodeHttpClient.prototype.execute = function(req) {
NodeHttpClient.prototype.execute = function(requestOpts) {
var deferred = q.defer();
var promise = deferred.promise;

// TODO: Lookup how angular does this for reference
return {
success: function(callback) {

var exec;
var hostname;

if (requestOpts.href.indexOf("https") === 0) {
hostname = requestOpts.url.host.substr("https://".length);
exec = this._execSsl;
} else {
hostname = requestOpts.url.host.substr("http://".length);
exec = this._exec;
}

var options = {
hostname: hostname,
port: requestOpts.url.port,
path: requestOpts.url.path,
method: requestOpts.METHOD,
headers: {
'Content-Type': 'application/json',
'Content-Length': requestOpts.body.length
},
error: function(callback) {

}
// TODO: Does this work?
auth: this._auth
};

var request = exec(options, function(res) {
var responseBody = "";

res.setEncoding("utf8");

res.on('data', function(chunk) {
responseBody += chunk;
});

res.on('end', function() {
// TODO: resolve with more than just body?
// TODO: error on lightblue error?
deferred.resolve(responseBody);
});

res.on('error', function(error) {
// TODO: what is this resolving with?
deferred.reject(error);
});
});

request.write(requestOpts.body, 'utf8');
request.end();

request.on('error', function(error) {
// TODO: reject with something consistent
deferred.reject(error);
});

promise.success = function(callback) {
return promise.then(callback);
};

promise.error = function(callback) {
return promise.then(null, callback);
};

return promise;
};

function ifDefined(it, otherwise) {
Expand Down

0 comments on commit 974ea7f

Please sign in to comment.