├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE-MIT ├── README.md ├── binding.gyp ├── examples ├── curl.js ├── curl.toffee ├── low-level.js ├── post-multi-part.js ├── post-multi-part.toffee ├── pressure.js ├── pressure.toffee ├── quick-start.js ├── test-get-info.js ├── test.js └── test.toffee ├── index.js ├── index.toffee ├── lib ├── Curl.js ├── Curl.toffee ├── CurlBuilder.js └── CurlBuilder.toffee ├── package.json ├── src ├── generate_curl_options_list.sh ├── node-curl.cc └── node-curl.h └── wscript /.gitignore: -------------------------------------------------------------------------------- 1 | raw 2 | build 3 | .lock-wscript 4 | node_modules 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .lock-wscript 2 | raw/ 3 | build/ 4 | build/.* 5 | src/*_infos.h 6 | src/*_options.h 7 | *.tgz 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | - 0.10 5 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Miao Jiang 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-curl [![Build Status](https://secure.travis-ci.org/jiangmiao/node-curl.png?branch=master)](http://travis-ci.org/jiangmiao/node-curl) 2 | ========= 3 | 4 | node cURL wrapper, support all options and infos. 5 | 6 | Quick Start 7 | ----------- 8 | 9 | * quick start 10 | 11 | curl = require('node-curl'); 12 | curl('www.google.com', function(err) { 13 | console.info(this.status); 14 | console.info('-----'); 15 | console.info(this.body); 16 | console.info('-----'); 17 | console.info(this.info('SIZE_DOWNLOAD')); 18 | }); 19 | 20 | * with options 21 | 22 | curl = require('node-curl') 23 | curl('www.google.com', {VERBOSE: 1, RAW: 1}, function(err) { 24 | console.info(this); 25 | }); 26 | 27 | * run the example/test.js 28 | 29 | node examples/test.js 30 | 31 | Usage 32 | ----- 33 | 34 | * curl 35 | 36 | curl(url, [options = {}], callback) 37 | callback includes 1 parameters (error) 38 | result is stored in curl 39 | 40 | * Retrieve Data from curl 41 | 42 | members: 43 | status - Http Response code 44 | body - Http body 45 | header - Http header 46 | 47 | url - the url set by curl(...) 48 | options - the options set by curl(...) 49 | defaultOptions - the defaultOptions 50 | effectiveOptions - the options curl used 51 | 52 | methods: 53 | info(name) - Get information of result, see 'info' section 54 | 55 | REMARK: 56 | If the http is redirected, then header will contain at least 2 http headers. 57 | 58 | 59 | * Curl Control 60 | 61 | members 62 | debug (default: false) 63 | - logging node-curl debug info 64 | 65 | methods: 66 | void reset() 67 | - reset curl and set options to default options 68 | 69 | void setDefaultOptions(options, reset = true) 70 | - set default options 71 | 72 | curl create(defaultOptions) 73 | - create a new curl with default options 74 | 75 | Options 76 | ------- 77 | * Any cURL Easy Options 78 | 79 | eg: CURLOPT_VERBOSE will be VERBOSE, CURLOPT_HEADER will be HEADER 80 | 81 | Full list at http://curl.haxx.se/libcurl/c/curl_easy_setopt.html 82 | 83 | * node-curl Extra Options 84 | 85 | RAW - Returns Buffer instead of String in result.body 86 | DEBUG - Replace curl.debug 87 | 88 | * About slist parameters 89 | 90 | node-curl support slist which map to Javascript Array 91 | 92 | eg: 93 | HTTPHEADER: ['FOO', 'BAR'] 94 | HTTPHEADER: 'FOO' 95 | 96 | any non-array parameter will convert to [ parameter.toString() ] 97 | 98 | Infos 99 | ----- 100 | * Any cURL Info options 101 | 102 | eg: CURLINFO_EFFECTIVE_URL will be EFFETCTIVE_URL 103 | 104 | full list at http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html 105 | 106 | 107 | * About slist 108 | 109 | slist will be returns in Array 110 | eg: CURLINFO_COOKIELIST 111 | 112 | MultiPart Upload 113 | ---------------- 114 | Use MULTIPART option 115 | 116 | There are 4 options in MULTIPART, `name`, `file`, `type`, `contents` 117 | 118 | ```javascript 119 | curl('127.0.0.1/upload.php', { 120 | MULTIPART: [ 121 | {name: 'file', file: '/file/path', type: 'text/html'}, 122 | {name: 'sumbit', contents: 'send'} 123 | ] 124 | }, function(e) { 125 | console.log(e); 126 | console.log(this.body); 127 | this.close() 128 | }); 129 | ``` 130 | 131 | Low Level Curl Usage 132 | -------------------- 133 | 134 | require 'node-curl/lib/Curl' 135 | 136 | Methods: 137 | 138 | Curl setopt(optionName, optionValue) 139 | Curl perform() 140 | Curl on(eventType, callback) 141 | Mixed getinfo(infoName) 142 | 143 | Events: 144 | 145 | 'data', function(Buffer chunk) {} 146 | 'header', function(Buffer chunk) {} 147 | 'error', function(Error error) {} 148 | 'end', function() {} 149 | 150 | Example: examples/low-level.js 151 | 152 | var Curl = require('node-curl/lib/Curl') 153 | 154 | var p = console.log; 155 | var url = process.argv[2]; 156 | 157 | var curl = new Curl(); 158 | 159 | if (!url) 160 | url = 'www.yahoo.com'; 161 | 162 | curl.setopt('URL', url); 163 | curl.setopt('CONNECTTIMEOUT', 2); 164 | 165 | // on 'data' must be returns chunk.length, or means interrupt the transfer 166 | curl.on('data', function(chunk) { 167 | p("receive " + chunk.length); 168 | return chunk.length; 169 | }); 170 | 171 | curl.on('header', function(chunk) { 172 | p("receive header " + chunk.length); 173 | return chunk.length; 174 | }) 175 | 176 | // curl.close() should be called in event 'error' and 'end' if the curl won't use any more. 177 | // or the resource will not release until V8 garbage mark sweep. 178 | curl.on('error', function(e) { 179 | p("error: " + e.message); 180 | curl.close(); 181 | }); 182 | 183 | 184 | curl.on('end', function() { 185 | p('code: ' + curl.getinfo('RESPONSE_CODE')); 186 | p('done.'); 187 | curl.close(); 188 | }); 189 | 190 | curl.perform(); 191 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'node-curl', 5 | 'cflags': ['-Wall', '-O1', '-g', '-fno-inline-functions'], 6 | 'cflags_cc': ['-Wall', '-O1', '-g', '-fno-inline-functions'], 7 | 'sources': ['src/node-curl.cc'], 8 | 'libraries': ['-lcurl'] 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples/curl.js: -------------------------------------------------------------------------------- 1 | // Generated by ToffeeScript 1.6.2-5 2 | (function() { 3 | var curl, err, p, res, 4 | _this = this; 5 | 6 | curl = require('../index'); 7 | 8 | p = console.info; 9 | 10 | curl(process.argv[2], { 11 | VERBOSE: 1, 12 | DEBUG: 1, 13 | FOLLOWLOCATION: 1 14 | }, function() { 15 | err = arguments[0], res = arguments[1]; 16 | if (err) { 17 | return p(err); 18 | } else { 19 | return p(res); 20 | } 21 | }); 22 | 23 | }).call(this); 24 | -------------------------------------------------------------------------------- /examples/curl.toffee: -------------------------------------------------------------------------------- 1 | curl = require '../index' 2 | p = console.info 3 | 4 | err, res = curl! process.argv[2], {VERBOSE: 1, DEBUG: 1, FOLLOWLOCATION: 1} 5 | if err 6 | p err 7 | else 8 | p res 9 | -------------------------------------------------------------------------------- /examples/low-level.js: -------------------------------------------------------------------------------- 1 | var Curl = require('../lib/Curl'); 2 | 3 | var p = console.log; 4 | var url = process.argv[2]; 5 | 6 | var curl = new Curl(); 7 | 8 | if (!url) 9 | url = 'www.yahoo.com'; 10 | 11 | curl.setopt('URL', url); 12 | curl.setopt('CONNECTTIMEOUT', 2); 13 | curl.setopt('VERBOSE', 1); 14 | 15 | // on 'data' must be returns chunk.length, or means interrupt the transfer 16 | curl.on('data', function(chunk) { 17 | p("receive " + chunk.length); 18 | return chunk.length; 19 | }); 20 | 21 | curl.on('header', function(chunk) { 22 | p("receive header " + chunk.length); 23 | return chunk.length; 24 | }) 25 | 26 | // curl.close() should be called in event 'error' and 'end' if the curl won't use any more. 27 | // or the resource will not release until V8 garbage mark sweep. 28 | curl.on('error', function(e) { 29 | p("error: " + e.message); 30 | curl.close(); 31 | }); 32 | 33 | 34 | curl.on('end', function() { 35 | p('code: ' + curl.getinfo('RESPONSE_CODE')); 36 | p('done.'); 37 | curl.close(); 38 | }); 39 | 40 | curl.perform(); 41 | -------------------------------------------------------------------------------- /examples/post-multi-part.js: -------------------------------------------------------------------------------- 1 | // Generated by ToffeeScript 1.6.2-5 2 | (function() { 3 | var curl, p; 4 | 5 | curl = require('../index'); 6 | 7 | p = console.log; 8 | 9 | p('start'); 10 | 11 | curl('127.0.0.1/upload.php', { 12 | multipart: [ 13 | { 14 | name: 'file', 15 | file: '/home/miao/test.js', 16 | type: 'text/html' 17 | }, { 18 | name: 'sumbit', 19 | contents: 'send' 20 | } 21 | ] 22 | }, function(e) { 23 | console.log(e); 24 | console.log(this.body); 25 | return this.close(); 26 | }); 27 | 28 | }).call(this); 29 | -------------------------------------------------------------------------------- /examples/post-multi-part.toffee: -------------------------------------------------------------------------------- 1 | curl = require '../index' 2 | p = console.log 3 | p 'start' 4 | 5 | curl '127.0.0.1/upload.php', { 6 | multipart: [ 7 | {name: 'file', file: '/home/miao/test.js', type: 'text/html'}, 8 | {name: 'sumbit', contents: 'send'} 9 | ], 10 | }, (e) -> 11 | console.log e 12 | console.log @body 13 | @close() 14 | -------------------------------------------------------------------------------- /examples/pressure.js: -------------------------------------------------------------------------------- 1 | // Generated by ToffeeScript 1.6.2-5 2 | (function() { 3 | var Curl, assert, i, j, next, _fn, _i; 4 | 5 | Curl = require('../index'); 6 | 7 | assert = require('assert'); 8 | 9 | j = 0; 10 | 11 | (next = function() { 12 | console.info("curl instances: ", Curl.get_count()); 13 | return setTimeout(next, 1000); 14 | })(); 15 | 16 | _fn = function() { 17 | var curl, once; 18 | curl = Curl.create(); 19 | return (once = function() { 20 | var err, res, 21 | _this = this; 22 | curl('localhost/test.html', function() { 23 | err = arguments[0], res = arguments[1]; 24 | assert.equal(res.body.length, 1468); 25 | if (++j % 100 === 0) { 26 | console.info(j); 27 | } 28 | if (j < 500000) { 29 | return once(); 30 | } 31 | }); 32 | })(); 33 | }; 34 | for (i = _i = 1; _i <= 100; i = ++_i) { 35 | _fn(); 36 | } 37 | 38 | }).call(this); 39 | -------------------------------------------------------------------------------- /examples/pressure.toffee: -------------------------------------------------------------------------------- 1 | Curl = require '../index' 2 | assert = require 'assert' 3 | j = 0; 4 | do next = -> 5 | console.info "curl instances: ", Curl.get_count() 6 | setTimeout next, 1000 7 | 8 | for i in [1..100] 9 | do -> 10 | curl = Curl.create() 11 | do once = -> 12 | err, res = curl! 'localhost/test.html' 13 | assert.equal res.body.length, 1468 14 | if ++j % 100 == 0 15 | console.info j 16 | if j < 500000 17 | once() 18 | # res.close() 19 | -------------------------------------------------------------------------------- /examples/quick-start.js: -------------------------------------------------------------------------------- 1 | curl = require('../index'); 2 | 3 | // second argument 'options' is omitted. 4 | url = 'www.google.com'; 5 | console.info("GET " + url); 6 | curl('www.google.com', function(err) { 7 | console.info("\x1b[32mGet " + this.url + " finished.\x1b[0m"); 8 | console.info("\tstatus: " + this.status); 9 | console.info("\tbody length: " + this.body.length); 10 | console.info("\tinfo SIZE_DOWNLOAD: " + this.info('SIZE_DOWNLOAD')); 11 | console.info("\tinfo EFFECTIVE_URL " + this.info('EFFECTIVE_URL')); 12 | this.close(); 13 | }); 14 | 15 | // because we uses curl in parallel. and each curl is only for one session. 16 | // so use curl.create(defaultOptions = {}) to create new curl/session. 17 | curl2 = curl.create({RAW: 1}); 18 | url2 = 'www.google.com'; 19 | options2 = {FOLLOWLOCATION: 1}; 20 | console.info("GET " + url + " with default options " + JSON.stringify(curl2.defaultOptions) + ' and options ' + JSON.stringify(options2)); 21 | curl2(url2, options2, function(err) { 22 | console.info("\x1b[32mGet " + this.url + " with " + JSON.stringify(this.effectiveOptions) + " finished.\x1b[0m"); 23 | console.info("\tstatus: " + this.status); 24 | console.info("\tbody length: " + this.body.length); 25 | console.info("\tinfo SIZE_DOWNLOAD: " + this.info('SIZE_DOWNLOAD')); 26 | console.info("\tinfo EFFECTIVE_URL " + this.info('EFFECTIVE_URL')); 27 | this.close(); 28 | }); 29 | -------------------------------------------------------------------------------- /examples/test-get-info.js: -------------------------------------------------------------------------------- 1 | Curl = require('../index'); 2 | 3 | defaultOptions = {CONNECTTIMEOUT: 2}; 4 | keys = ["EFFECTIVE_URL", "CONTENT_TYPE", "PRIVATE", "FTP_ENTRY_PATH", "REDIRECT_URL", "PRIMARY_IP", "RTSP_SESSION_ID", "LOCAL_IP"]; 5 | 6 | function print(text, color) { 7 | if (!/^\d+$/.test(color)) { 8 | color = {red: 31, green: 32, yellow: 33}[color]; 9 | } 10 | 11 | if (!color) 12 | return console.info(text); 13 | 14 | console.info("\x1b[" + color + "m" + text + "\x1b[0m"); 15 | } 16 | 17 | requests = [ 18 | ['www.nodejs.org'], 19 | ['www.yahoo.com'], 20 | ['https://www.google.com', {VERBOSE: 1, RAW: 1}] 21 | ]; 22 | 23 | requests.forEach(function(request) { 24 | url = request[0]; 25 | options = request[1]; 26 | if (!options) 27 | options = {}; 28 | 29 | curl = Curl.create(defaultOptions); 30 | print('GET ' + url, 'green') 31 | curl(url, options, function(err) { 32 | print('GET ' + this.url + ' ' + JSON.stringify(this.effectiveOptions) + " finished.", 'green'); 33 | 34 | if (err) 35 | return print(err, 'red'); 36 | 37 | self = this; 38 | keys.forEach(function(key) { 39 | print("\t" + key + ": [" + self.info(key) + "]", 'yellow'); 40 | }) 41 | 42 | print("\tbody length: " + this.body.length); 43 | this.close(); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /examples/test.js: -------------------------------------------------------------------------------- 1 | // Generated by ToffeeScript 1.6.2-5 2 | (function() { 3 | var cookieFile, curl, err, fs, options, p, stream, util, 4 | _this = this; 5 | 6 | curl = require('../index'); 7 | 8 | fs = require('fs'); 9 | 10 | util = require('util'); 11 | 12 | p = console.info; 13 | 14 | cookieFile = 'node-curl-cookie.txt'; 15 | 16 | options = { 17 | VERBOSE: 1, 18 | COOKIEFILE: cookieFile, 19 | COOKIEJAR: cookieFile, 20 | ACCEPT_ENCODING: 'gzip', 21 | RAW: 1 22 | }; 23 | 24 | curl.debug = 1; 25 | 26 | curl.setDefaultOptions(options); 27 | 28 | curl('www.google.com', function() { 29 | err = arguments[0]; 30 | p("\x1b[33m" + util.inspect(curl.info('COOKIELIST')) + "\x1b[0m"); 31 | curl.reset(); 32 | curl('www.yahoo.com', function() { 33 | err = arguments[0]; 34 | p("\x1b[33m" + util.inspect(curl.info('COOKIELIST')) + "\x1b[0m"); 35 | p("body length " + curl.body.length); 36 | p("\x1b[33mText in " + cookieFile + "\x1b[0m"); 37 | p("----"); 38 | stream = fs.createReadStream(cookieFile); 39 | stream.pipe(process.stdout); 40 | return stream.on('end', function() { 41 | var _this = this; 42 | p("----"); 43 | curl.close(); 44 | p("deleting " + cookieFile); 45 | fs.unlink(cookieFile, function() { 46 | return p("done."); 47 | }); 48 | }); 49 | }); 50 | }); 51 | 52 | }).call(this); 53 | -------------------------------------------------------------------------------- /examples/test.toffee: -------------------------------------------------------------------------------- 1 | curl = require '../index' 2 | fs = require 'fs' 3 | util = require 'util' 4 | p = console.info 5 | 6 | cookieFile = 'node-curl-cookie.txt' 7 | options = { 8 | VERBOSE: 1 9 | COOKIEFILE: cookieFile 10 | COOKIEJAR: cookieFile 11 | ACCEPT_ENCODING: 'gzip' 12 | RAW: 1 13 | } 14 | curl.debug = 1 15 | curl.setDefaultOptions options 16 | err = curl! 'www.google.com' 17 | p "\x1b[33m" + util.inspect(curl.info('COOKIELIST')) + "\x1b[0m" 18 | curl.reset() 19 | err = curl! 'www.yahoo.com' 20 | p "\x1b[33m" + util.inspect(curl.info('COOKIELIST')) + "\x1b[0m" 21 | p "body length #{curl.body.length}" 22 | p "\x1b[33mText in #{cookieFile}\x1b[0m" 23 | p "----" 24 | stream = fs.createReadStream(cookieFile) 25 | stream.pipe(process.stdout) 26 | stream.on 'end', -> 27 | p "----" 28 | curl.close() 29 | p "deleting #{cookieFile}" 30 | fs.unlink! cookieFile 31 | p "done." 32 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Generated by ToffeeScript 1.4.0 2 | (function() { 3 | var CurlBuilder; 4 | 5 | CurlBuilder = require('./lib/CurlBuilder'); 6 | 7 | module.exports = CurlBuilder.create(); 8 | 9 | }).call(this); 10 | -------------------------------------------------------------------------------- /index.toffee: -------------------------------------------------------------------------------- 1 | CurlBuilder = require './lib/CurlBuilder' 2 | module.exports = CurlBuilder.create() 3 | -------------------------------------------------------------------------------- /lib/Curl.js: -------------------------------------------------------------------------------- 1 | // Generated by ToffeeScript 1.6.2-5 2 | (function() { 3 | var Curl, curls, e, id, m, p, 4 | __hasProp = {}.hasOwnProperty; 5 | 6 | try { 7 | Curl = require(__dirname + '/../build/Release/node-curl').Curl; 8 | } catch (_error) { 9 | e = _error; 10 | Curl = require(__dirname + '/../build/default/node-curl').Curl; 11 | } 12 | 13 | Curl.prototype.setopt_user_ = function(option_id, value) { 14 | return this.options[option_id] = value; 15 | }; 16 | 17 | Curl.prototype.setopt_httppost = function(rows) { 18 | var cols, k, option_id, row, v; 19 | this.httppost = (function() { 20 | var _i, _len, _results; 21 | _results = []; 22 | for (_i = 0, _len = rows.length; _i < _len; _i++) { 23 | row = rows[_i]; 24 | cols = []; 25 | for (k in row) { 26 | if (!__hasProp.call(row, k)) continue; 27 | v = row[k]; 28 | k = k.toUpperCase(); 29 | if ((option_id = Curl.httppost_options[k]) != null) { 30 | cols.push(option_id); 31 | if (!(v instanceof Buffer)) { 32 | v = new Buffer(v.toString()); 33 | } 34 | cols.push(v); 35 | } else { 36 | throw new Error("invalid http post option " + k); 37 | } 38 | } 39 | _results.push(cols); 40 | } 41 | return _results; 42 | })(); 43 | this.setopt_httppost_(this.httppost); 44 | return this; 45 | }; 46 | 47 | Curl.prototype.setopt = function(option_name, value) { 48 | var option, option_id; 49 | option = option_name.toUpperCase(); 50 | if ((option_id = Curl.user_options[option]) != null) { 51 | if (option === 'MULTIPART') { 52 | this.setopt_httppost(value); 53 | } else { 54 | this.setopt_user_(option_id, value); 55 | } 56 | } else if ((option_id = Curl.slist_options[option]) != null) { 57 | this.setopt_slist_(option_id, value); 58 | } else if ((option_id = Curl.integer_options[option]) != null) { 59 | this.setopt_int_(option_id, value >> 0); 60 | } else if ((option_id = Curl.string_options[option]) != null) { 61 | if (value == null) { 62 | throw new Error("Cannot set option " + option_name + " to " + value + "."); 63 | } 64 | this.setopt_str_(option_id, value.toString()); 65 | } else { 66 | throw new Error("unsupported option " + option); 67 | } 68 | return this; 69 | }; 70 | 71 | Curl.prototype.getinfo = function(oinfo) { 72 | var info, info_id; 73 | info = oinfo.toUpperCase(); 74 | if ((info_id = Curl.slist_infos[info]) != null) { 75 | return this.getinfo_slist_(info_id); 76 | } else if ((info_id = Curl.integer_infos[info]) != null) { 77 | return this.getinfo_int_(info_id); 78 | } else if ((info_id = Curl.string_infos[info]) != null) { 79 | return this.getinfo_str_(info_id); 80 | } else if ((info_id = Curl.double_infos[info]) != null) { 81 | return this.getinfo_double_(info_id); 82 | } else { 83 | throw new Error("unsupported info " + oinfo); 84 | } 85 | }; 86 | 87 | Curl.user_options = { 88 | RAW: 'RAW', 89 | DEBUG: 'DEBUG', 90 | MULTIPART: 'MULTIPART' 91 | }; 92 | 93 | id = 0; 94 | 95 | curls = {}; 96 | 97 | Curl.prototype.on = function(event, callback) { 98 | var _this = this; 99 | switch (event) { 100 | case 'data': 101 | this.on_write = function(chunk) { 102 | return callback.call(_this, chunk); 103 | }; 104 | break; 105 | case 'header': 106 | this.on_header = function(chunk) { 107 | return callback.call(_this, chunk); 108 | }; 109 | break; 110 | case 'error': 111 | this.on_error = function(e) { 112 | delete curls[_this.id]; 113 | return callback.call(_this, e); 114 | }; 115 | break; 116 | case 'end': 117 | this.on_end = function() { 118 | delete curls[_this.id]; 119 | return callback.call(_this); 120 | }; 121 | break; 122 | default: 123 | throw new Error("invalid event type " + event); 124 | } 125 | return this; 126 | }; 127 | 128 | Curl.prototype.close = function() { 129 | delete curls[this.id]; 130 | return this.close_(); 131 | }; 132 | 133 | Curl.prototype.perform = function() { 134 | this.id = ++id; 135 | curls[this.id] = this; 136 | this.perform_(); 137 | Curl.process(); 138 | return this; 139 | }; 140 | 141 | m = 0; 142 | 143 | p = console.log; 144 | 145 | Curl.process = function() { 146 | var once; 147 | if (Curl.in_process) { 148 | return; 149 | } 150 | return (once = function() { 151 | var n, w; 152 | n = Curl.process_(); 153 | if (n > 0) { 154 | Curl.in_process = true; 155 | if (n > 8192 && m < 10) { 156 | ++m; 157 | return process.nextTick(once); 158 | } else { 159 | m = 0; 160 | w = (8192 - n) * 80 / 8192 >> 0; 161 | if (w < 0) { 162 | w = 0; 163 | } 164 | return setTimeout(once, w); 165 | } 166 | } else { 167 | return Curl.in_process = false; 168 | } 169 | })(); 170 | }; 171 | 172 | module.exports = Curl; 173 | 174 | }).call(this); 175 | -------------------------------------------------------------------------------- /lib/Curl.toffee: -------------------------------------------------------------------------------- 1 | try 2 | {Curl} = require __dirname + '/../build/Release/node-curl' 3 | catch e 4 | {Curl} = require __dirname + '/../build/default/node-curl' 5 | 6 | Curl::setopt_user_ = (option_id, value) -> 7 | @options[option_id] = value 8 | 9 | Curl::setopt_httppost = (rows) -> 10 | # convert object-rows to array-rows 11 | # format 12 | # [ 13 | # [OPTION_ID, VALUE] 14 | # ] 15 | @httppost = for row in rows 16 | cols = [] 17 | for own k, v of row 18 | k = k.toUpperCase() 19 | if (option_id = Curl.httppost_options[k])? 20 | cols.push option_id 21 | unless v instanceof Buffer 22 | v = new Buffer(v.toString()) 23 | cols.push v 24 | else 25 | throw new Error("invalid http post option #{k}") 26 | cols 27 | @setopt_httppost_(@httppost) 28 | @ 29 | 30 | 31 | Curl::setopt = (option_name, value) -> 32 | option = option_name.toUpperCase() 33 | 34 | # slist must be at the top of condition 35 | # the option exists in string_options too 36 | if (option_id = Curl.user_options[option])? 37 | if (option == 'MULTIPART') 38 | @setopt_httppost value 39 | else 40 | @setopt_user_ option_id, value 41 | else if (option_id = Curl.slist_options[option])? 42 | @setopt_slist_ option_id, value 43 | else if (option_id = Curl.integer_options[option])? 44 | @setopt_int_ option_id, value >> 0 45 | else if (option_id = Curl.string_options[option])? 46 | if !value? 47 | throw new Error("Cannot set option #{option_name} to #{value}.") 48 | @setopt_str_ option_id, value.toString() 49 | else 50 | throw new Error("unsupported option #{option}") 51 | @ 52 | 53 | Curl::getinfo = (oinfo) -> 54 | info = oinfo.toUpperCase() 55 | if (info_id = Curl.slist_infos[info])? 56 | @getinfo_slist_(info_id) 57 | else if (info_id = Curl.integer_infos[info])? 58 | @getinfo_int_(info_id) 59 | else if (info_id = Curl.string_infos[info])? 60 | @getinfo_str_(info_id) 61 | else if (info_id = Curl.double_infos[info])? 62 | @getinfo_double_(info_id) 63 | else 64 | throw new Error("unsupported info #{oinfo}") 65 | 66 | Curl.user_options = 67 | RAW: 'RAW' 68 | DEBUG: 'DEBUG' 69 | MULTIPART: 'MULTIPART' 70 | 71 | id = 0 72 | curls = {} 73 | 74 | # on 'data' must be returns the chunk length 75 | Curl::on = (event, callback) -> 76 | switch event 77 | when 'data' 78 | # (Buffer chunk) -> 79 | @on_write = (chunk) => 80 | callback.call @, chunk 81 | when 'header' 82 | @on_header = (chunk) => 83 | callback.call @, chunk 84 | when 'error' 85 | # (Error error) -> 86 | @on_error = (e) => 87 | delete curls[@id] 88 | callback.call @, e 89 | when 'end' 90 | # () -> 91 | @on_end = => 92 | delete curls[@id] 93 | callback.call @ 94 | else 95 | throw new Error("invalid event type #{event}") 96 | @ 97 | 98 | Curl::close = () -> 99 | delete curls[@id] 100 | @close_() 101 | 102 | Curl::perform = -> 103 | @id = ++id 104 | curls[@id] = @ 105 | @perform_() 106 | Curl.process() 107 | @ 108 | 109 | m = 0 110 | p = console.log 111 | Curl.process = -> 112 | if Curl.in_process 113 | return 114 | do once = -> 115 | n = Curl.process_() 116 | if n > 0 117 | Curl.in_process = true 118 | if n > 8192 && m < 10 119 | ++m 120 | process.nextTick once 121 | else 122 | m = 0 123 | w = (8192 - n) * 80 / 8192 >> 0 124 | if w < 0 125 | w = 0 126 | setTimeout once, w 127 | else 128 | Curl.in_process = false 129 | 130 | 131 | 132 | module.exports = Curl 133 | # vim: sw=2 ts=2 sts=2 expandtab : 134 | -------------------------------------------------------------------------------- /lib/CurlBuilder.js: -------------------------------------------------------------------------------- 1 | // Generated by ToffeeScript 1.6.2-5 2 | (function() { 3 | var Curl, CurlBuilder, e, 4 | __hasProp = {}.hasOwnProperty, 5 | __slice = [].slice; 6 | 7 | try { 8 | Curl = require(__dirname + '/Curl'); 9 | } catch (_error) { 10 | e = _error; 11 | Curl = require(__dirname + '/Curl'); 12 | } 13 | 14 | function merge_chunks(chunks, length) { 15 | var chunk, data, position, _i, _len; 16 | data = new Buffer(length); 17 | position = 0; 18 | for (_i = 0, _len = chunks.length; _i < _len; _i++) { 19 | chunk = chunks[_i]; 20 | chunk.copy(data, position); 21 | position += chunk.length; 22 | } 23 | return data; 24 | }; 25 | 26 | CurlBuilder = (function() { 27 | function CurlBuilder() {} 28 | 29 | CurlBuilder.curls = {}; 30 | 31 | CurlBuilder.id = 0; 32 | 33 | CurlBuilder.close_all = function() { 34 | var curl, id, _ref; 35 | _ref = CurlBuilder.curls; 36 | for (id in _ref) { 37 | if (!__hasProp.call(_ref, id)) continue; 38 | curl = _ref[id]; 39 | curl.end(); 40 | delete CurlBuilder.curls[id]; 41 | } 42 | return CurlBuilder; 43 | }; 44 | 45 | CurlBuilder.create = function(defaultOptions) { 46 | function curl() { 47 | return curl.perform.apply(curl, arguments); 48 | }; 49 | curl.perform = function() { 50 | var args, c, cb, header_length, k, length, v, _ref, _ref1, _ref2, _ref3, _ref4; 51 | args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 52 | if (this.running) { 53 | throw new Error('the cURL session is busy, use curl.create to create another cURL Session'); 54 | } 55 | if (!this.curl_) { 56 | throw new Error('the cURL is closed.'); 57 | } 58 | this.running = true; 59 | cb = args.pop(); 60 | this.url = args[0], this.options = args[1]; 61 | if ((_ref = this.options) == null) { 62 | this.options = {}; 63 | } 64 | length = 0; 65 | header_length = 0; 66 | this.debug = (_ref1 = (_ref2 = this.defaultOptions.DEBUG) != null ? _ref2 : this.options.DEBUG) != null ? _ref1 : this.debug; 67 | this.effectiveOptions = {}; 68 | _ref3 = this.defaultOptions; 69 | for (k in _ref3) { 70 | v = _ref3[k]; 71 | this.effectiveOptions[k] = v; 72 | } 73 | _ref4 = this.options; 74 | for (k in _ref4) { 75 | v = _ref4[k]; 76 | this.effectiveOptions[k] = v; 77 | } 78 | this.setOptions(this.effectiveOptions); 79 | this.setOptions({ 80 | URL: this.url 81 | }); 82 | c = this.curl_; 83 | c.chunks = []; 84 | c.header_chunks = []; 85 | c.on('data', function(chunk) { 86 | curl.log("receive " + chunk.length + " bytes"); 87 | c.chunks.push(chunk); 88 | length += chunk.length; 89 | return chunk.length; 90 | }); 91 | c.on('header', function(chunk) { 92 | curl.log("receive " + chunk.length + " header"); 93 | c.header_chunks.push(chunk); 94 | header_length += chunk.length; 95 | return chunk.length; 96 | }); 97 | c.on('end', function() { 98 | var data, header, 99 | _this = this; 100 | curl.log("receive succeeded."); 101 | curl.running = false; 102 | data = merge_chunks(c.chunks, length); 103 | header = merge_chunks(c.header_chunks, header_length); 104 | c.chunks = []; 105 | c.header_chunks = []; 106 | if (c.options.RAW) { 107 | curl.body = data; 108 | curl.header = header; 109 | } else { 110 | curl.body = data.toString(); 111 | curl.header = header.toString(); 112 | } 113 | curl.status = curl.code = c.getinfo('RESPONSE_CODE'); 114 | process.nextTick(function() { 115 | return cb.call(curl, null, curl); 116 | }); 117 | }); 118 | c.on('error', function(err) { 119 | var _this = this; 120 | curl.log("receive failed: " + err.message); 121 | curl.running = false; 122 | process.nextTick(function() { 123 | return cb.call(curl, err, null); 124 | }); 125 | }); 126 | this.log('perform'); 127 | return c.perform(); 128 | }; 129 | curl.setDefaultOptions = function(options, reset) { 130 | if (options == null) { 131 | options = {}; 132 | } 133 | if (reset == null) { 134 | reset = true; 135 | } 136 | defaultOptions = options; 137 | if (reset) { 138 | this.log('Set default options and reset cURL'); 139 | return this.reset(); 140 | } 141 | }; 142 | curl.log = function(text) { 143 | if (this.debug) { 144 | return console.info(("[cURL " + this.id + "] ") + text); 145 | } 146 | }; 147 | curl.setOptions = function(options) { 148 | var k, v; 149 | if (options == null) { 150 | options = {}; 151 | } 152 | for (k in options) { 153 | if (!__hasProp.call(options, k)) continue; 154 | v = options[k]; 155 | this.log("Set option '" + k + "' to '" + v + "'"); 156 | this.curl_.setopt(k, v); 157 | } 158 | return this; 159 | }; 160 | curl.setopts = function(options) { 161 | if (options == null) { 162 | options = {}; 163 | } 164 | return this.setOptions(options); 165 | }; 166 | curl.info = function(info) { 167 | if (this.curl_ == null) { 168 | throw new Error('curl is closed'); 169 | } 170 | return this.curl_.getinfo(info); 171 | }; 172 | curl.end = function() { 173 | if (this.curl_ != null) { 174 | this.curl_.close(); 175 | } 176 | this.curl_ = null; 177 | this.body = null; 178 | delete CurlBuilder.curls[this.id]; 179 | return this.log("closed."); 180 | }; 181 | curl.close = function() { 182 | return this.end(); 183 | }; 184 | curl.open = function() { 185 | if (curl.id == null) { 186 | curl.id = ++CurlBuilder.id; 187 | } 188 | this.log("opening."); 189 | this.curl_ = new Curl(); 190 | this.curl_.options = {}; 191 | this.defaultOptions = defaultOptions != null ? defaultOptions : {}; 192 | CurlBuilder.curls[curl.id] = curl; 193 | return this.log("opened."); 194 | }; 195 | curl.reset = function() { 196 | this.log('reset'); 197 | if (this.curl_) { 198 | this.end(); 199 | } 200 | return this.open(); 201 | }; 202 | curl.create = function(defaultOptions) { 203 | return CurlBuilder.create(defaultOptions); 204 | }; 205 | curl.get_count = function() { 206 | return Curl.get_count(); 207 | }; 208 | curl.open(); 209 | return curl; 210 | }; 211 | 212 | return CurlBuilder; 213 | 214 | }).call(this); 215 | 216 | process.on('exit', function() { 217 | return CurlBuilder.close_all(); 218 | }); 219 | 220 | module.exports = CurlBuilder; 221 | 222 | }).call(this); 223 | -------------------------------------------------------------------------------- /lib/CurlBuilder.toffee: -------------------------------------------------------------------------------- 1 | try 2 | Curl = require __dirname + '/Curl' 3 | catch e 4 | Curl = require __dirname + '/Curl' 5 | 6 | merge_chunks = (chunks, length) -> 7 | data = new Buffer(length) 8 | position = 0 9 | for chunk in chunks 10 | chunk.copy data, position 11 | position += chunk.length 12 | data 13 | 14 | class CurlBuilder 15 | @curls: {} 16 | @id: 0 17 | @close_all: => 18 | for own id, curl of @curls 19 | curl.end() 20 | delete @curls[id] 21 | @ 22 | 23 | @create: (defaultOptions) => 24 | curl = -> 25 | curl.perform.apply curl, arguments 26 | 27 | curl.perform = (args...) -> 28 | if @running 29 | throw new Error 'the cURL session is busy, use curl.create to create another cURL Session' 30 | 31 | if !@curl_ 32 | throw new Error 'the cURL is closed.' 33 | 34 | @running = true 35 | 36 | # pop arguments (url, [options = {}], callback) 37 | cb = args.pop() 38 | [@url, @options] = args 39 | @options ?= {} 40 | 41 | length = 0 42 | header_length = 0 43 | 44 | @debug = @defaultOptions.DEBUG ? @options.DEBUG ? @debug 45 | @effectiveOptions = {} 46 | for k, v of @defaultOptions 47 | @effectiveOptions[k] = v 48 | 49 | for k, v of @options 50 | @effectiveOptions[k] = v 51 | 52 | @setOptions @effectiveOptions 53 | @setOptions {URL: @url} 54 | 55 | c = @curl_ 56 | c.chunks = [] 57 | c.header_chunks = [] 58 | c.on 'data', (chunk) -> 59 | curl.log "receive #{chunk.length} bytes" 60 | c.chunks.push chunk 61 | length += chunk.length 62 | chunk.length 63 | 64 | c.on 'header', (chunk) -> 65 | curl.log "receive #{chunk.length} header" 66 | c.header_chunks.push chunk 67 | header_length += chunk.length 68 | chunk.length 69 | 70 | c.on 'end', -> 71 | curl.log "receive succeeded." 72 | curl.running = false 73 | data = merge_chunks(c.chunks, length) 74 | header = merge_chunks(c.header_chunks, header_length) 75 | c.chunks = [] 76 | c.header_chunks = [] 77 | 78 | if c.options.RAW 79 | curl.body = data 80 | curl.header = header 81 | else 82 | curl.body = data.toString() 83 | curl.header = header.toString() 84 | curl.status = curl.code = c.getinfo('RESPONSE_CODE') 85 | 86 | # if curl returns to fast, avoid cb recursive call 87 | process.nextTick! 88 | cb.call curl, null, curl 89 | 90 | c.on 'error', (err)-> 91 | curl.log "receive failed: #{err.message}" 92 | curl.running = false 93 | process.nextTick! 94 | cb.call curl, err, null 95 | 96 | @log 'perform' 97 | c.perform() 98 | 99 | 100 | 101 | curl.setDefaultOptions = (options = {}, reset = true) -> 102 | defaultOptions = options 103 | if reset 104 | @log 'Set default options and reset cURL' 105 | @reset() 106 | 107 | curl.log = (text) -> 108 | if @debug 109 | console.info "[cURL #{@id}] " + text 110 | 111 | curl.setOptions = (options = {}) -> 112 | for own k, v of options 113 | @log "Set option '#{k}' to '#{v}'" 114 | @curl_.setopt k, v 115 | @ 116 | 117 | curl.setopts = (options = {}) -> 118 | @setOptions options 119 | 120 | curl.info = (info)-> 121 | unless @curl_? 122 | throw new Error('curl is closed') 123 | @curl_.getinfo(info) 124 | 125 | curl.end = -> 126 | @curl_.close() if @curl_? 127 | @curl_ = null 128 | @body = null 129 | delete CurlBuilder.curls[@id] 130 | @log "closed." 131 | 132 | curl.close = -> 133 | @end() 134 | 135 | curl.open = -> 136 | unless curl.id? 137 | curl.id = ++CurlBuilder.id 138 | @log "opening." 139 | @curl_ = new Curl() 140 | @curl_.options = {} 141 | @defaultOptions = defaultOptions ? {} 142 | CurlBuilder.curls[curl.id] = curl 143 | @log "opened." 144 | 145 | curl.reset = -> 146 | @log 'reset' 147 | if @curl_ 148 | @end() 149 | @open() 150 | 151 | curl.create = (defaultOptions) -> 152 | CurlBuilder.create(defaultOptions) 153 | 154 | curl.get_count = -> 155 | Curl.get_count() 156 | 157 | curl.open() 158 | curl 159 | 160 | process.on 'exit', -> 161 | CurlBuilder.close_all() 162 | 163 | module.exports = CurlBuilder 164 | 165 | # vim: sw=2 ts=2 sts=2 expandtab : 166 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-curl", 3 | "version": "0.3.3", 4 | "author" : "Jiang Miao ", 5 | "description": "node wrapper for multi curl, fully implemented.", 6 | "keywords" : ["node-curl", "curl", "multi-curl", "mcurl"], 7 | "homepage": "http://github.com/jiangmiao/node-curl", 8 | "repository" : { 9 | "type" : "git", 10 | "url" : "git://github.com/jiangmiao/node-curl.git" 11 | }, 12 | "main" : "./lib", 13 | "scripts" : { 14 | "install" : "sh src/generate_curl_options_list.sh && (node-gyp rebuild || node-waf configure build)" 15 | }, 16 | "engines" : { "node": ">= 0.6.0" } 17 | } 18 | -------------------------------------------------------------------------------- /src/generate_curl_options_list.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | root=`dirname $0` 4 | 5 | if [ "$NODE_CURL_H" != "" ] ; then 6 | curl_header=$NODE_CURL_H 7 | elif [ -f "/usr/local/include/curl/curl.h" ] ; then 8 | curl_header="/usr/local/include/curl/curl.h" 9 | elif [ -f "/usr/include/curl/curl.h" ] ; then 10 | curl_header="/usr/include/curl/curl.h" 11 | fi 12 | 13 | if [ ! -f $curl_header ] ; then 14 | echo "cannot find curl's header file $curl_header" 15 | exit 1 16 | else 17 | echo "extract constants from $curl_header" 18 | fi 19 | 20 | generate() { 21 | name=$1 22 | pattern=$2 23 | prefix=$3 24 | echo "generate $root/$name.h" 25 | ( 26 | echo "// generated by $0 at $(date)" 27 | echo "CurlOption $name[] = {" 28 | cat "$curl_header"|perl -ne "/$pattern/i && print \"\t{\\\"\$1\\\", CURL${prefix}_\$1},\n\"" 29 | echo '};' 30 | ) > $root/$name.h 31 | } 32 | generate integer_options 'CINIT\((\w+).*LONG' OPT 33 | generate string_options 'CINIT\((\w+).*OBJECT' OPT 34 | 35 | generate integer_infos 'CURLINFO_(\w+).*LONG' INFO 36 | generate string_infos 'CURLINFO_(\w+).*STRING' INFO 37 | generate double_infos 'CURLINFO_(\w+).*DOUBLE' INFO 38 | -------------------------------------------------------------------------------- /src/node-curl.cc: -------------------------------------------------------------------------------- 1 | #include "node-curl.h" 2 | 3 | extern "C" 4 | void init(v8::Handle target) 5 | { 6 | NodeCurl::Initialize(target); 7 | } 8 | 9 | #ifdef NODE_MODULE 10 | NODE_MODULE(node_curl, init) 11 | #endif 12 | -------------------------------------------------------------------------------- /src/node-curl.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_CURL_NOHE_CURL_H 2 | #define NODE_CURL_NOHE_CURL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define NODE_CURL_EXPORT(name) export_curl_options(t, #name, name, sizeof(name) / sizeof(CurlOption)); 15 | 16 | class NodeCurlHttppost 17 | { 18 | public: 19 | curl_httppost *first; 20 | curl_httppost *last; 21 | 22 | public: 23 | NodeCurlHttppost() 24 | : first(NULL), last(NULL) 25 | { 26 | reset(); 27 | } 28 | 29 | ~NodeCurlHttppost() 30 | { 31 | reset(); 32 | } 33 | 34 | void reset() 35 | { 36 | curl_httppost *cur = first; 37 | while (cur) { 38 | curl_httppost *next = cur->next; 39 | if (cur->contenttype) 40 | free(cur->contenttype); 41 | if (cur->contents) 42 | free(cur->contents); 43 | if (cur->buffer) 44 | free(cur->buffer); 45 | if (cur->name) 46 | free(cur->name); 47 | free(cur); 48 | cur = next; 49 | } 50 | first = NULL; 51 | last = NULL; 52 | } 53 | 54 | void append() 55 | { 56 | if (!first) { 57 | first = (curl_httppost*)calloc(1, sizeof(curl_httppost)); 58 | last = first; 59 | } else { 60 | last->next = (curl_httppost*)calloc(1, sizeof(curl_httppost)); 61 | last = last->next; 62 | } 63 | } 64 | 65 | enum { 66 | NAME, 67 | FILE, 68 | CONTENTS, 69 | TYPE 70 | }; 71 | 72 | void set(int field, char *value, long length) 73 | { 74 | value = strndup(value, length); 75 | switch (field) { 76 | case NAME: 77 | last->name = value; 78 | last->namelength = length; 79 | break; 80 | case TYPE: 81 | last->contenttype = value; 82 | break; 83 | case FILE: 84 | last->flags |= HTTPPOST_FILENAME; 85 | case CONTENTS: 86 | last->contents = value; 87 | last->contentslength = length; 88 | break; 89 | default: 90 | // `default` should never be reached. 91 | free(value); 92 | break; 93 | } 94 | } 95 | }; 96 | 97 | class NodeCurl 98 | { 99 | struct CurlOption 100 | { 101 | const char *name; 102 | int value; 103 | }; 104 | 105 | static CURLM * curlm; 106 | static int running_handles; 107 | static bool is_ref; 108 | static std::map< CURL*, NodeCurl* > curls; 109 | static int count; 110 | static int transfered; 111 | 112 | CURL * curl; 113 | v8::Persistent handle; 114 | 115 | bool in_curlm; 116 | std::vector slists; 117 | std::map strings; 118 | NodeCurlHttppost httppost; 119 | 120 | NodeCurl(v8::Handle object) 121 | : in_curlm(false) 122 | { 123 | ++count; 124 | v8::V8::AdjustAmountOfExternalAllocatedMemory(2*4096); 125 | object->SetPointerInInternalField(0, this); 126 | handle = v8::Persistent::New(object); 127 | handle.MakeWeak(this, destructor); 128 | 129 | curl = curl_easy_init(); 130 | if (!curl) 131 | { 132 | raise("curl_easy_init failed"); 133 | return; 134 | } 135 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_function); 136 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, this); 137 | curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_function); 138 | curl_easy_setopt(curl, CURLOPT_HEADERDATA, this); 139 | curls[curl] = this; 140 | } 141 | 142 | ~NodeCurl() 143 | { 144 | --count; 145 | v8::V8::AdjustAmountOfExternalAllocatedMemory(-2*4096); 146 | if (curl) 147 | { 148 | if (in_curlm) 149 | curl_multi_remove_handle(curlm, curl); 150 | curl_easy_cleanup(curl); 151 | curls.erase(curl); 152 | } 153 | 154 | for (std::vector::iterator i = slists.begin(), e = slists.end(); i != e; ++i) 155 | { 156 | curl_slist * slist = *i; 157 | if (slist) 158 | { 159 | curl_slist_free_all(slist); 160 | } 161 | } 162 | } 163 | 164 | static void destructor(v8::Persistent value, void *data) 165 | { 166 | v8::Handle object = value->ToObject(); 167 | NodeCurl * curl = (NodeCurl*)object->GetPointerFromInternalField(0); 168 | curl->close(); 169 | } 170 | 171 | void close() 172 | { 173 | handle->SetPointerInInternalField(0, NULL); 174 | handle.Dispose(); 175 | delete this; 176 | } 177 | 178 | static v8::Handle close(const v8::Arguments & args) 179 | { 180 | NodeCurl * node_curl = unwrap(args.This()); 181 | if (node_curl) 182 | node_curl->close(); 183 | return args.This(); 184 | } 185 | 186 | 187 | static NodeCurl * unwrap(v8::Handle value) 188 | { 189 | return (NodeCurl*)value->GetPointerFromInternalField(0); 190 | } 191 | 192 | // curl write function mapping 193 | static size_t write_function(char *ptr, size_t size, size_t nmemb, void *userdata) 194 | { 195 | transfered += size * nmemb; 196 | NodeCurl *nodecurl = (NodeCurl*)userdata; 197 | return nodecurl->on_write(ptr, size * nmemb); 198 | } 199 | 200 | static size_t header_function(char *ptr, size_t size, size_t nmemb, void *userdata) 201 | { 202 | transfered += size * nmemb; 203 | NodeCurl *nodecurl = (NodeCurl*)userdata; 204 | return nodecurl->on_header(ptr, size * nmemb); 205 | } 206 | 207 | size_t on_write(char *data, size_t n) 208 | { 209 | static v8::Persistent SYM_ON_WRITE = v8::Persistent::New(v8::String::NewSymbol("on_write")); 210 | v8::Handle cb = handle->Get(SYM_ON_WRITE); 211 | if (cb->IsFunction()) 212 | { 213 | node::Buffer * buffer = node::Buffer::New(data, n); 214 | v8::Handle argv[] = { buffer->handle_ }; 215 | v8::Handle rt = cb->ToObject()->CallAsFunction(handle, 1, argv); 216 | if (rt.IsEmpty()) 217 | return 0; 218 | else 219 | return rt->Int32Value(); 220 | } 221 | return n; 222 | } 223 | 224 | size_t on_header(char *data, size_t n) 225 | { 226 | static v8::Persistent SYM_ON_HEADER = v8::Persistent::New(v8::String::NewSymbol("on_header")); 227 | v8::Handle cb = handle->Get(SYM_ON_HEADER); 228 | if (cb->IsFunction()) 229 | { 230 | node::Buffer * buffer = node::Buffer::New(data, n); 231 | v8::Handle argv[] = { buffer->handle_ }; 232 | v8::Handle rt = cb->ToObject()->CallAsFunction(handle, 1, argv); 233 | if (rt.IsEmpty()) 234 | return 0; 235 | else 236 | return rt->Int32Value(); 237 | } 238 | return n; 239 | } 240 | 241 | void on_end(CURLMsg *msg) 242 | { 243 | static v8::Persistent SYM_ON_END = v8::Persistent::New(v8::String::NewSymbol("on_end")); 244 | v8::Handle cb = handle->Get(SYM_ON_END); 245 | if (cb->IsFunction()) 246 | { 247 | v8::Handle argv[] = {}; 248 | cb->ToObject()->CallAsFunction(handle, 0, argv); 249 | } 250 | } 251 | 252 | void on_error(CURLMsg *msg) 253 | { 254 | static v8::Persistent SYM_ON_ERROR = v8::Persistent::New(v8::String::NewSymbol("on_error")); 255 | v8::Handle cb = handle->Get(SYM_ON_ERROR); 256 | if (cb->IsFunction()) 257 | { 258 | v8::Handle argv[] = {v8::Exception::Error(v8::String::New(curl_easy_strerror(msg->data.result)))}; 259 | cb->ToObject()->CallAsFunction(handle, 1, argv); 260 | } 261 | } 262 | 263 | // curl_easy_getinfo 264 | template 265 | static v8::Handle getinfo(const v8::Arguments &args) 266 | { 267 | CType result; 268 | NodeCurl * node_curl = unwrap(args.This()); 269 | CURLINFO info = (CURLINFO)args[0]->Int32Value(); 270 | CURLcode code = curl_easy_getinfo(node_curl->curl, info, &result); 271 | if (code != CURLE_OK) 272 | { 273 | return raise("curl_easy_getinfo failed", curl_easy_strerror(code)); 274 | } 275 | return result ? JsClass::New(result) : v8::Null(); 276 | } 277 | 278 | static v8::Handle getinfo_int(const v8::Arguments & args) 279 | { 280 | return getinfo(args); 281 | } 282 | 283 | static v8::Handle getinfo_str(const v8::Arguments & args) 284 | { 285 | return getinfo(args); 286 | } 287 | 288 | static v8::Handle getinfo_double(const v8::Arguments & args) 289 | { 290 | return getinfo(args); 291 | } 292 | 293 | static v8::Handle getinfo_slist(const v8::Arguments & args) 294 | { 295 | curl_slist * slist, * cur; 296 | NodeCurl* node_curl = unwrap(args.This()); 297 | CURLINFO info = (CURLINFO)args[0]->Int32Value(); 298 | CURLcode code = curl_easy_getinfo(node_curl->curl, info, &slist); 299 | if (code != CURLE_OK) 300 | { 301 | return raise("curl_easy_getinfo failed", curl_easy_strerror(code)); 302 | } 303 | v8::Handle array = v8::Array::New(); 304 | if (slist) 305 | { 306 | cur = slist; 307 | while (cur) 308 | { 309 | array->Set(array->Length(), v8::String::New(cur->data)); 310 | cur = cur->next; 311 | } 312 | curl_slist_free_all(slist); 313 | } 314 | return array; 315 | } 316 | 317 | // curl_easy_setopt 318 | template 319 | v8::Handle setopt(v8::Handle option, T value) 320 | { 321 | return v8::Integer::New( 322 | curl_easy_setopt( 323 | curl, 324 | (CURLoption)option->Int32Value(), 325 | value 326 | ) 327 | ); 328 | } 329 | 330 | static v8::Handle setopt_int(const v8::Arguments & args) 331 | { 332 | return unwrap(args.This())->setopt(args[0], args[1]->Int32Value()); 333 | } 334 | 335 | static v8::Handle setopt_str(const v8::Arguments & args) 336 | { 337 | // Create a string copy 338 | // https://github.com/jiangmiao/node-curl/issues/3 339 | NodeCurl * node_curl = unwrap(args.This()); 340 | int key = args[0]->Int32Value(); 341 | v8::String::Utf8Value value(args[1]); 342 | int length = value.length(); 343 | node_curl->strings[key] = std::string(*value, length); 344 | return unwrap(args.This())->setopt(args[0], node_curl->strings[key].c_str() ); 345 | } 346 | 347 | static v8::Handle setopt_slist(const v8::Arguments & args) 348 | { 349 | NodeCurl * node_curl = unwrap(args.This()); 350 | curl_slist * slist = value_to_slist(args[1]); 351 | node_curl->slists.push_back(slist); 352 | return node_curl->setopt(args[0], slist); 353 | } 354 | 355 | static v8::Handle setopt_httppost(const v8::Arguments & args) 356 | { 357 | NodeCurl * node_curl = unwrap(args.This()); 358 | NodeCurlHttppost &httppost = node_curl->httppost; 359 | v8::Handle rows = v8::Handle::Cast(args[0]); 360 | httppost.reset(); 361 | for (uint32_t i=0, len = rows->Length(); i cols = v8::Handle::Cast(rows->Get(i)); 364 | uint32_t j=0, cols_len = cols->Length(); 365 | httppost.append(); 366 | while (jGet(j++)->Int32Value(); 369 | v8::Handle buffer = cols->Get(j++)->ToObject(); 370 | char *value = node::Buffer::Data(buffer); 371 | int length = node::Buffer::Length(buffer); 372 | httppost.set(field, value, length); 373 | } 374 | } 375 | curl_easy_setopt(node_curl->curl, CURLOPT_HTTPPOST, node_curl->httppost.first); 376 | return args.This(); 377 | } 378 | 379 | static curl_slist * value_to_slist(v8::Handle value) 380 | { 381 | curl_slist * slist = NULL; 382 | if (!value->IsArray()) 383 | { 384 | slist = curl_slist_append(slist, *v8::String::Utf8Value(value)); 385 | } 386 | else 387 | { 388 | v8::Handle array = v8::Handle::Cast(value); 389 | for (uint32_t i=0, len = array->Length(); iGet(i))); 392 | } 393 | } 394 | return slist; 395 | } 396 | 397 | 398 | static v8::Handle raise(const char *data, const char *reason = NULL) 399 | { 400 | static char message[256]; 401 | const char *what = data; 402 | if (reason) 403 | { 404 | snprintf(message, sizeof(message), "%s: %s", data, reason); 405 | what = message; 406 | } 407 | return v8::ThrowException(v8::Exception::Error(v8::String::New(what))); 408 | } 409 | 410 | template 411 | static void export_curl_options(T t, const char *group_name, CurlOption *options, int len) 412 | { 413 | v8::Handle node_options = v8::Object::New(); 414 | for (int i=0; iSet( 418 | v8::String::NewSymbol(option.name), 419 | v8::Integer::New(option.value) 420 | ); 421 | } 422 | t->Set(v8::String::NewSymbol(group_name), node_options); 423 | } 424 | 425 | // node js functions 426 | static v8::Handle New(const v8::Arguments & args) 427 | { 428 | new NodeCurl(args.This()); 429 | return args.This(); 430 | } 431 | 432 | // int process() 433 | static v8::Handle process(const v8::Arguments & args) 434 | { 435 | transfered = 0; 436 | if (running_handles > 0) 437 | { 438 | CURLMcode code; 439 | // remove select totally for timeout doesn't work properly 440 | do 441 | { 442 | code = curl_multi_perform(curlm, &running_handles); 443 | } while ( code == CURLM_CALL_MULTI_PERFORM ); 444 | 445 | if (code != CURLM_OK) 446 | { 447 | return raise("curl_multi_perform failed", curl_multi_strerror(code)); 448 | } 449 | 450 | int msgs = 0; 451 | CURLMsg * msg = NULL; 452 | while ( (msg = curl_multi_info_read(curlm, &msgs)) ) 453 | { 454 | if (msg->msg == CURLMSG_DONE) 455 | { 456 | NodeCurl * curl = curls[msg->easy_handle]; 457 | CURLMsg msg_copy = *msg; 458 | code = curl_multi_remove_handle(curlm, msg->easy_handle); 459 | curl->in_curlm = false; 460 | if (code != CURLM_OK) 461 | { 462 | return raise("curl_multi_remove_handle failed", curl_multi_strerror(code)); 463 | } 464 | 465 | if (msg_copy.data.result == CURLE_OK) 466 | curl->on_end(&msg_copy); 467 | else 468 | curl->on_error(&msg_copy); 469 | } 470 | } 471 | } 472 | return v8::Integer::New(transfered + (int)(running_handles > 0)); 473 | } 474 | 475 | // perform() 476 | static v8::Handle perform(const v8::Arguments & args) 477 | { 478 | NodeCurl *curl = unwrap(args.This()); 479 | if (!curl) 480 | return raise("curl is closed."); 481 | 482 | if (curl->in_curlm) 483 | return raise("curl session is running."); 484 | 485 | CURLMcode code = curl_multi_add_handle(curlm, curl->curl); 486 | if (code != CURLM_OK) 487 | { 488 | return raise("curl_multi_add_handle failed", curl_multi_strerror(code)); 489 | } 490 | curl->in_curlm = true; 491 | ++running_handles; 492 | 493 | return args.This(); 494 | } 495 | 496 | static v8::Handle get_count(const v8::Arguments & args ) 497 | { 498 | return v8::Integer::New(count); 499 | } 500 | 501 | public: 502 | static v8::Handle Initialize(v8::Handle target) 503 | { 504 | // Initialize curl 505 | CURLcode code = curl_global_init(CURL_GLOBAL_ALL); 506 | if (code != CURLE_OK) 507 | { 508 | return raise("curl_global_init faield"); 509 | } 510 | 511 | curlm = curl_multi_init(); 512 | if (curlm == NULL) 513 | { 514 | return raise("curl_multi_init failed"); 515 | } 516 | 517 | // Initialize node-curl 518 | v8::Handle t = v8::FunctionTemplate::New(New); 519 | t->InstanceTemplate()->SetInternalFieldCount(1); 520 | 521 | // Set prototype methods 522 | NODE_SET_PROTOTYPE_METHOD(t , "perform_" , perform); 523 | NODE_SET_PROTOTYPE_METHOD(t , "setopt_int_" , setopt_int); 524 | NODE_SET_PROTOTYPE_METHOD(t , "setopt_str_" , setopt_str); 525 | NODE_SET_PROTOTYPE_METHOD(t , "setopt_slist_" , setopt_slist); 526 | NODE_SET_PROTOTYPE_METHOD(t , "setopt_httppost_" , setopt_httppost); 527 | 528 | NODE_SET_PROTOTYPE_METHOD(t , "getinfo_int_" , getinfo_int); 529 | NODE_SET_PROTOTYPE_METHOD(t , "getinfo_str_" , getinfo_str); 530 | NODE_SET_PROTOTYPE_METHOD(t , "getinfo_double_" , getinfo_double); 531 | NODE_SET_PROTOTYPE_METHOD(t , "getinfo_slist_" , getinfo_slist); 532 | 533 | NODE_SET_PROTOTYPE_METHOD(t, "close_", close); 534 | 535 | NODE_SET_METHOD(t , "process_" , process); 536 | NODE_SET_METHOD(t , "get_count" , get_count); 537 | 538 | // Set curl constants 539 | #include "string_options.h" 540 | #include "integer_options.h" 541 | #include "string_infos.h" 542 | #include "integer_infos.h" 543 | #include "double_infos.h" 544 | 545 | #define X(name) {#name, CURLOPT_##name} 546 | CurlOption slist_options[] = { 547 | #if LIBCURL_VERSION_NUM >= 0x070a03 548 | X(HTTP200ALIASES), 549 | #endif 550 | 551 | #if LIBCURL_VERSION_NUM >= 0x071400 552 | X(MAIL_RCPT), 553 | #endif 554 | 555 | #if LIBCURL_VERSION_NUM >= 0x071503 556 | X(RESOLVE), 557 | #endif 558 | 559 | X(HTTPHEADER), 560 | X(QUOTE), 561 | X(POSTQUOTE), 562 | X(PREQUOTE), 563 | X(TELNETOPTIONS) 564 | }; 565 | #undef X 566 | 567 | #define X(name) {#name, CURLINFO_##name} 568 | CurlOption slist_infos[] = { 569 | X(SSL_ENGINES), 570 | X(COOKIELIST) 571 | }; 572 | 573 | #undef X 574 | #define X(name) {#name, NodeCurlHttppost::name} 575 | CurlOption httppost_options[] = { 576 | X(NAME), 577 | X(FILE), 578 | X(CONTENTS), 579 | X(TYPE) 580 | }; 581 | #undef X 582 | 583 | NODE_CURL_EXPORT(string_options); 584 | NODE_CURL_EXPORT(integer_options); 585 | NODE_CURL_EXPORT(slist_options); 586 | 587 | NODE_CURL_EXPORT(string_infos); 588 | NODE_CURL_EXPORT(integer_infos); 589 | NODE_CURL_EXPORT(double_infos); 590 | NODE_CURL_EXPORT(slist_infos); 591 | 592 | NODE_CURL_EXPORT(httppost_options); 593 | 594 | target->Set(v8::String::NewSymbol("Curl"), t->GetFunction()); 595 | return target; 596 | } 597 | }; 598 | 599 | CURLM * NodeCurl::curlm = NULL; 600 | int NodeCurl::running_handles = 0; 601 | bool NodeCurl::is_ref = false; 602 | std::map< CURL*, NodeCurl* > NodeCurl::curls; 603 | int NodeCurl::count = 0; 604 | int NodeCurl::transfered = 0; 605 | 606 | #endif 607 | -------------------------------------------------------------------------------- /wscript: -------------------------------------------------------------------------------- 1 | def set_options(opt): 2 | opt.tool_options('compiler_cxx') 3 | 4 | def configure(conf): 5 | conf.check_tool('compiler_cxx') 6 | conf.check_tool('node_addon') 7 | conf.env.append_unique('CXXFLAGS', ['-Wall', '-O1', '-fno-inline-functions']) 8 | conf.env['LIB_CURL'] = 'curl' 9 | 10 | def build(bld): 11 | obj = bld.new_task_gen('cxx', 'shlib', 'node_addon', uselib="CURL") 12 | obj.target = 'node-curl' 13 | obj.source = [ 14 | 'src/node-curl.cc', 15 | ] 16 | --------------------------------------------------------------------------------