use c++ instead of c++0x to compatible with old compiler

This commit is contained in:
jiangfriend@gmail.com 2012-02-14 21:10:45 +08:00
parent f8f934a119
commit 314d43203f
11 changed files with 454 additions and 106 deletions

102
README.md
View file

@ -1,36 +1,88 @@
Node Curl Wrap node-curl
Use select curl multi =========
node cURL wrapper, support all options and infos.
Quick Start Quick Start
=========== -----------
curl = require('node-curl') * quick start
curl('www.google.com', {VERBOSE: 1}, function(err, res) {
console.info(res.status) curl = require('node-curl');
console.info('-----') curl('www.google.com', function(err, res) {
console.info(res.body) console.info(res.status);
console.info('-----') console.info('-----');
console.info(res.info('SIZE_DOWNLOAD')) console.info(res.body);
}); console.info('-----');
console.info(res.info('SIZE_DOWNLOAD'));
res.close();
});
* with options
curl = require('node-curl')
curl('www.google.com', {VERBOSE: 1, RAW: 1}, function(err, res) {
console.info(res);
res.close();
});
Usage Usage
===== -----
Function: curl * curl
curl(url, [options = {}], callback)
callback includes 2 parameters (error, result)
Curl Options: curl(url, [options = {}], callback)
options is on the list at http://curl.haxx.se/libcurl/c/curl_easy_setopt.html without CURLOPT_ callback includes 2 parameters (error, result)
eg: CURLOPT_VERBOSE will be VERBOSE, CURLOPT_HEADER will be HEADER
Object: result * result in callback
body
status
info
Curl Infos: members:
infos is on the list at http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html without CURLINFO_ status - Http Response code
eg: CURLINFO_EFFECTIVE_URL will be EFFETCTIVE_URL body - Http body
methods:
info(name) - Get information of result, see 'info' section
Options
-------
* Any Curl Easy Options
eg: CURLOPT_VERBOSE will be VERBOSE, CURLOPT_HEADER will be HEADER
Full list at http://curl.haxx.se/libcurl/c/curl_easy_setopt.html
* node-curl Extra Options
RAW - Returns Buffer instead of String in result.body
* About slist parameters
node-curl support slist which map to Javascript Array
eg:
HTTP_HEADER: ['FOO', 'BAR']
HTTP_HEADER: 'FOO'
any non-array parameter will convert to [ parameter.toString() ]
Infos
-----
* Any Curl Info options
eg: CURLINFO_EFFECTIVE_URL will be EFFETCTIVE_URL
full list at http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html
* About slist
slist will be returns in Array
eg: CURLINFO_COOKIELIST
Hints
-----
close the result to release resource of curl immediately.
or the resource will not release until gc performed.

30
examples/pressure.js Normal file
View file

@ -0,0 +1,30 @@
// Generated by ToffeeScript 1.1.4-4
(function() {
var assert, curl, i, j, next, once, _i;
curl = require('../index');
assert = require('assert');
j = 0;
(next = function() {
console.info("curl instances: ", curl.get_count());
return setTimeout(next, 1000);
})();
for (i = _i = 1; _i <= 100; i = ++_i) {
(once = function() {
var _this = this;
return curl('localhost/test.html', function(_$$_err, _$$_res) {
var err, res;
err = _$$_err;
res = _$$_res;
assert.equal(res.body.length, 1468);
if (++j % 100 === 0) console.info(j);
if (j < 20000) return once();
});
})();
}
}).call(this);

16
examples/pressure.toffee Normal file
View file

@ -0,0 +1,16 @@
curl = require '../index'
assert = require 'assert'
j = 0;
do next = ->
console.info "curl instances: ", curl.get_count()
setTimeout next, 1000
for i in [1..100]
do once = ->
err, res = curl! 'localhost/test.html'
assert.equal res.body.length, 1468
if ++j % 100 == 0
console.info j
if j < 20000
once()
# res.close()

View file

@ -1,19 +1,27 @@
curl = require('../index'); curl = require('../index');
url = 'www.yahoo.com'; url = 'www.nodejs.org';
curl(url, function(err, res) { options = {CONNECTTIMEOUT: 2, VERBOSE: 1};
curl(url, options, function(err, res) {
console.info("\x1b[33meffetcive url: " + res.info('EFFECTIVE_URL') + "\x1b[0m");
console.info("body length: " + res.body.length); console.info("body length: " + res.body.length);
console.info('-----'); res.close();
console.info("status: " + res.status);
console.info('-----');
console.info("size download: " + res.info('SIZE_DOWNLOAD'));
console.info("\033[33meffetcive url: " + res.info('EFFECTIVE_URL') + "\033[0m");
}); });
curl(url, function(err, res) { url = 'www.yahoo.com'
curl(url, options, function(err, res) {
console.info("\x1b[33meffetcive url: " + res.info('EFFECTIVE_URL') + "\x1b[0m");
console.info("body length: " + res.body.length); console.info("body length: " + res.body.length);
console.info('-----'); res.close();
console.info("status: " + res.status);
console.info('-----');
console.info("size download: " + res.info('SIZE_DOWNLOAD'));
console.info("\033[33meffetcive url: " + res.info('EFFECTIVE_URL') + "\033[0m");
}); });
curl('www.google.com', {VERBOSE: 1, RAW: 1}, function(err, res) {
console.info(res);
res.close();
});
/*
console.info('-----');
console.info("status: " + res.status);
console.info('-----');
console.info("size download: " + res.info('SIZE_DOWNLOAD'));
*/

40
examples/test.js Normal file
View file

@ -0,0 +1,40 @@
// Generated by ToffeeScript 1.1.4-4
(function() {
var curl, fs, p, url, util,
_this = this;
curl = require('../index');
fs = require('fs');
util = require('util');
p = console.info;
url = 'www.google.com';
curl(url, {
HTTPHEADER: 'BAR',
VERBOSE: 1,
COOKIEFILE: 'node-curl-cookie.txt'
}, function(_$$_err, _$$_res) {
var err, res;
err = _$$_err;
res = _$$_res;
p("\x1b[33m" + util.inspect(res.info('COOKIELIST')) + "\x1b[0m");
url = 'www.yahoo.com';
return curl(url, {
HTTPHEADER: ['foo', 'bar'],
VERBOSE: 1,
COOKIEFILE: 'node-curl-cookie.txt',
RAW: 1
}, function(_$$_err, _$$_res) {
err = _$$_err;
res = _$$_res;
p("\x1b[33m" + util.inspect(res.info('COOKIELIST')) + "\x1b[0m");
p(res.body);
return fs.unlink('node-curl-cookie.txt');
});
});
}).call(this);

22
examples/test.toffee Normal file
View file

@ -0,0 +1,22 @@
curl = require '../index'
fs = require 'fs'
util = require 'util'
p = console.info
url = 'www.google.com'
err, res = curl! url,
HTTPHEADER: 'BAR'
VERBOSE: 1
COOKIEFILE: 'node-curl-cookie.txt'
p "\x1b[33m" + util.inspect(res.info('COOKIELIST')) + "\x1b[0m"
url = 'www.yahoo.com'
err, res = curl! url,
HTTPHEADER: ['foo', 'bar']
VERBOSE: 1
COOKIEFILE: 'node-curl-cookie.txt'
RAW: 1
p "\x1b[33m" + util.inspect(res.info('COOKIELIST')) + "\x1b[0m"
p res.body
fs.unlink 'node-curl-cookie.txt'

View file

@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.1.4-3 // Generated by ToffeeScript 1.1.4-4
(function() { (function() {
var Curl, curl, curl_id, var Curl, curl, curl_id, curls,
__slice = [].slice, __slice = [].slice,
__hasProp = {}.hasOwnProperty; __hasProp = {}.hasOwnProperty;
@ -10,10 +10,18 @@
Curl = require(__dirname + '/../build/default/node-curl').Curl; Curl = require(__dirname + '/../build/default/node-curl').Curl;
} }
Curl.prototype.setopt_user_ = function(option_id, value) {
return this.options[option_id] = value;
};
Curl.prototype.setopt = function(ooption, value) { Curl.prototype.setopt = function(ooption, value) {
var option, option_id; var option, option_id;
option = ooption.toUpperCase(); option = ooption.toUpperCase();
if ((option_id = Curl.integer_options[option]) != null) { if ((option_id = Curl.user_options[option]) != null) {
return this.setopt_user_(option_id, value);
} else if ((option_id = Curl.slist_options[option]) != null) {
return this.setopt_slist_(option_id, value);
} else if ((option_id = Curl.integer_options[option]) != null) {
return this.setopt_int_(option_id, value >> 0); return this.setopt_int_(option_id, value >> 0);
} else if ((option_id = Curl.string_options[option]) != null) { } else if ((option_id = Curl.string_options[option]) != null) {
return this.setopt_str_(option_id, value.toString()); return this.setopt_str_(option_id, value.toString());
@ -25,14 +33,16 @@
Curl.prototype.getinfo = function(oinfo) { Curl.prototype.getinfo = function(oinfo) {
var info, info_id; var info, info_id;
info = oinfo.toUpperCase(); info = oinfo.toUpperCase();
if ((info_id = Curl.integer_infos[info]) != null) { if ((info_id = Curl.slist_infos[info]) != null) {
return this.getinfo_slist_(info_id);
} else if ((info_id = Curl.integer_infos[info]) != null) {
return this.getinfo_int_(info_id); return this.getinfo_int_(info_id);
} else if ((info_id = Curl.string_infos[info]) != null) { } else if ((info_id = Curl.string_infos[info]) != null) {
return this.getinfo_str_(info_id); return this.getinfo_str_(info_id);
} else if ((info_id = Curl.double_infos[info]) != null) { } else if ((info_id = Curl.double_infos[info]) != null) {
return this.getinfo_double_(info_id); return this.getinfo_double_(info_id);
} else { } else {
throw new Error("unsupproted info " + oinfo); throw new Error("unsupported info " + oinfo);
} }
}; };
@ -41,6 +51,10 @@
return Curl.process(); return Curl.process();
}; };
Curl.user_options = {
RAW: 'RAW'
};
Curl.process = function() { Curl.process = function() {
var once; var once;
if (Curl.in_process) return; if (Curl.in_process) return;
@ -58,63 +72,84 @@
curl_id = 0; curl_id = 0;
curls = {};
curl = function() { curl = function() {
var args, c, cb, chunks, k, length, options, res, url, v; var args, c, cb, k, length, options, url, v;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
cb = args.pop(); cb = args.pop();
url = args[0], options = args[1]; url = args[0], options = args[1];
if (options == null) options = {}; if (options == null) options = {};
c = new Curl(); c = new Curl();
c.options = {};
c.id = ++curl_id; c.id = ++curl_id;
curls[c.id] = c;
c.setopt('FOLLOWLOCATION', 1); c.setopt('FOLLOWLOCATION', 1);
c.setopt('ACCEPT_ENCODING', 'gzip'); c.setopt('ACCEPT_ENCODING', 'gzip');
chunks = []; c.chunks = [];
length = 0; length = 0;
res = {};
for (k in options) { for (k in options) {
if (!__hasProp.call(options, k)) continue; if (!__hasProp.call(options, k)) continue;
v = options[k]; v = options[k];
c.setopt(k, v); c.setopt(k, v);
} }
c.on_write = function(chunk) { c.on_write = function(chunk) {
chunks.push(chunk); c.chunks.push(chunk);
length += chunk.length; return length += chunk.length;
return console.info("on_write " + c.id + " " + chunk.length);
}; };
c.on_end = function() { c.on_end = function() {
var chunk, data, i, position, st, _i, _len; var chunk, data, position, res, _i, _len, _ref,
_this = this;
data = new Buffer(length); data = new Buffer(length);
position = 0; position = 0;
for (_i = 0, _len = chunks.length; _i < _len; _i++) { _ref = c.chunks;
chunk = chunks[_i]; for (_i = 0, _len = _ref.length; _i < _len; _i++) {
chunk = _ref[_i];
chunk.copy(data, position); chunk.copy(data, position);
position += chunk.length; position += chunk.length;
} }
st = Date.now(); c.chunks = [];
i = 0; res = {};
while (Date.now() - st < 500) { res.curl_ = c;
++i; delete curls[c.id];
if (c.options.RAW) {
res.body = data;
} else {
res.body = data.toString();
} }
res.body = data;
res.status = res.code = c.getinfo('RESPONSE_CODE'); res.status = res.code = c.getinfo('RESPONSE_CODE');
res.info = function(info) { res.info = function(info) {
return c.getinfo(info); if (this.curl_ == null) throw new Error('curl is closed');
return this.curl_.getinfo(info);
}; };
console.info("id: " + c.id); res.close = function() {
return cb(null, res); if (this.curl_ != null) this.curl_.close();
this.curl_ = null;
return this.body = null;
};
return process.nextTick(function() {
return cb(null, res);
});
}; };
c.on_error = function(err) { c.on_error = function(err) {
var _this = this; var _this = this;
curls[c.id].close();
delete curls[c.id];
return process.nextTick(function() { return process.nextTick(function() {
return cb(err, null); return cb(err, null);
}); });
}; };
c.setopt('URL', url); c.setopt('URL', url);
return c.perform(); c.perform();
return c;
}; };
curl.Curl = Curl; curl.Curl = Curl;
curl.get_count = function() {
return Curl.get_count();
};
module.exports = curl; module.exports = curl;
}).call(this); }).call(this);

View file

@ -3,9 +3,19 @@ try
catch e catch e
{Curl} = require __dirname + '/../build/default/node-curl' {Curl} = require __dirname + '/../build/default/node-curl'
Curl::setopt_user_ = (option_id, value) ->
@options[option_id] = value
Curl::setopt = (ooption, value) -> Curl::setopt = (ooption, value) ->
option = ooption.toUpperCase() option = ooption.toUpperCase()
if (option_id = Curl.integer_options[option])?
# slist must be at the top of condition
# the option exists in string_options too
if (option_id = Curl.user_options[option])?
@setopt_user_ option_id, value
else if (option_id = Curl.slist_options[option])?
@setopt_slist_ option_id, value
else if (option_id = Curl.integer_options[option])?
@setopt_int_ option_id, value >> 0 @setopt_int_ option_id, value >> 0
else if (option_id = Curl.string_options[option])? else if (option_id = Curl.string_options[option])?
@setopt_str_ option_id, value.toString() @setopt_str_ option_id, value.toString()
@ -14,19 +24,24 @@ Curl::setopt = (ooption, value) ->
Curl::getinfo = (oinfo) -> Curl::getinfo = (oinfo) ->
info = oinfo.toUpperCase() info = oinfo.toUpperCase()
if (info_id = Curl.integer_infos[info])? if (info_id = Curl.slist_infos[info])?
@getinfo_slist_(info_id)
else if (info_id = Curl.integer_infos[info])?
@getinfo_int_(info_id) @getinfo_int_(info_id)
else if (info_id = Curl.string_infos[info])? else if (info_id = Curl.string_infos[info])?
@getinfo_str_(info_id) @getinfo_str_(info_id)
else if (info_id = Curl.double_infos[info])? else if (info_id = Curl.double_infos[info])?
@getinfo_double_(info_id) @getinfo_double_(info_id)
else else
throw new Error("unsupproted info #{oinfo}") throw new Error("unsupported info #{oinfo}")
Curl::perform = -> Curl::perform = ->
@perform_() @perform_()
Curl.process() Curl.process()
Curl.user_options =
RAW: 'RAW'
Curl.process = -> Curl.process = ->
if Curl.in_process if Curl.in_process
return return
@ -40,56 +55,75 @@ Curl.process = ->
# url, [options], cb # url, [options], cb
curl_id = 0 curl_id = 0
curls = {}
curl = (args...) -> curl = (args...) ->
cb = args.pop() cb = args.pop()
[url, options] = args [url, options] = args
options ?= {} options ?= {}
c = new Curl() c = new Curl()
c.options = {}
c.id = ++curl_id c.id = ++curl_id
# after the scope c will not valid any more, so add to curls to keep c alive
curls[c.id] = c
c.setopt 'FOLLOWLOCATION', 1 c.setopt 'FOLLOWLOCATION', 1
c.setopt 'ACCEPT_ENCODING', 'gzip' c.setopt 'ACCEPT_ENCODING', 'gzip'
chunks = [] c.chunks = []
length = 0 length = 0
res = {}
for own k, v of options for own k, v of options
c.setopt k, v c.setopt k, v
c.on_write = (chunk) -> c.on_write = (chunk) ->
chunks.push chunk c.chunks.push chunk
length += chunk.length length += chunk.length
console.info "on_write #{c.id} #{chunk.length}"
c.on_end = -> c.on_end = ->
data = new Buffer(length) data = new Buffer(length)
position = 0 position = 0
for chunk in chunks for chunk in c.chunks
chunk.copy data, position chunk.copy data, position
position += chunk.length position += chunk.length
# Strange Issue c.chunks = []
# use data.toString() will cause parallel http request terminated? eg yahoo.com
st = Date.now()
i = 0
while Date.now() - st < 500
++i
res = {}
# now the c is in res.curl, delete curl in curls
# if res destruct, curl will be destruct after gc
res.curl_ = c
delete curls[c.id]
res.body = data #.toString() if c.options.RAW
res.body = data
else
res.body = data.toString() #.toString()
res.status = res.code = c.getinfo('RESPONSE_CODE') res.status = res.code = c.getinfo('RESPONSE_CODE')
res.info = (info)-> res.info = (info)->
c.getinfo(info) unless @curl_?
# 当curl返回过快且cb循环调用回导致堆栈溢出 throw new Error('curl is closed')
# process.nextTick! @curl_.getinfo(info)
console.info "id: #{c.id}"
res.close = ->
@curl_.close() if @curl_?
@curl_ = null
@body = null
# if curl returns to fast, avoid cb recursive call
process.nextTick!
cb null, res cb null, res
c.on_error = (err)-> c.on_error = (err)->
curls[c.id].close()
delete curls[c.id]
process.nextTick! process.nextTick!
cb err, null cb err, null
c.setopt('URL', url) c.setopt('URL', url)
c.perform() c.perform()
c
curl.Curl = Curl curl.Curl = Curl
curl.get_count = ->
Curl.get_count()
module.exports = curl module.exports = curl

View file

@ -1,14 +1,14 @@
{ {
"name": "node-curl", "name": "node-curl",
"version": "0.1.0", "version": "0.1.0",
"description": "node wrapper for multi curl", "author" : "Jiang Miao <jiangfriend@gmail.com>",
"description": "node wrapper for multi curl, full implemented.",
"keywords" : ["node-curl", "curl", "multi-curl", "mcurl"], "keywords" : ["node-curl", "curl", "multi-curl", "mcurl"],
"homepage": "http://github.com/jiangmiao/node-curl", "homepage": "http://github.com/jiangmiao/node-curl",
"repository" : { "repository" : {
"type" : "git", "type" : "git",
"url" : "git://github.com/jiangmiao/node-curl.git" "url" : "git://github.com/jiangmiao/node-curl.git"
}, },
"author" : "Jiang Miao <jiangfriend@gmail.com>",
"main" : "./lib", "main" : "./lib",
"scripts" : { "scripts" : {
"install" : "node-waf configure build || true" "install" : "node-waf configure build || true"

View file

@ -8,6 +8,7 @@
#include <node_buffer.h> #include <node_buffer.h>
#include <curl/curl.h> #include <curl/curl.h>
#include <map> #include <map>
#include <vector>
#define NODE_CURL_OPTION_NX(name, value) {#name, value} #define NODE_CURL_OPTION_NX(name, value) {#name, value}
#define NODE_CURL_OPTION(name) NODE_CURL_OPTION_NX(name , CURLOPT_##name) #define NODE_CURL_OPTION(name) NODE_CURL_OPTION_NX(name , CURLOPT_##name)
@ -26,11 +27,17 @@ class NodeCurl
static int running_handles; static int running_handles;
static bool is_ref; static bool is_ref;
static std::map< CURL*, NodeCurl* > curls; static std::map< CURL*, NodeCurl* > curls;
static int count;
CURL * curl; CURL * curl;
v8::Persistent<v8::Object> handle; v8::Persistent<v8::Object> handle;
bool in_curlm;
std::vector<curl_slist*> slists;
NodeCurl(v8::Handle<v8::Object> object) NodeCurl(v8::Handle<v8::Object> object)
: in_curlm(false)
{ {
++count;
v8::V8::AdjustAmountOfExternalAllocatedMemory(2*4096);
object->SetPointerInInternalField(0, this); object->SetPointerInInternalField(0, this);
handle = v8::Persistent<v8::Object>::New(object); handle = v8::Persistent<v8::Object>::New(object);
handle.MakeWeak(this, destructor); handle.MakeWeak(this, destructor);
@ -39,6 +46,7 @@ class NodeCurl
if (!curl) if (!curl)
{ {
raise("curl_easy_init failed"); raise("curl_easy_init failed");
return;
} }
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_function); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_function);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
@ -47,18 +55,31 @@ class NodeCurl
~NodeCurl() ~NodeCurl()
{ {
--count;
v8::V8::AdjustAmountOfExternalAllocatedMemory(-2*4096);
if (curl) if (curl)
{ {
curl_multi_remove_handle(curlm, curl); if (in_curlm)
curl_multi_remove_handle(curlm, curl);
curl_easy_cleanup(curl); curl_easy_cleanup(curl);
curls.erase(curl); curls.erase(curl);
} }
for (std::vector<curl_slist*>::iterator i = slists.begin(), e = slists.end(); i != e; ++i)
{
curl_slist * slist = *i;
if (slist)
{
curl_slist_free_all(slist);
}
}
} }
static void destructor(v8::Persistent<v8::Value> object, void *data) static void destructor(v8::Persistent<v8::Value> value, void *data)
{ {
NodeCurl * curl = (NodeCurl*)object->ToObject()->GetPointerFromInternalField(0); v8::Handle<v8::Object> object = value->ToObject();
delete curl; NodeCurl * curl = (NodeCurl*)object->GetPointerFromInternalField(0);
curl->close();
} }
void close() void close()
@ -68,6 +89,15 @@ class NodeCurl
delete this; delete this;
} }
static v8::Handle<v8::Value> close(const v8::Arguments & args)
{
NodeCurl * node_curl = unwrap(args.This());
if (node_curl)
node_curl->close();
return args.This();
}
static NodeCurl * unwrap(v8::Handle<v8::Object> value) static NodeCurl * unwrap(v8::Handle<v8::Object> value)
{ {
return (NodeCurl*)value->GetPointerFromInternalField(0); return (NodeCurl*)value->GetPointerFromInternalField(0);
@ -82,12 +112,11 @@ class NodeCurl
size_t on_write(char *data, size_t n) size_t on_write(char *data, size_t n)
{ {
v8::HandleScope scope; static v8::Persistent<v8::String> SYM_ON_WRITE = v8::Persistent<v8::String>::New(v8::String::NewSymbol("on_write"));
static auto SYM_ON_WRITE = v8::Persistent<v8::String>::New(v8::String::NewSymbol("on_write")); v8::Handle<v8::Value> cb = handle->Get(SYM_ON_WRITE);
auto cb = handle->Get(SYM_ON_WRITE);
if (cb->IsFunction()) if (cb->IsFunction())
{ {
auto buffer = node::Buffer::New(data, n); node::Buffer * buffer = node::Buffer::New(data, n);
v8::Handle<v8::Value> argv[] = { buffer->handle_ }; v8::Handle<v8::Value> argv[] = { buffer->handle_ };
cb->ToObject()->CallAsFunction(handle, 1, argv); cb->ToObject()->CallAsFunction(handle, 1, argv);
} }
@ -96,8 +125,8 @@ class NodeCurl
void on_end(CURLMsg *msg) void on_end(CURLMsg *msg)
{ {
static auto SYM_ON_END = v8::Persistent<v8::String>::New(v8::String::NewSymbol("on_end")); static v8::Persistent<v8::String> SYM_ON_END = v8::Persistent<v8::String>::New(v8::String::NewSymbol("on_end"));
auto cb = handle->Get(SYM_ON_END); v8::Handle<v8::Value> cb = handle->Get(SYM_ON_END);
if (cb->IsFunction()) if (cb->IsFunction())
{ {
v8::Handle<v8::Value> argv[] = {}; v8::Handle<v8::Value> argv[] = {};
@ -107,8 +136,8 @@ class NodeCurl
void on_error(CURLMsg *msg) void on_error(CURLMsg *msg)
{ {
static auto SYM_ON_ERROR = v8::Persistent<v8::String>::New(v8::String::NewSymbol("on_error")); static v8::Persistent<v8::String> SYM_ON_ERROR = v8::Persistent<v8::String>::New(v8::String::NewSymbol("on_error"));
auto cb = handle->Get(SYM_ON_ERROR); v8::Handle<v8::Value> cb = handle->Get(SYM_ON_ERROR);
if (cb->IsFunction()) if (cb->IsFunction())
{ {
v8::Handle<v8::Value> argv[] = {v8::Exception::Error(v8::String::New(curl_easy_strerror(msg->data.result)))}; v8::Handle<v8::Value> argv[] = {v8::Exception::Error(v8::String::New(curl_easy_strerror(msg->data.result)))};
@ -117,10 +146,10 @@ class NodeCurl
} }
// curl_easy_getinfo // curl_easy_getinfo
template<typename T, typename S> template<typename CType, typename JsClass>
static v8::Handle<v8::Value> getinfo(const v8::Arguments &args) static v8::Handle<v8::Value> getinfo(const v8::Arguments &args)
{ {
T result; CType result;
NodeCurl * node_curl = unwrap(args.This()); NodeCurl * node_curl = unwrap(args.This());
CURLINFO info = (CURLINFO)args[0]->Int32Value(); CURLINFO info = (CURLINFO)args[0]->Int32Value();
CURLcode code = curl_easy_getinfo(node_curl->curl, info, &result); CURLcode code = curl_easy_getinfo(node_curl->curl, info, &result);
@ -128,7 +157,7 @@ class NodeCurl
{ {
return raise("curl_easy_getinfo failed", curl_easy_strerror(code)); return raise("curl_easy_getinfo failed", curl_easy_strerror(code));
} }
return S::New(result); return JsClass::New(result);
} }
static v8::Handle<v8::Value> getinfo_int(const v8::Arguments & args) static v8::Handle<v8::Value> getinfo_int(const v8::Arguments & args)
@ -146,6 +175,30 @@ class NodeCurl
return getinfo<double, v8::Number>(args); return getinfo<double, v8::Number>(args);
} }
static v8::Handle<v8::Value> getinfo_slist(const v8::Arguments & args)
{
curl_slist * slist, * cur;
NodeCurl* node_curl = unwrap(args.This());
CURLINFO info = (CURLINFO)args[0]->Int32Value();
CURLcode code = curl_easy_getinfo(node_curl->curl, info, &slist);
if (code != CURLE_OK)
{
return raise("curl_easy_getinfo failed", curl_easy_strerror(code));
}
v8::Handle<v8::Array> array = v8::Array::New();
if (slist)
{
cur = slist;
while (cur)
{
array->Set(array->Length(), v8::String::New(slist->data));
cur = cur->next;
}
curl_slist_free_all(slist);
}
return array;
}
// curl_easy_setopt // curl_easy_setopt
template<typename T> template<typename T>
v8::Handle<v8::Value> setopt(v8::Handle<v8::Value> option, T value) v8::Handle<v8::Value> setopt(v8::Handle<v8::Value> option, T value)
@ -169,6 +222,32 @@ class NodeCurl
return unwrap(args.This())->setopt(args[0], *v8::String::Utf8Value(args[1]) ); return unwrap(args.This())->setopt(args[0], *v8::String::Utf8Value(args[1]) );
} }
static v8::Handle<v8::Value> setopt_slist(const v8::Arguments & args)
{
NodeCurl * node_curl = unwrap(args.This());
curl_slist * slist = value_to_slist(args[1]);
node_curl->slists.push_back(slist);
return node_curl->setopt(args[0], slist);
}
static curl_slist * value_to_slist(v8::Handle<v8::Value> value)
{
curl_slist * slist = NULL;
if (!value->IsArray())
{
slist = curl_slist_append(slist, *v8::String::Utf8Value(value));
}
else
{
v8::Handle<v8::Array> array = v8::Handle<v8::Array>::Cast(value);
for (uint32_t i=0, len = array->Length(); i<len; ++i)
{
slist = curl_slist_append(slist, *v8::String::Utf8Value(array->Get(i)));
}
}
return slist;
}
static v8::Handle<v8::Value> raise(const char *data, const char *reason = NULL) static v8::Handle<v8::Value> raise(const char *data, const char *reason = NULL)
{ {
@ -185,10 +264,10 @@ class NodeCurl
template<typename T> template<typename T>
static void export_curl_options(T t, const char *group_name, CurlOption *options, int len) static void export_curl_options(T t, const char *group_name, CurlOption *options, int len)
{ {
auto node_options = v8::Object::New(); v8::Handle<v8::Object> node_options = v8::Object::New();
for (int i=0; i<len; ++i) for (int i=0; i<len; ++i)
{ {
const auto & option = options[i]; const CurlOption & option = options[i];
node_options->Set( node_options->Set(
v8::String::NewSymbol(option.name), v8::String::NewSymbol(option.name),
v8::Integer::New(option.value) v8::Integer::New(option.value)
@ -223,12 +302,10 @@ class NodeCurl
{ {
return raise("curl_multi_fdset failed", curl_multi_strerror(code)); return raise("curl_multi_fdset failed", curl_multi_strerror(code));
} }
printf("maxfd returns %d\n", max_fd);
if (max_fd > 0) if (max_fd > 0)
{ {
timeval tv = {0}; timeval tv = {0};
int n = select(max_fd+1, &read_fds, &write_fds, &error_fds, &tv); int n = select(max_fd+1, &read_fds, &write_fds, &error_fds, &tv);
printf("selecting returns %d\n", n);
if (n == 0) if (n == 0)
{ {
return v8::Integer::New(running_handles); return v8::Integer::New(running_handles);
@ -249,21 +326,20 @@ class NodeCurl
CURLMsg * msg = NULL; CURLMsg * msg = NULL;
while ( (msg = curl_multi_info_read(curlm, &msgs)) ) while ( (msg = curl_multi_info_read(curlm, &msgs)) )
{ {
printf("msgs: %d, running_handles: %d\n", msgs, running_handles);
if (msg->msg == CURLMSG_DONE) if (msg->msg == CURLMSG_DONE)
{ {
auto curl = curls[msg->easy_handle]; NodeCurl * curl = curls[msg->easy_handle];
if (msg->data.result == CURLE_OK) if (msg->data.result == CURLE_OK)
curl->on_end(msg); curl->on_end(msg);
else else
curl->on_error(msg); curl->on_error(msg);
code = curl_multi_remove_handle(curlm, msg->easy_handle); code = curl_multi_remove_handle(curlm, msg->easy_handle);
curl->in_curlm = false;
if (code != CURLM_OK) if (code != CURLM_OK)
{ {
return raise("curl_multi_remove_handle failed", curl_multi_strerror(code)); return raise("curl_multi_remove_handle failed", curl_multi_strerror(code));
} }
} }
puts("done.");
} }
} }
return v8::Integer::New(running_handles); return v8::Integer::New(running_handles);
@ -274,6 +350,7 @@ class NodeCurl
{ {
NodeCurl *curl = unwrap(args.This()); NodeCurl *curl = unwrap(args.This());
CURLMcode code = curl_multi_add_handle(curlm, curl->curl); CURLMcode code = curl_multi_add_handle(curlm, curl->curl);
curl->in_curlm = true;
if (code != CURLM_OK) if (code != CURLM_OK)
{ {
return raise("curl_multi_add_handle failed", curl_multi_strerror(code)); return raise("curl_multi_add_handle failed", curl_multi_strerror(code));
@ -282,6 +359,11 @@ class NodeCurl
return args.This(); return args.This();
} }
static v8::Handle<v8::Value> get_count(const v8::Arguments & args )
{
return v8::Integer::New(count);
}
public: public:
static v8::Handle<v8::Value> Initialize(v8::Handle<v8::Object> target) static v8::Handle<v8::Value> Initialize(v8::Handle<v8::Object> target)
{ {
@ -299,19 +381,24 @@ class NodeCurl
} }
// Initialize node-curl // Initialize node-curl
auto t = v8::FunctionTemplate::New(New); v8::Handle<v8::FunctionTemplate> t = v8::FunctionTemplate::New(New);
t->InstanceTemplate()->SetInternalFieldCount(1); t->InstanceTemplate()->SetInternalFieldCount(1);
// Set prototype methods // Set prototype methods
NODE_SET_PROTOTYPE_METHOD(t, "perform_", perform); NODE_SET_PROTOTYPE_METHOD(t, "perform_", perform);
NODE_SET_PROTOTYPE_METHOD(t, "setopt_int_", setopt_int); NODE_SET_PROTOTYPE_METHOD(t, "setopt_int_", setopt_int);
NODE_SET_PROTOTYPE_METHOD(t, "setopt_str_", setopt_str); NODE_SET_PROTOTYPE_METHOD(t, "setopt_str_", setopt_str);
NODE_SET_PROTOTYPE_METHOD(t, "setopt_slist_", setopt_slist);
NODE_SET_PROTOTYPE_METHOD(t, "getinfo_int_", getinfo_int); NODE_SET_PROTOTYPE_METHOD(t, "getinfo_int_", getinfo_int);
NODE_SET_PROTOTYPE_METHOD(t, "getinfo_str_", getinfo_str); NODE_SET_PROTOTYPE_METHOD(t, "getinfo_str_", getinfo_str);
NODE_SET_PROTOTYPE_METHOD(t, "getinfo_double_", getinfo_double); NODE_SET_PROTOTYPE_METHOD(t, "getinfo_double_", getinfo_double);
NODE_SET_PROTOTYPE_METHOD(t, "getinfo_slist_", getinfo_slist);
NODE_SET_PROTOTYPE_METHOD(t, "close", close);
NODE_SET_METHOD(t, "process_", process); NODE_SET_METHOD(t, "process_", process);
NODE_SET_METHOD(t, "get_count", get_count);
// Set curl constants // Set curl constants
#include "string_options.h" #include "string_options.h"
@ -320,11 +407,34 @@ class NodeCurl
#include "integer_infos.h" #include "integer_infos.h"
#include "double_infos.h" #include "double_infos.h"
#define X(name) {#name, CURLOPT_##name}
CurlOption slist_options[] = {
X(HTTPHEADER),
X(HTTP200ALIASES),
X(MAIL_RCPT),
X(QUOTE),
X(POSTQUOTE),
X(PREQUOTE),
X(RESOLVE),
X(TELNETOPTIONS)
};
#undef X
#define X(name) {#name, CURLINFO_##name}
CurlOption slist_infos[] = {
X(SSL_ENGINES),
X(COOKIELIST)
};
#undef X
NODE_CURL_EXPORT(string_options); NODE_CURL_EXPORT(string_options);
NODE_CURL_EXPORT(integer_options); NODE_CURL_EXPORT(integer_options);
NODE_CURL_EXPORT(slist_options);
NODE_CURL_EXPORT(string_infos); NODE_CURL_EXPORT(string_infos);
NODE_CURL_EXPORT(integer_infos); NODE_CURL_EXPORT(integer_infos);
NODE_CURL_EXPORT(double_infos); NODE_CURL_EXPORT(double_infos);
NODE_CURL_EXPORT(slist_infos);
target->Set(v8::String::NewSymbol("Curl"), t->GetFunction()); target->Set(v8::String::NewSymbol("Curl"), t->GetFunction());
return target; return target;
@ -335,5 +445,6 @@ CURLM * NodeCurl::curlm = NULL;
int NodeCurl::running_handles = 0; int NodeCurl::running_handles = 0;
bool NodeCurl::is_ref = false; bool NodeCurl::is_ref = false;
std::map< CURL*, NodeCurl* > NodeCurl::curls; std::map< CURL*, NodeCurl* > NodeCurl::curls;
int NodeCurl::count = 0;
#endif #endif

View file

@ -4,7 +4,7 @@ def set_options(opt):
def configure(conf): def configure(conf):
conf.check_tool('compiler_cxx') conf.check_tool('compiler_cxx')
conf.check_tool('node_addon') conf.check_tool('node_addon')
conf.env.append_unique('CXXFLAGS', ['-Wall', '-O2', '-std=c++0x']) conf.env.append_unique('CXXFLAGS', ['-Wall', '-O2'])
conf.env['LIB_CURL'] = 'curl' conf.env['LIB_CURL'] = 'curl'
def build(bld): def build(bld):