From f8f934a11927592da54fb166ce0e44386c7340cd Mon Sep 17 00:00:00 2001 From: "jiangfriend@gmail.com" Date: Tue, 14 Feb 2012 14:56:03 +0800 Subject: [PATCH] first commit --- .gitignore | 2 + .npmignore | 2 + README.md | 36 ++++ examples/quick-start.js | 19 ++ index.js | 6 + index.toffee | 1 + lib/curl.js | 120 +++++++++++ lib/curl.toffee | 95 +++++++++ package.json | 17 ++ src/double_infos.h | 16 ++ src/generate_curl_options_list.sh | 19 ++ src/integer_infos.h | 21 ++ src/integer_options.h | 91 ++++++++ src/node-curl.cc | 7 + src/node-curl.h | 339 ++++++++++++++++++++++++++++++ src/string_infos.h | 11 + src/string_options.h | 84 ++++++++ wscript | 15 ++ 18 files changed, 901 insertions(+) create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 README.md create mode 100644 examples/quick-start.js create mode 100644 index.js create mode 100644 index.toffee create mode 100644 lib/curl.js create mode 100644 lib/curl.toffee create mode 100644 package.json create mode 100644 src/double_infos.h create mode 100644 src/generate_curl_options_list.sh create mode 100644 src/integer_infos.h create mode 100644 src/integer_options.h create mode 100644 src/node-curl.cc create mode 100644 src/node-curl.h create mode 100644 src/string_infos.h create mode 100644 src/string_options.h create mode 100644 wscript diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78e83bb --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +raw +build diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..78e83bb --- /dev/null +++ b/.npmignore @@ -0,0 +1,2 @@ +raw +build diff --git a/README.md b/README.md new file mode 100644 index 0000000..615be06 --- /dev/null +++ b/README.md @@ -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 + + diff --git a/examples/quick-start.js b/examples/quick-start.js new file mode 100644 index 0000000..a60d162 --- /dev/null +++ b/examples/quick-start.js @@ -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"); +}); diff --git a/index.js b/index.js new file mode 100644 index 0000000..92a1ac4 --- /dev/null +++ b/index.js @@ -0,0 +1,6 @@ +// Generated by CoffeeScript 1.1.4-3 +(function() { + + module.exports = require('./lib/curl'); + +}).call(this); diff --git a/index.toffee b/index.toffee new file mode 100644 index 0000000..1d34e60 --- /dev/null +++ b/index.toffee @@ -0,0 +1 @@ +module.exports = require './lib/curl' diff --git a/lib/curl.js b/lib/curl.js new file mode 100644 index 0000000..1205042 --- /dev/null +++ b/lib/curl.js @@ -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); diff --git a/lib/curl.toffee b/lib/curl.toffee new file mode 100644 index 0000000..eda4a21 --- /dev/null +++ b/lib/curl.toffee @@ -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 diff --git a/package.json b/package.json new file mode 100644 index 0000000..17bc5db --- /dev/null +++ b/package.json @@ -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 ", + "main" : "./lib", + "scripts" : { + "install" : "node-waf configure build || true" + }, + "engines" : { "node": ">= 0.6.0" } +} diff --git a/src/double_infos.h b/src/double_infos.h new file mode 100644 index 0000000..1cf1817 --- /dev/null +++ b/src/double_infos.h @@ -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}, +}; diff --git a/src/generate_curl_options_list.sh b/src/generate_curl_options_list.sh new file mode 100644 index 0000000..f071e64 --- /dev/null +++ b/src/generate_curl_options_list.sh @@ -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 diff --git a/src/integer_infos.h b/src/integer_infos.h new file mode 100644 index 0000000..c244fb8 --- /dev/null +++ b/src/integer_infos.h @@ -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}, +}; diff --git a/src/integer_options.h b/src/integer_options.h new file mode 100644 index 0000000..6c7e047 --- /dev/null +++ b/src/integer_options.h @@ -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}, +}; diff --git a/src/node-curl.cc b/src/node-curl.cc new file mode 100644 index 0000000..5fa4698 --- /dev/null +++ b/src/node-curl.cc @@ -0,0 +1,7 @@ +#include "node-curl.h" + +extern "C" +void init(v8::Handle target) +{ + NodeCurl::Initialize(target); +} diff --git a/src/node-curl.h b/src/node-curl.h new file mode 100644 index 0000000..7a3904b --- /dev/null +++ b/src/node-curl.h @@ -0,0 +1,339 @@ +#ifndef NODE_CURL_NOHE_CURL_H +#define NODE_CURL_NOHE_CURL_H + + +#include +#include +#include +#include +#include +#include + +#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 handle; + NodeCurl(v8::Handle object) + { + object->SetPointerInInternalField(0, this); + handle = v8::Persistent::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 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 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::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 argv[] = { buffer->handle_ }; + cb->ToObject()->CallAsFunction(handle, 1, argv); + } + return n; + } + + void on_end(CURLMsg *msg) + { + static auto SYM_ON_END = v8::Persistent::New(v8::String::NewSymbol("on_end")); + auto cb = handle->Get(SYM_ON_END); + if (cb->IsFunction()) + { + v8::Handle argv[] = {}; + cb->ToObject()->CallAsFunction(handle, 0, argv); + } + } + + void on_error(CURLMsg *msg) + { + static auto SYM_ON_ERROR = v8::Persistent::New(v8::String::NewSymbol("on_error")); + auto cb = handle->Get(SYM_ON_ERROR); + if (cb->IsFunction()) + { + v8::Handle argv[] = {v8::Exception::Error(v8::String::New(curl_easy_strerror(msg->data.result)))}; + cb->ToObject()->CallAsFunction(handle, 1, argv); + } + } + + // curl_easy_getinfo + template + static v8::Handle 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 getinfo_int(const v8::Arguments & args) + { + return getinfo(args); + } + + static v8::Handle getinfo_str(const v8::Arguments & args) + { + return getinfo(args); + } + + static v8::Handle getinfo_double(const v8::Arguments & args) + { + return getinfo(args); + } + + // curl_easy_setopt + template + v8::Handle setopt(v8::Handle option, T value) + { + return v8::Integer::New( + curl_easy_setopt( + curl, + (CURLoption)option->Int32Value(), + value + ) + ); + } + + static v8::Handle setopt_int(const v8::Arguments & args) + { + return unwrap(args.This())->setopt(args[0], args[1]->Int32Value()); + } + + static v8::Handle setopt_str(const v8::Arguments & args) + { + return unwrap(args.This())->setopt(args[0], *v8::String::Utf8Value(args[1]) ); + } + + + static v8::Handle 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 + 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; iSet( + 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 New(const v8::Arguments & args) + { + new NodeCurl(args.This()); + return args.This(); + } + + // int process() + static v8::Handle 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 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 Initialize(v8::Handle 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 diff --git a/src/string_infos.h b/src/string_infos.h new file mode 100644 index 0000000..4b30808 --- /dev/null +++ b/src/string_infos.h @@ -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}, +}; diff --git a/src/string_options.h b/src/string_options.h new file mode 100644 index 0000000..531eb82 --- /dev/null +++ b/src/string_options.h @@ -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}, +}; diff --git a/wscript b/wscript new file mode 100644 index 0000000..dbeab4a --- /dev/null +++ b/wscript @@ -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', + ]