first commit

This commit is contained in:
jiangfriend@gmail.com 2012-02-14 14:56:03 +08:00
commit f8f934a119
18 changed files with 901 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
raw
build

2
.npmignore Normal file
View file

@ -0,0 +1,2 @@
raw
build

36
README.md Normal file
View file

@ -0,0 +1,36 @@
Node Curl Wrap
Use select curl multi
Quick Start
===========
curl = require('node-curl')
curl('www.google.com', {VERBOSE: 1}, function(err, res) {
console.info(res.status)
console.info('-----')
console.info(res.body)
console.info('-----')
console.info(res.info('SIZE_DOWNLOAD'))
});
Usage
=====
Function: curl
curl(url, [options = {}], callback)
callback includes 2 parameters (error, result)
Curl Options:
options is on the list at http://curl.haxx.se/libcurl/c/curl_easy_setopt.html without CURLOPT_
eg: CURLOPT_VERBOSE will be VERBOSE, CURLOPT_HEADER will be HEADER
Object: result
body
status
info
Curl Infos:
infos is on the list at http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html without CURLINFO_
eg: CURLINFO_EFFECTIVE_URL will be EFFETCTIVE_URL

19
examples/quick-start.js Normal file
View file

@ -0,0 +1,19 @@
curl = require('../index');
url = 'www.yahoo.com';
curl(url, function(err, res) {
console.info("body length: " + res.body.length);
console.info('-----');
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) {
console.info("body length: " + res.body.length);
console.info('-----');
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");
});

6
index.js Normal file
View file

@ -0,0 +1,6 @@
// Generated by CoffeeScript 1.1.4-3
(function() {
module.exports = require('./lib/curl');
}).call(this);

1
index.toffee Normal file
View file

@ -0,0 +1 @@
module.exports = require './lib/curl'

120
lib/curl.js Normal file
View file

@ -0,0 +1,120 @@
// Generated by CoffeeScript 1.1.4-3
(function() {
var Curl, curl, curl_id,
__slice = [].slice,
__hasProp = {}.hasOwnProperty;
try {
Curl = require(__dirname + '/../build/Release/node-curl').Curl;
} catch (e) {
Curl = require(__dirname + '/../build/default/node-curl').Curl;
}
Curl.prototype.setopt = function(ooption, value) {
var option, option_id;
option = ooption.toUpperCase();
if ((option_id = Curl.integer_options[option]) != null) {
return this.setopt_int_(option_id, value >> 0);
} else if ((option_id = Curl.string_options[option]) != null) {
return this.setopt_str_(option_id, value.toString());
} else {
throw new Error("unsupported option " + option);
}
};
Curl.prototype.getinfo = function(oinfo) {
var info, info_id;
info = oinfo.toUpperCase();
if ((info_id = Curl.integer_infos[info]) != null) {
return this.getinfo_int_(info_id);
} else if ((info_id = Curl.string_infos[info]) != null) {
return this.getinfo_str_(info_id);
} else if ((info_id = Curl.double_infos[info]) != null) {
return this.getinfo_double_(info_id);
} else {
throw new Error("unsupproted info " + oinfo);
}
};
Curl.prototype.perform = function() {
this.perform_();
return Curl.process();
};
Curl.process = function() {
var once;
if (Curl.in_process) return;
return (once = function() {
var num;
num = Curl.process_();
if (num > 0) {
Curl.in_process = true;
return setTimeout(once, 80);
} else {
return Curl.in_process = false;
}
})();
};
curl_id = 0;
curl = function() {
var args, c, cb, chunks, k, length, options, res, url, v;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
cb = args.pop();
url = args[0], options = args[1];
if (options == null) options = {};
c = new Curl();
c.id = ++curl_id;
c.setopt('FOLLOWLOCATION', 1);
c.setopt('ACCEPT_ENCODING', 'gzip');
chunks = [];
length = 0;
res = {};
for (k in options) {
if (!__hasProp.call(options, k)) continue;
v = options[k];
c.setopt(k, v);
}
c.on_write = function(chunk) {
chunks.push(chunk);
length += chunk.length;
return console.info("on_write " + c.id + " " + chunk.length);
};
c.on_end = function() {
var chunk, data, i, position, st, _i, _len;
data = new Buffer(length);
position = 0;
for (_i = 0, _len = chunks.length; _i < _len; _i++) {
chunk = chunks[_i];
chunk.copy(data, position);
position += chunk.length;
}
st = Date.now();
i = 0;
while (Date.now() - st < 500) {
++i;
}
res.body = data;
res.status = res.code = c.getinfo('RESPONSE_CODE');
res.info = function(info) {
return c.getinfo(info);
};
console.info("id: " + c.id);
return cb(null, res);
};
c.on_error = function(err) {
var _this = this;
return process.nextTick(function() {
return cb(err, null);
});
};
c.setopt('URL', url);
return c.perform();
};
curl.Curl = Curl;
module.exports = curl;
}).call(this);

95
lib/curl.toffee Normal file
View file

@ -0,0 +1,95 @@
try
{Curl} = require __dirname + '/../build/Release/node-curl'
catch e
{Curl} = require __dirname + '/../build/default/node-curl'
Curl::setopt = (ooption, value) ->
option = ooption.toUpperCase()
if (option_id = Curl.integer_options[option])?
@setopt_int_ option_id, value >> 0
else if (option_id = Curl.string_options[option])?
@setopt_str_ option_id, value.toString()
else
throw new Error("unsupported option #{option}")
Curl::getinfo = (oinfo) ->
info = oinfo.toUpperCase()
if (info_id = Curl.integer_infos[info])?
@getinfo_int_(info_id)
else if (info_id = Curl.string_infos[info])?
@getinfo_str_(info_id)
else if (info_id = Curl.double_infos[info])?
@getinfo_double_(info_id)
else
throw new Error("unsupproted info #{oinfo}")
Curl::perform = ->
@perform_()
Curl.process()
Curl.process = ->
if Curl.in_process
return
do once = ->
num = Curl.process_()
if num > 0
Curl.in_process = true
setTimeout once, 80
else
Curl.in_process = false
# url, [options], cb
curl_id = 0
curl = (args...) ->
cb = args.pop()
[url, options] = args
options ?= {}
c = new Curl()
c.id = ++curl_id
c.setopt 'FOLLOWLOCATION', 1
c.setopt 'ACCEPT_ENCODING', 'gzip'
chunks = []
length = 0
res = {}
for own k, v of options
c.setopt k, v
c.on_write = (chunk) ->
chunks.push chunk
length += chunk.length
console.info "on_write #{c.id} #{chunk.length}"
c.on_end = ->
data = new Buffer(length)
position = 0
for chunk in chunks
chunk.copy data, position
position += chunk.length
# Strange Issue
# use data.toString() will cause parallel http request terminated? eg yahoo.com
st = Date.now()
i = 0
while Date.now() - st < 500
++i
res.body = data #.toString()
res.status = res.code = c.getinfo('RESPONSE_CODE')
res.info = (info)->
c.getinfo(info)
# 当curl返回过快且cb循环调用回导致堆栈溢出
# process.nextTick!
console.info "id: #{c.id}"
cb null, res
c.on_error = (err)->
process.nextTick!
cb err, null
c.setopt('URL', url)
c.perform()
curl.Curl = Curl
module.exports = curl

17
package.json Normal file
View file

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

16
src/double_infos.h Normal file
View file

@ -0,0 +1,16 @@
// generated by generate_curl_options_list.sh at Mon, 13 Feb 2012 21:40:10 +0800
CurlOption double_infos[] = {
{"TOTAL_TIME", CURLINFO_TOTAL_TIME},
{"NAMELOOKUP_TIME", CURLINFO_NAMELOOKUP_TIME},
{"CONNECT_TIME", CURLINFO_CONNECT_TIME},
{"PRETRANSFER_TIME", CURLINFO_PRETRANSFER_TIME},
{"SIZE_UPLOAD", CURLINFO_SIZE_UPLOAD},
{"SIZE_DOWNLOAD", CURLINFO_SIZE_DOWNLOAD},
{"SPEED_DOWNLOAD", CURLINFO_SPEED_DOWNLOAD},
{"SPEED_UPLOAD", CURLINFO_SPEED_UPLOAD},
{"CONTENT_LENGTH_DOWNLOAD", CURLINFO_CONTENT_LENGTH_DOWNLOAD},
{"CONTENT_LENGTH_UPLOAD", CURLINFO_CONTENT_LENGTH_UPLOAD},
{"STARTTRANSFER_TIME", CURLINFO_STARTTRANSFER_TIME},
{"REDIRECT_TIME", CURLINFO_REDIRECT_TIME},
{"APPCONNECT_TIME", CURLINFO_APPCONNECT_TIME},
};

View file

@ -0,0 +1,19 @@
#!/bin/sh
generate() {
name=$1
pattern=$2
prefix=$3
(
echo "// generated by $0 at $(date -R)"
echo "CurlOption $name[] = {"
cat /usr/include/curl/curl.h|perl -ne "/$pattern/i && print \"\t{\\\"\$1\\\", CURL${prefix}_\$1},\n\""
echo '};'
) > $name.h
}
generate integer_options 'CINIT\((\w+).*LONG' OPT
generate string_options 'CINIT\((\w+).*OBJECT' OPT
generate integer_infos 'CURLINFO_(\w+).*LONG' INFO
generate string_infos 'CURLINFO_(\w+).*STRING' INFO
generate double_infos 'CURLINFO_(\w+).*DOUBLE' INFO

21
src/integer_infos.h Normal file
View file

@ -0,0 +1,21 @@
// generated by generate_curl_options_list.sh at Mon, 13 Feb 2012 21:40:10 +0800
CurlOption integer_infos[] = {
{"RESPONSE_CODE", CURLINFO_RESPONSE_CODE},
{"HEADER_SIZE", CURLINFO_HEADER_SIZE},
{"REQUEST_SIZE", CURLINFO_REQUEST_SIZE},
{"SSL_VERIFYRESULT", CURLINFO_SSL_VERIFYRESULT},
{"FILETIME", CURLINFO_FILETIME},
{"REDIRECT_COUNT", CURLINFO_REDIRECT_COUNT},
{"HTTP_CONNECTCODE", CURLINFO_HTTP_CONNECTCODE},
{"HTTPAUTH_AVAIL", CURLINFO_HTTPAUTH_AVAIL},
{"PROXYAUTH_AVAIL", CURLINFO_PROXYAUTH_AVAIL},
{"OS_ERRNO", CURLINFO_OS_ERRNO},
{"NUM_CONNECTS", CURLINFO_NUM_CONNECTS},
{"LASTSOCKET", CURLINFO_LASTSOCKET},
{"CONDITION_UNMET", CURLINFO_CONDITION_UNMET},
{"RTSP_CLIENT_CSEQ", CURLINFO_RTSP_CLIENT_CSEQ},
{"RTSP_SERVER_CSEQ", CURLINFO_RTSP_SERVER_CSEQ},
{"RTSP_CSEQ_RECV", CURLINFO_RTSP_CSEQ_RECV},
{"PRIMARY_PORT", CURLINFO_PRIMARY_PORT},
{"LOCAL_PORT", CURLINFO_LOCAL_PORT},
};

91
src/integer_options.h Normal file
View file

@ -0,0 +1,91 @@
// generated by generate_curl_options_list.sh at Mon, 13 Feb 2012 21:40:10 +0800
CurlOption integer_options[] = {
{"PORT", CURLOPT_PORT},
{"TIMEOUT", CURLOPT_TIMEOUT},
{"INFILESIZE", CURLOPT_INFILESIZE},
{"LOW_SPEED_LIMIT", CURLOPT_LOW_SPEED_LIMIT},
{"LOW_SPEED_TIME", CURLOPT_LOW_SPEED_TIME},
{"RESUME_FROM", CURLOPT_RESUME_FROM},
{"CRLF", CURLOPT_CRLF},
{"SSLVERSION", CURLOPT_SSLVERSION},
{"TIMECONDITION", CURLOPT_TIMECONDITION},
{"TIMEVALUE", CURLOPT_TIMEVALUE},
{"VERBOSE", CURLOPT_VERBOSE},
{"HEADER", CURLOPT_HEADER},
{"NOPROGRESS", CURLOPT_NOPROGRESS},
{"NOBODY", CURLOPT_NOBODY},
{"FAILONERROR", CURLOPT_FAILONERROR},
{"UPLOAD", CURLOPT_UPLOAD},
{"POST", CURLOPT_POST},
{"DIRLISTONLY", CURLOPT_DIRLISTONLY},
{"APPEND", CURLOPT_APPEND},
{"NETRC", CURLOPT_NETRC},
{"FOLLOWLOCATION", CURLOPT_FOLLOWLOCATION},
{"TRANSFERTEXT", CURLOPT_TRANSFERTEXT},
{"PUT", CURLOPT_PUT},
{"AUTOREFERER", CURLOPT_AUTOREFERER},
{"PROXYPORT", CURLOPT_PROXYPORT},
{"POSTFIELDSIZE", CURLOPT_POSTFIELDSIZE},
{"HTTPPROXYTUNNEL", CURLOPT_HTTPPROXYTUNNEL},
{"SSL_VERIFYPEER", CURLOPT_SSL_VERIFYPEER},
{"MAXREDIRS", CURLOPT_MAXREDIRS},
{"FILETIME", CURLOPT_FILETIME},
{"MAXCONNECTS", CURLOPT_MAXCONNECTS},
{"CLOSEPOLICY", CURLOPT_CLOSEPOLICY},
{"FRESH_CONNECT", CURLOPT_FRESH_CONNECT},
{"FORBID_REUSE", CURLOPT_FORBID_REUSE},
{"CONNECTTIMEOUT", CURLOPT_CONNECTTIMEOUT},
{"HTTPGET", CURLOPT_HTTPGET},
{"SSL_VERIFYHOST", CURLOPT_SSL_VERIFYHOST},
{"HTTP_VERSION", CURLOPT_HTTP_VERSION},
{"FTP_USE_EPSV", CURLOPT_FTP_USE_EPSV},
{"SSLENGINE_DEFAULT", CURLOPT_SSLENGINE_DEFAULT},
{"DNS_USE_GLOBAL_CACHE", CURLOPT_DNS_USE_GLOBAL_CACHE},
{"DNS_CACHE_TIMEOUT", CURLOPT_DNS_CACHE_TIMEOUT},
{"COOKIESESSION", CURLOPT_COOKIESESSION},
{"BUFFERSIZE", CURLOPT_BUFFERSIZE},
{"NOSIGNAL", CURLOPT_NOSIGNAL},
{"PROXYTYPE", CURLOPT_PROXYTYPE},
{"UNRESTRICTED_AUTH", CURLOPT_UNRESTRICTED_AUTH},
{"FTP_USE_EPRT", CURLOPT_FTP_USE_EPRT},
{"HTTPAUTH", CURLOPT_HTTPAUTH},
{"FTP_CREATE_MISSING_DIRS", CURLOPT_FTP_CREATE_MISSING_DIRS},
{"PROXYAUTH", CURLOPT_PROXYAUTH},
{"FTP_RESPONSE_TIMEOUT", CURLOPT_FTP_RESPONSE_TIMEOUT},
{"IPRESOLVE", CURLOPT_IPRESOLVE},
{"MAXFILESIZE", CURLOPT_MAXFILESIZE},
{"USE_SSL", CURLOPT_USE_SSL},
{"TCP_NODELAY", CURLOPT_TCP_NODELAY},
{"FTPSSLAUTH", CURLOPT_FTPSSLAUTH},
{"IGNORE_CONTENT_LENGTH", CURLOPT_IGNORE_CONTENT_LENGTH},
{"FTP_SKIP_PASV_IP", CURLOPT_FTP_SKIP_PASV_IP},
{"FTP_FILEMETHOD", CURLOPT_FTP_FILEMETHOD},
{"LOCALPORT", CURLOPT_LOCALPORT},
{"LOCALPORTRANGE", CURLOPT_LOCALPORTRANGE},
{"CONNECT_ONLY", CURLOPT_CONNECT_ONLY},
{"SSL_SESSIONID_CACHE", CURLOPT_SSL_SESSIONID_CACHE},
{"SSH_AUTH_TYPES", CURLOPT_SSH_AUTH_TYPES},
{"FTP_SSL_CCC", CURLOPT_FTP_SSL_CCC},
{"TIMEOUT_MS", CURLOPT_TIMEOUT_MS},
{"CONNECTTIMEOUT_MS", CURLOPT_CONNECTTIMEOUT_MS},
{"HTTP_TRANSFER_DECODING", CURLOPT_HTTP_TRANSFER_DECODING},
{"HTTP_CONTENT_DECODING", CURLOPT_HTTP_CONTENT_DECODING},
{"NEW_FILE_PERMS", CURLOPT_NEW_FILE_PERMS},
{"NEW_DIRECTORY_PERMS", CURLOPT_NEW_DIRECTORY_PERMS},
{"POSTREDIR", CURLOPT_POSTREDIR},
{"PROXY_TRANSFER_MODE", CURLOPT_PROXY_TRANSFER_MODE},
{"ADDRESS_SCOPE", CURLOPT_ADDRESS_SCOPE},
{"CERTINFO", CURLOPT_CERTINFO},
{"TFTP_BLKSIZE", CURLOPT_TFTP_BLKSIZE},
{"SOCKS5_GSSAPI_NEC", CURLOPT_SOCKS5_GSSAPI_NEC},
{"PROTOCOLS", CURLOPT_PROTOCOLS},
{"REDIR_PROTOCOLS", CURLOPT_REDIR_PROTOCOLS},
{"FTP_USE_PRET", CURLOPT_FTP_USE_PRET},
{"RTSP_REQUEST", CURLOPT_RTSP_REQUEST},
{"RTSP_CLIENT_CSEQ", CURLOPT_RTSP_CLIENT_CSEQ},
{"RTSP_SERVER_CSEQ", CURLOPT_RTSP_SERVER_CSEQ},
{"WILDCARDMATCH", CURLOPT_WILDCARDMATCH},
{"TRANSFER_ENCODING", CURLOPT_TRANSFER_ENCODING},
{"GSSAPI_DELEGATION", CURLOPT_GSSAPI_DELEGATION},
{"ACCEPTTIMEOUT_MS", CURLOPT_ACCEPTTIMEOUT_MS},
};

7
src/node-curl.cc Normal file
View file

@ -0,0 +1,7 @@
#include "node-curl.h"
extern "C"
void init(v8::Handle<v8::Object> target)
{
NodeCurl::Initialize(target);
}

339
src/node-curl.h Normal file
View file

@ -0,0 +1,339 @@
#ifndef NODE_CURL_NOHE_CURL_H
#define NODE_CURL_NOHE_CURL_H
#include <unistd.h>
#include <v8.h>
#include <node.h>
#include <node_buffer.h>
#include <curl/curl.h>
#include <map>
#define NODE_CURL_OPTION_NX(name, value) {#name, value}
#define NODE_CURL_OPTION(name) NODE_CURL_OPTION_NX(name , CURLOPT_##name)
#define NODE_CURL_EXPORT(name) export_curl_options(t, #name, name, sizeof(name) / sizeof(CurlOption));
class NodeCurl
{
struct CurlOption {
const char *name;
int value;
};
static CURLM * curlm;
static int running_handles;
static bool is_ref;
static std::map< CURL*, NodeCurl* > curls;
CURL * curl;
v8::Persistent<v8::Object> handle;
NodeCurl(v8::Handle<v8::Object> object)
{
object->SetPointerInInternalField(0, this);
handle = v8::Persistent<v8::Object>::New(object);
handle.MakeWeak(this, destructor);
curl = curl_easy_init();
if (!curl)
{
raise("curl_easy_init failed");
}
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_function);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
curls[curl] = this;
}
~NodeCurl()
{
if (curl)
{
curl_multi_remove_handle(curlm, curl);
curl_easy_cleanup(curl);
curls.erase(curl);
}
}
static void destructor(v8::Persistent<v8::Value> object, void *data)
{
NodeCurl * curl = (NodeCurl*)object->ToObject()->GetPointerFromInternalField(0);
delete curl;
}
void close()
{
handle->SetPointerInInternalField(0, NULL);
handle.Dispose();
delete this;
}
static NodeCurl * unwrap(v8::Handle<v8::Object> value)
{
return (NodeCurl*)value->GetPointerFromInternalField(0);
}
// curl write function mapping
static size_t write_function(char *ptr, size_t size, size_t nmemb, void *userdata)
{
NodeCurl *nodecurl = (NodeCurl*)userdata;
return nodecurl->on_write(ptr, size * nmemb);
}
size_t on_write(char *data, size_t n)
{
v8::HandleScope scope;
static auto SYM_ON_WRITE = v8::Persistent<v8::String>::New(v8::String::NewSymbol("on_write"));
auto cb = handle->Get(SYM_ON_WRITE);
if (cb->IsFunction())
{
auto buffer = node::Buffer::New(data, n);
v8::Handle<v8::Value> argv[] = { buffer->handle_ };
cb->ToObject()->CallAsFunction(handle, 1, argv);
}
return n;
}
void on_end(CURLMsg *msg)
{
static auto SYM_ON_END = v8::Persistent<v8::String>::New(v8::String::NewSymbol("on_end"));
auto cb = handle->Get(SYM_ON_END);
if (cb->IsFunction())
{
v8::Handle<v8::Value> argv[] = {};
cb->ToObject()->CallAsFunction(handle, 0, argv);
}
}
void on_error(CURLMsg *msg)
{
static auto SYM_ON_ERROR = v8::Persistent<v8::String>::New(v8::String::NewSymbol("on_error"));
auto cb = handle->Get(SYM_ON_ERROR);
if (cb->IsFunction())
{
v8::Handle<v8::Value> argv[] = {v8::Exception::Error(v8::String::New(curl_easy_strerror(msg->data.result)))};
cb->ToObject()->CallAsFunction(handle, 1, argv);
}
}
// curl_easy_getinfo
template<typename T, typename S>
static v8::Handle<v8::Value> getinfo(const v8::Arguments &args)
{
T result;
NodeCurl * node_curl = unwrap(args.This());
CURLINFO info = (CURLINFO)args[0]->Int32Value();
CURLcode code = curl_easy_getinfo(node_curl->curl, info, &result);
if (code != CURLE_OK)
{
return raise("curl_easy_getinfo failed", curl_easy_strerror(code));
}
return S::New(result);
}
static v8::Handle<v8::Value> getinfo_int(const v8::Arguments & args)
{
return getinfo<int, v8::Integer>(args);
}
static v8::Handle<v8::Value> getinfo_str(const v8::Arguments & args)
{
return getinfo<char*,v8::String>(args);
}
static v8::Handle<v8::Value> getinfo_double(const v8::Arguments & args)
{
return getinfo<double, v8::Number>(args);
}
// curl_easy_setopt
template<typename T>
v8::Handle<v8::Value> setopt(v8::Handle<v8::Value> option, T value)
{
return v8::Integer::New(
curl_easy_setopt(
curl,
(CURLoption)option->Int32Value(),
value
)
);
}
static v8::Handle<v8::Value> setopt_int(const v8::Arguments & args)
{
return unwrap(args.This())->setopt(args[0], args[1]->Int32Value());
}
static v8::Handle<v8::Value> setopt_str(const v8::Arguments & args)
{
return unwrap(args.This())->setopt(args[0], *v8::String::Utf8Value(args[1]) );
}
static v8::Handle<v8::Value> raise(const char *data, const char *reason = NULL)
{
static char message[256];
const char *what = data;
if (reason)
{
snprintf(message, sizeof(message), "%s: %s", data, reason);
what = message;
}
return v8::ThrowException(v8::Exception::Error(v8::String::New(what)));
}
template<typename T>
static void export_curl_options(T t, const char *group_name, CurlOption *options, int len)
{
auto node_options = v8::Object::New();
for (int i=0; i<len; ++i)
{
const auto & option = options[i];
node_options->Set(
v8::String::NewSymbol(option.name),
v8::Integer::New(option.value)
);
}
t->Set(v8::String::NewSymbol(group_name), node_options);
}
// node js functions
static v8::Handle<v8::Value> New(const v8::Arguments & args)
{
new NodeCurl(args.This());
return args.This();
}
// int process()
static v8::Handle<v8::Value> process(const v8::Arguments & args)
{
if (running_handles > 0)
{
CURLMcode code;
int max_fd = FD_SETSIZE;
fd_set read_fds;
fd_set write_fds;
fd_set error_fds;
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
FD_ZERO(&error_fds);
// use select because of libuv didn't support sockfd well
code = curl_multi_fdset(curlm, &read_fds, &write_fds, &error_fds, &max_fd);
if (code != CURLM_OK)
{
return raise("curl_multi_fdset failed", curl_multi_strerror(code));
}
printf("maxfd returns %d\n", max_fd);
if (max_fd > 0)
{
timeval tv = {0};
int n = select(max_fd+1, &read_fds, &write_fds, &error_fds, &tv);
printf("selecting returns %d\n", n);
if (n == 0)
{
return v8::Integer::New(running_handles);
}
}
do
{
code = curl_multi_perform(curlm, &running_handles);
} while ( code == CURLM_CALL_MULTI_PERFORM );
if (code != CURLM_OK)
{
return raise("curl_multi_perform failed", curl_multi_strerror(code));
}
int msgs = 0;
CURLMsg * msg = NULL;
while ( (msg = curl_multi_info_read(curlm, &msgs)) )
{
printf("msgs: %d, running_handles: %d\n", msgs, running_handles);
if (msg->msg == CURLMSG_DONE)
{
auto curl = curls[msg->easy_handle];
if (msg->data.result == CURLE_OK)
curl->on_end(msg);
else
curl->on_error(msg);
code = curl_multi_remove_handle(curlm, msg->easy_handle);
if (code != CURLM_OK)
{
return raise("curl_multi_remove_handle failed", curl_multi_strerror(code));
}
}
puts("done.");
}
}
return v8::Integer::New(running_handles);
}
// perform()
static v8::Handle<v8::Value> perform(const v8::Arguments & args)
{
NodeCurl *curl = unwrap(args.This());
CURLMcode code = curl_multi_add_handle(curlm, curl->curl);
if (code != CURLM_OK)
{
return raise("curl_multi_add_handle failed", curl_multi_strerror(code));
}
++running_handles;
return args.This();
}
public:
static v8::Handle<v8::Value> Initialize(v8::Handle<v8::Object> target)
{
// Initialize curl
CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
if (code != CURLE_OK)
{
return raise("curl_global_init faield");
}
curlm = curl_multi_init();
if (curlm == NULL)
{
return raise("curl_multi_init failed");
}
// Initialize node-curl
auto t = v8::FunctionTemplate::New(New);
t->InstanceTemplate()->SetInternalFieldCount(1);
// Set prototype methods
NODE_SET_PROTOTYPE_METHOD(t, "perform_", perform);
NODE_SET_PROTOTYPE_METHOD(t, "setopt_int_", setopt_int);
NODE_SET_PROTOTYPE_METHOD(t, "setopt_str_", setopt_str);
NODE_SET_PROTOTYPE_METHOD(t, "getinfo_int_", getinfo_int);
NODE_SET_PROTOTYPE_METHOD(t, "getinfo_str_", getinfo_str);
NODE_SET_PROTOTYPE_METHOD(t, "getinfo_double_", getinfo_double);
NODE_SET_METHOD(t, "process_", process);
// Set curl constants
#include "string_options.h"
#include "integer_options.h"
#include "string_infos.h"
#include "integer_infos.h"
#include "double_infos.h"
NODE_CURL_EXPORT(string_options);
NODE_CURL_EXPORT(integer_options);
NODE_CURL_EXPORT(string_infos);
NODE_CURL_EXPORT(integer_infos);
NODE_CURL_EXPORT(double_infos);
target->Set(v8::String::NewSymbol("Curl"), t->GetFunction());
return target;
}
};
CURLM * NodeCurl::curlm = NULL;
int NodeCurl::running_handles = 0;
bool NodeCurl::is_ref = false;
std::map< CURL*, NodeCurl* > NodeCurl::curls;
#endif

11
src/string_infos.h Normal file
View file

@ -0,0 +1,11 @@
// generated by generate_curl_options_list.sh at Mon, 13 Feb 2012 21:40:10 +0800
CurlOption string_infos[] = {
{"EFFECTIVE_URL", CURLINFO_EFFECTIVE_URL},
{"CONTENT_TYPE", CURLINFO_CONTENT_TYPE},
{"PRIVATE", CURLINFO_PRIVATE},
{"FTP_ENTRY_PATH", CURLINFO_FTP_ENTRY_PATH},
{"REDIRECT_URL", CURLINFO_REDIRECT_URL},
{"PRIMARY_IP", CURLINFO_PRIMARY_IP},
{"RTSP_SESSION_ID", CURLINFO_RTSP_SESSION_ID},
{"LOCAL_IP", CURLINFO_LOCAL_IP},
};

84
src/string_options.h Normal file
View file

@ -0,0 +1,84 @@
// generated by generate_curl_options_list.sh at Mon, 13 Feb 2012 21:40:10 +0800
CurlOption string_options[] = {
{"FILE", CURLOPT_FILE},
{"URL", CURLOPT_URL},
{"PROXY", CURLOPT_PROXY},
{"USERPWD", CURLOPT_USERPWD},
{"PROXYUSERPWD", CURLOPT_PROXYUSERPWD},
{"RANGE", CURLOPT_RANGE},
{"INFILE", CURLOPT_INFILE},
{"ERRORBUFFER", CURLOPT_ERRORBUFFER},
{"POSTFIELDS", CURLOPT_POSTFIELDS},
{"REFERER", CURLOPT_REFERER},
{"FTPPORT", CURLOPT_FTPPORT},
{"USERAGENT", CURLOPT_USERAGENT},
{"COOKIE", CURLOPT_COOKIE},
{"HTTPHEADER", CURLOPT_HTTPHEADER},
{"HTTPPOST", CURLOPT_HTTPPOST},
{"SSLCERT", CURLOPT_SSLCERT},
{"KEYPASSWD", CURLOPT_KEYPASSWD},
{"QUOTE", CURLOPT_QUOTE},
{"WRITEHEADER", CURLOPT_WRITEHEADER},
{"COOKIEFILE", CURLOPT_COOKIEFILE},
{"CUSTOMREQUEST", CURLOPT_CUSTOMREQUEST},
{"STDERR", CURLOPT_STDERR},
{"POSTQUOTE", CURLOPT_POSTQUOTE},
{"WRITEINFO", CURLOPT_WRITEINFO},
{"PROGRESSDATA", CURLOPT_PROGRESSDATA},
{"INTERFACE", CURLOPT_INTERFACE},
{"KRBLEVEL", CURLOPT_KRBLEVEL},
{"CAINFO", CURLOPT_CAINFO},
{"TELNETOPTIONS", CURLOPT_TELNETOPTIONS},
{"RANDOM_FILE", CURLOPT_RANDOM_FILE},
{"EGDSOCKET", CURLOPT_EGDSOCKET},
{"COOKIEJAR", CURLOPT_COOKIEJAR},
{"SSL_CIPHER_LIST", CURLOPT_SSL_CIPHER_LIST},
{"SSLCERTTYPE", CURLOPT_SSLCERTTYPE},
{"SSLKEY", CURLOPT_SSLKEY},
{"SSLKEYTYPE", CURLOPT_SSLKEYTYPE},
{"SSLENGINE", CURLOPT_SSLENGINE},
{"PREQUOTE", CURLOPT_PREQUOTE},
{"DEBUGDATA", CURLOPT_DEBUGDATA},
{"CAPATH", CURLOPT_CAPATH},
{"SHARE", CURLOPT_SHARE},
{"ACCEPT_ENCODING", CURLOPT_ACCEPT_ENCODING},
{"PRIVATE", CURLOPT_PRIVATE},
{"HTTP200ALIASES", CURLOPT_HTTP200ALIASES},
{"SSL_CTX_DATA", CURLOPT_SSL_CTX_DATA},
{"NETRC_FILE", CURLOPT_NETRC_FILE},
{"IOCTLDATA", CURLOPT_IOCTLDATA},
{"FTP_ACCOUNT", CURLOPT_FTP_ACCOUNT},
{"COOKIELIST", CURLOPT_COOKIELIST},
{"FTP_ALTERNATIVE_TO_USER", CURLOPT_FTP_ALTERNATIVE_TO_USER},
{"SOCKOPTDATA", CURLOPT_SOCKOPTDATA},
{"SSH_PUBLIC_KEYFILE", CURLOPT_SSH_PUBLIC_KEYFILE},
{"SSH_PRIVATE_KEYFILE", CURLOPT_SSH_PRIVATE_KEYFILE},
{"SSH_HOST_PUBLIC_KEY_MD5", CURLOPT_SSH_HOST_PUBLIC_KEY_MD5},
{"OPENSOCKETDATA", CURLOPT_OPENSOCKETDATA},
{"COPYPOSTFIELDS", CURLOPT_COPYPOSTFIELDS},
{"SEEKDATA", CURLOPT_SEEKDATA},
{"CRLFILE", CURLOPT_CRLFILE},
{"ISSUERCERT", CURLOPT_ISSUERCERT},
{"USERNAME", CURLOPT_USERNAME},
{"PASSWORD", CURLOPT_PASSWORD},
{"PROXYUSERNAME", CURLOPT_PROXYUSERNAME},
{"PROXYPASSWORD", CURLOPT_PROXYPASSWORD},
{"NOPROXY", CURLOPT_NOPROXY},
{"SOCKS5_GSSAPI_SERVICE", CURLOPT_SOCKS5_GSSAPI_SERVICE},
{"SSH_KNOWNHOSTS", CURLOPT_SSH_KNOWNHOSTS},
{"SSH_KEYDATA", CURLOPT_SSH_KEYDATA},
{"MAIL_FROM", CURLOPT_MAIL_FROM},
{"MAIL_RCPT", CURLOPT_MAIL_RCPT},
{"RTSP_SESSION_ID", CURLOPT_RTSP_SESSION_ID},
{"RTSP_STREAM_URI", CURLOPT_RTSP_STREAM_URI},
{"RTSP_TRANSPORT", CURLOPT_RTSP_TRANSPORT},
{"INTERLEAVEDATA", CURLOPT_INTERLEAVEDATA},
{"CHUNK_DATA", CURLOPT_CHUNK_DATA},
{"FNMATCH_DATA", CURLOPT_FNMATCH_DATA},
{"RESOLVE", CURLOPT_RESOLVE},
{"TLSAUTH_USERNAME", CURLOPT_TLSAUTH_USERNAME},
{"TLSAUTH_PASSWORD", CURLOPT_TLSAUTH_PASSWORD},
{"TLSAUTH_TYPE", CURLOPT_TLSAUTH_TYPE},
{"CLOSESOCKETDATA", CURLOPT_CLOSESOCKETDATA},
{"DNS_SERVERS", CURLOPT_DNS_SERVERS},
};

15
wscript Normal file
View file

@ -0,0 +1,15 @@
def set_options(opt):
opt.tool_options('compiler_cxx')
def configure(conf):
conf.check_tool('compiler_cxx')
conf.check_tool('node_addon')
conf.env.append_unique('CXXFLAGS', ['-Wall', '-O2', '-std=c++0x'])
conf.env['LIB_CURL'] = 'curl'
def build(bld):
obj = bld.new_task_gen('cxx', 'shlib', 'node_addon', uselib="CURL")
obj.target = 'node-curl'
obj.source = [
'src/node-curl.cc',
]