node-curl/src/node-curl.h
2012-02-14 21:10:54 +08:00

450 lines
11 KiB
C++

#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>
#include <vector>
#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;
static int count;
CURL * curl;
v8::Persistent<v8::Object> handle;
bool in_curlm;
std::vector<curl_slist*> slists;
NodeCurl(v8::Handle<v8::Object> object)
: in_curlm(false)
{
++count;
v8::V8::AdjustAmountOfExternalAllocatedMemory(2*4096);
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");
return;
}
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_function);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
curls[curl] = this;
}
~NodeCurl()
{
--count;
v8::V8::AdjustAmountOfExternalAllocatedMemory(-2*4096);
if (curl)
{
if (in_curlm)
curl_multi_remove_handle(curlm, curl);
curl_easy_cleanup(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> value, void *data)
{
v8::Handle<v8::Object> object = value->ToObject();
NodeCurl * curl = (NodeCurl*)object->GetPointerFromInternalField(0);
curl->close();
}
void close()
{
handle->SetPointerInInternalField(0, NULL);
handle.Dispose();
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)
{
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)
{
static v8::Persistent<v8::String> SYM_ON_WRITE = v8::Persistent<v8::String>::New(v8::String::NewSymbol("on_write"));
v8::Handle<v8::Value> cb = handle->Get(SYM_ON_WRITE);
if (cb->IsFunction())
{
node::Buffer * 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 v8::Persistent<v8::String> SYM_ON_END = v8::Persistent<v8::String>::New(v8::String::NewSymbol("on_end"));
v8::Handle<v8::Value> 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 v8::Persistent<v8::String> SYM_ON_ERROR = v8::Persistent<v8::String>::New(v8::String::NewSymbol("on_error"));
v8::Handle<v8::Value> 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 CType, typename JsClass>
static v8::Handle<v8::Value> getinfo(const v8::Arguments &args)
{
CType 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 JsClass::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);
}
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
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> 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 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)
{
v8::Handle<v8::Object> node_options = v8::Object::New();
for (int i=0; i<len; ++i)
{
const CurlOption & 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));
}
if (max_fd > 0)
{
timeval tv = {0};
int n = select(max_fd+1, &read_fds, &write_fds, &error_fds, &tv);
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)) )
{
if (msg->msg == CURLMSG_DONE)
{
NodeCurl * 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);
curl->in_curlm = false;
if (code != CURLM_OK)
{
return raise("curl_multi_remove_handle failed", curl_multi_strerror(code));
}
}
}
}
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);
curl->in_curlm = true;
if (code != CURLM_OK)
{
return raise("curl_multi_add_handle failed", curl_multi_strerror(code));
}
++running_handles;
return args.This();
}
static v8::Handle<v8::Value> get_count(const v8::Arguments & args )
{
return v8::Integer::New(count);
}
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
v8::Handle<v8::FunctionTemplate> 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, "setopt_slist_", setopt_slist);
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_PROTOTYPE_METHOD(t, "getinfo_slist_", getinfo_slist);
NODE_SET_PROTOTYPE_METHOD(t, "close", close);
NODE_SET_METHOD(t, "process_", process);
NODE_SET_METHOD(t, "get_count", get_count);
// Set curl constants
#include "string_options.h"
#include "integer_options.h"
#include "string_infos.h"
#include "integer_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(integer_options);
NODE_CURL_EXPORT(slist_options);
NODE_CURL_EXPORT(string_infos);
NODE_CURL_EXPORT(integer_infos);
NODE_CURL_EXPORT(double_infos);
NODE_CURL_EXPORT(slist_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;
int NodeCurl::count = 0;
#endif