Skip to content

Commit

Permalink
Working commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
hgarcia committed Dec 31, 2012
1 parent 7224857 commit 8ba0c42
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 102 deletions.
80 changes: 79 additions & 1 deletion README.md
@@ -1,4 +1,82 @@
curling
=======

A node wrapper for curl with a very simple api.
A node wrapper for curl with a very simple api.

## API

Exports only two methods `connect` and `run`

### run(command, cb)

You shouldn't have to use `run` but is in there just as a convenience or if you need to do something crazy that is not possible to do via the connection object.

It pretty much allow you to send any command with any option to curl. It used internally by connect and the connection object.

Ex:

var curl = require('curling');
curl.run("--GET http://www.cnn.com", function (err, result) {
console.log(result.payload); // should output the html for the cnn page to console.
console.log(result.stats); // should output some of the statistics on downloading the page
});

### connect(options)

This method takes an `options` object with general options for the connection like the username and password in case you are using basic auth to connect to an end point.

var options = {username: "hernan", password: "secret"};

At the moment those are the only options that connect knows how to deal with.

It returns a connection object.

### Connection object API

It has five methods, each corresponding to an HTTP verb. They all have the same signature: `method(url, options, cb)`. The method are:

head();
get();
post();
put();
del(); //DELETE

The callback takes two parameters `cb(err, result)` where the result is a `curl-result` object.


### curl-result

It has two properties, `payload` and `stats`. The payload contains the data returned in the stdout by curl while the stats is an object that parse as the content of stderr.

Stats is of the form:

{
totalSize: 0,
received: 0,
xferd: 0,
averageDownloadSpeed: 0,
averageUploadSpeed: 0,
totalTime: 0,
timeSpent: 0,
timeLeft: 0,
currentSpeed: '0 Kb'
}

The time properties are converted to milliseconds, the rest of the properties are of type `Number` in the same units as returned by curl except for the `currentSpeed` that is a string with the unit at the end (again as returned by curl).

## Passing options.

There are two ways to pass options and data to a request.
You can use the `options` for the `connect` method and this options will be used in each and every request.
You can also use the `options` object in each of the verb methods.

The `options` object is actually a hash where the keys should be the name of the flag in a curl command, for example to set an Accept header and pass some data you could pass an `options` as the following.

var options = {
header: "Accept: text/html",
data: ["name=hernan", "last=garcia"]
};

The keys in an `options` object can be one of the following types, String, Array or null.

Strings are useful when you only need to set a single value, arrays are used to pass multiple values, like data, header and so. Null is a specialcase and is used for empty flags, like `--false`.
1 change: 1 addition & 0 deletions index.js
@@ -1 +1,2 @@
exports.connect = require('./lib/curl-transport').connect;
exports.run = require('./lib/curl-transport').run;
134 changes: 88 additions & 46 deletions lib/curl-transport.js
@@ -1,77 +1,119 @@
"use strict";
var exec = require('child_process').exec;
var qs = require('querystring');

exports.connect = function (connOptions) {
function run(command, cb) {
exec(command, function (error, stdout, stderr) {
try {
if (connOptions.json) {
cb(null, JSON.parse(stdout));
} else {
cb(null, stdout);
}
} catch (e) {
cb(e, null);
}
})
.on('error', function (err) {
cb(err, null);
});
function parseStats(stderr) {
var lines = [];
var items = [];
var stats = {};
var propMap = [
{index: 1, name: 'totalSize', val: 0},
{index: 3, name: 'received', val: 0},
{index: 5, name: 'xferd', val: 0},
{index: 6, name: 'averageDownloadSpeed', val: 0},
{index: 7, name: 'averageUploadSpeed', val: 0},
{index: 8, name: 'totalTime', val: "--:--:--"},
{index: 9, name: 'timeSpent', val: "--:--:--"},
{index: 10, name: 'timeLeft', val: "--:--:--"},
{index: 11, name: 'currentSpeed', val: 0}
];

if (stderr) {
try {
lines = stderr.split("\r");
items = lines[2].replace("\n", "").split(" ").filter(function (item) { return item; });
} catch (e) {}
}

function getCredentials(connOptions) {
if (connOptions.username && connOptions.password) {
return " --user " + connOptions.username + ":" + connOptions.password;
function getValue(prop) {
if (items.length > prop.index) {
return items[prop.index];
}
return "";
return prop.val;
}

function getData(options) {
if (options.data) {
if (options.data.toLowerCase) {
return " --data \"" + options.data + "\"";
} else {
return " --data '" + JSON.stringify(options.data) + "'";
}
propMap.forEach(function (prop) {
stats[prop.name] = getValue(prop);
});

return stats;
}


exports.run = function (command, cb) {
exec("curl " + command, function (error, stdout, stderr) {
cb(null, {payload: stdout, stats: parseStats(stderr)});
})
.on('error', function (err) {
cb(err, null);
});
};

exports.connect = function (connOptions) {

var ctx = this;

function getEmptyOption(option) {
if (option.length === 1) {
return " -" + option;
}
return "";
return " --" + option;
}

function getHeaders(options) {
if (options.headers) {
var headers = "";
Object.keys(options.headers).forEach(function (key) {
headers += " -H " + key + ":\"" + options.headers[key] + "\"";
function getStringOption(option, value) {
return getEmptyOption(option) + " \"" + value + "\"";
}

function processOptions(options) {
var tmp = "";
if (options) {
Object.keys(options).forEach(function (option) {
var values = options[option];
if (!values) {
tmp += getEmptyOption(option)
} else if (values.toLowerCase) {
tmp += getStringOption(option, values);
} else if (Array.isArray(values)) {
values.forEach(function (value) {
tmp += getStringOption(option, value);
});
}
});
return headers;
}
return "";
return tmp;
}

function getUrlForCommand(url, connOptions) {
return getCredentials(connOptions) + " " + url;
function getOptions(options) {
return processOptions(connOptions) + processOptions(options);
}

function getCommand(url, options) {
return url + getOptions(options);
}

return {
head: function (url, options, cb) {
var command = "--HEAD " + getCommand(url, options);
ctx.run(command, cb);
},

get: function (url, options, cb) {
var command = "curl --GET" + getUrlForCommand(url, connOptions) + getHeaders(options) + getData(options);
run(command, cb);
var command = "--GET " + getCommand(url, options);
ctx.run(command, cb);
},

post: function (url, options, cb) {
var command = "curl" + getUrlForCommand(url, connOptions) + getHeaders(options) + getData(options);
run(command, cb);
var command = getCommand(url, options);
ctx.run(command, cb);
},

put: function (url, options, cb) {
var command = "curl --request PUT" + getUrlForCommand(url, connOptions) + getHeaders(options) + getData(options);
run(command, cb);
var command = "--request PUT " + getCommand(url, options);
ctx.run(command, cb);
},

del: function (url, options, cb) {
var command = "curl --include --request DELETE" + getUrlForCommand(url, connOptions) + getHeaders(options);
run(command, cb, true);
var command = "--include --request DELETE" + getCommand(url, options);
ctx.run(command, cb, true);
}
};
};
7 changes: 4 additions & 3 deletions package.json
Expand Up @@ -4,15 +4,16 @@
"description": "A simple wrapper around curl with a easy to use interface.",
"main": "index.js",
"scripts": {
"test": "mocha tests/*.tests.js"
},
"test": "mocha tests/*.tests.js -t 15000 -R spec"
},
"repository": {
"type": "git",
"url": "git://github.com/hgarcia/curling.git"
},
"devDependencies": {
"mocha": "1.7.4",
"should": "1.2.1"
"should": "1.2.1",
"sinon": "1.5.2"
},
"keywords": [
"curl",
Expand Down

0 comments on commit 8ba0c42

Please sign in to comment.