juozasg-curl-multi 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest.txt ADDED
@@ -0,0 +1,25 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ config/hoe.rb
7
+ config/requirements.rb
8
+ lib/curl-multi.rb
9
+ lib/curl-multi/version.rb
10
+ lib/uri-extensions.rb
11
+ log/debug.log
12
+ script/destroy
13
+ script/generate
14
+ script/txt2html
15
+ setup.rb
16
+ tasks/deployment.rake
17
+ tasks/environment.rake
18
+ tasks/website.rake
19
+ test/test_curl-multi.rb
20
+ test/test_helper.rb
21
+ website/index.html
22
+ website/index.txt
23
+ website/javascripts/rounded_corners_lite.inc.js
24
+ website/stylesheets/screen.css
25
+ website/template.rhtml
data/README.txt ADDED
@@ -0,0 +1,7 @@
1
+ curl-multi - Ruby bindings for the libcurl multi interface
2
+ Copyright (C) 2007 Philotic, Inc.
3
+
4
+ See http://curl-multi.rubyforge.net/ for instructions.
5
+
6
+ If you enjoy curl-multi, check out http://xph.us/software/ for other software
7
+ you might find useful.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
data/config/hoe.rb ADDED
@@ -0,0 +1,71 @@
1
+ require 'curl-multi/version'
2
+
3
+ AUTHOR = ['Kristjan Petursson', 'Keith Rarick']
4
+ EMAIL = "kr@essembly.com"
5
+ DESCRIPTION = 'Ruby bindings for the libcurl multi interface'
6
+ GEM_NAME = 'curl-multi' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'curl-multi' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+
11
+ @config_file = "~/.rubyforge/user-config.yml"
12
+ @config = nil
13
+ RUBYFORGE_USERNAME = "unknown"
14
+ def rubyforge_username
15
+ unless @config
16
+ begin
17
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
18
+ rescue
19
+ puts <<-EOS
20
+ ERROR: No rubyforge config file found: #{@config_file}
21
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
22
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
23
+ EOS
24
+ exit
25
+ end
26
+ end
27
+ RUBYFORGE_USERNAME.replace @config["username"]
28
+ end
29
+
30
+
31
+ REV = nil
32
+ # UNCOMMENT IF REQUIRED:
33
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
34
+ VERS = Curl::Multi::VERSION::STRING + (REV ? ".#{REV}" : "")
35
+ RDOC_OPTS = ['--quiet', '--title', 'curl-multi documentation',
36
+ "--opname", "index.html",
37
+ "--line-numbers",
38
+ "--main", "README",
39
+ "--inline-source"]
40
+
41
+ class Hoe
42
+ def extra_deps
43
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
44
+ @extra_deps
45
+ end
46
+ end
47
+
48
+ # Generate all the Rake tasks
49
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
50
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
51
+ p.author = AUTHOR
52
+ p.description = DESCRIPTION
53
+ p.email = EMAIL
54
+ p.summary = DESCRIPTION
55
+ p.url = HOMEPATH
56
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
57
+ p.test_globs = ["test/**/test_*.rb"]
58
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store']
59
+
60
+ # == Optional
61
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\\n\\n")
62
+ p.extra_deps = [['RubyInline', '>= 3.6.2'], ['curb', '>= 0.1.2']]
63
+
64
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
65
+
66
+ end
67
+
68
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
69
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
70
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
71
+ hoe.rsync_args = '-av --delete --ignore-errors'
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
16
+
17
+ require 'curl-multi'
data/lib/curl-multi.rb ADDED
@@ -0,0 +1,380 @@
1
+ # curl-multi - Ruby bindings for the libcurl multi interface
2
+ # Copyright (C) 2007 Philotic, Inc.
3
+
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ $:.unshift File.dirname(__FILE__)
18
+
19
+ require 'rubygems'
20
+ require 'inline'
21
+ require 'uri-extensions'
22
+ require 'curb'
23
+
24
+ module Curl
25
+ class Error < RuntimeError
26
+ def initialize(code)
27
+ super("Curl error #{code}")
28
+ end
29
+ end
30
+
31
+ class HTTPError < RuntimeError
32
+ def initialize(status)
33
+ super("Curl HTTP error #{status}")
34
+ end
35
+ end
36
+
37
+ class MultiError < RuntimeError
38
+ attr_reader :errors
39
+ def initialize(errors)
40
+ @errors = errors
41
+ super("Multiple errors: #{errors}")
42
+ end
43
+ end
44
+
45
+ # An instance of this class collects the response for each curl easy handle.
46
+ class Req
47
+ CURLE_OK = 0
48
+
49
+ attr_reader :url, :body
50
+
51
+ def done(code, status)
52
+ @done = true
53
+ if code != CURLE_OK
54
+ @error = Error.new(code)
55
+ elsif status != 200
56
+ @error = HTTPError.new(status)
57
+ end
58
+ end
59
+
60
+ def done?() @done end
61
+
62
+ def failed?() !!@error end
63
+
64
+ def do_success() @success.call(body) end
65
+
66
+ def do_failure() @failure.call(@error) end
67
+
68
+ def inspect() "{Curl::Req #{url}}" end
69
+ alias_method :to_s, :inspect
70
+
71
+ def initialize(url, success, failure)
72
+ @url = url
73
+ @success = success
74
+ @failure = failure
75
+ @body = ''
76
+ @done = false
77
+ @error = nil
78
+ end
79
+
80
+ def add_chunk(chunk)
81
+ @body = body + chunk
82
+ end
83
+ end
84
+
85
+ class Multi
86
+ def size() @handles.size end
87
+
88
+ def inspect() '{Curl::Multi' + @handles.map{|h| ' '+h.url}.join + '}' end
89
+ alias_method :to_s, :inspect
90
+
91
+ def get(url, success, failure=lambda{})
92
+ add(url, nil, success, failure)
93
+ end
94
+
95
+ def post(url, params, success, failure=lambda{})
96
+ add(url, URI.escape_params(params), success, failure)
97
+ end
98
+
99
+ def select(rfds, wfds)
100
+ loop do
101
+ ready_rfds, ready_wfds = c_select(rfds.map{|s|s.fileno},
102
+ wfds.map{|s|s.fileno})
103
+ work() # Curl may or may not have work to do, but we can't tell.
104
+ return ready_rfds, ready_wfds if ready_rfds.any? or ready_wfds.any?
105
+ return [], [] if rfds == [] and wfds == []
106
+ end
107
+ end
108
+
109
+ STACK_DIVIDER = ' -- request stack trace is below this line -- :0'.freeze
110
+
111
+ # Do as much work as possible without blocking on the network. That is,
112
+ # read as much data as is ready and write as much data as we have buffer
113
+ # space for. If any complete responses arrive, call their handlers.
114
+ def work
115
+ perform()
116
+
117
+ done, @handles = @handles.partition{|h| h.done?}
118
+ failed, done = done.partition{|h| h.failed?}
119
+
120
+ errors = []
121
+ errors += post_process(done) {|x| x.do_success}
122
+ errors += post_process(failed) {|x| x.do_failure}
123
+ raise errors[0] if errors.size == 1
124
+ raise MultiError.new(errors) if errors.size > 1
125
+
126
+ :ok
127
+ rescue Exception => ex
128
+ ex.set_backtrace(ex.backtrace + [STACK_DIVIDER] + @creation_stack)
129
+ raise ex
130
+ end
131
+
132
+ def cleanup
133
+ @handles = []
134
+ :ok
135
+ end
136
+
137
+ def initialize
138
+ @handles = []
139
+ end
140
+
141
+ # Add a URL to the queue of items to fetch
142
+ def add(url, body, success, failure=lambda{})
143
+ @creation_stack = caller() # save for later, just in case
144
+ while (h = add_to_curl(Req.new(url, success, failure), url, body)) == nil
145
+ select([], [])
146
+ end
147
+ @handles << h
148
+ work()
149
+ :ok
150
+ end
151
+
152
+ def post_process(handles)
153
+ errors = []
154
+ handles.each do |h|
155
+ begin
156
+ yield(h) # This ought not to raise anything.
157
+ rescue => ex
158
+ errors << ex
159
+ end
160
+ end
161
+ return errors
162
+ end
163
+
164
+ inline do |builder|
165
+ builder.include('<errno.h>')
166
+ if File.exists?('/usr/include/curl/curl.h')
167
+ builder.include('"/usr/include/curl/curl.h"')
168
+ elsif File.exists?('/opt/csw/include/curl/curl.h')
169
+ builder.include('"/opt/csw/include/curl/curl.h"')
170
+ else
171
+ builder.include('"/usr/local/include/curl/curl.h"')
172
+ end
173
+
174
+ builder.prefix <<-end
175
+ #define GET_MULTI_HANDLE(name) \
176
+ CURLM *(name);\
177
+ Data_Get_Struct(self, CURLM, (name));\
178
+ if (!(name)) rb_raise(rb_eTypeError,\
179
+ "Expected initialized curl multi handle")
180
+
181
+ #define CHECK(e) do {\
182
+ if (!(e)) rb_raise(rb_eTypeError, "curl error");\
183
+ } while (0)
184
+
185
+ #define CHECKN(e) CHECK(!(e))
186
+
187
+ ID id_initialize, id_done, id_add_chunk, id_size;
188
+ end
189
+
190
+ builder.c_raw_singleton <<-end
191
+ void c_curl_multi_cleanup(void *x)
192
+ {
193
+ curl_multi_cleanup(x);
194
+ }
195
+ end
196
+
197
+ # Creates a new CurlMulti handle
198
+ builder.c_singleton <<-end
199
+ VALUE new() {
200
+ CURLcode r;
201
+ VALUE inst;
202
+
203
+ /* can't think of a better place to put this */
204
+ id_initialize = rb_intern("initialize");
205
+ id_done = rb_intern("done");
206
+ id_add_chunk = rb_intern("add_chunk");
207
+ id_size = rb_intern("size");
208
+
209
+ /* must be called at least once before any other curl functions */
210
+ r = curl_global_init(CURL_GLOBAL_ALL);
211
+ CHECKN(r);
212
+
213
+ inst = Data_Wrap_Struct(self, 0, c_curl_multi_cleanup, curl_multi_init());
214
+ rb_funcall(inst, id_initialize, 0);
215
+ return inst;
216
+ }
217
+ end
218
+
219
+ # We tell CurlEasy handles to write to this function in the constructor.
220
+ # The self argument is a parameter we set up with CURLOPT_WRITEDATA that
221
+ # lets us pass whatever data we'd like into the write fn.
222
+ builder.c_raw_singleton <<-end
223
+ uint c_add_chunk(char *chunk, uint size, uint nmemb, VALUE rb_req) {
224
+ uint bytes = size * nmemb; /* Number of bytes of data */
225
+ if (bytes == 0) return 0;
226
+
227
+ /* This is (rubyInstance, methodId, numArgs, arg1, arg2...) */
228
+ rb_funcall(rb_req, id_add_chunk, 1, rb_str_new(chunk, bytes));
229
+ return bytes;
230
+ }
231
+ end
232
+
233
+ # Adds an easy handle to the multi handle's list
234
+ builder.c <<-end
235
+ VALUE add_to_curl(VALUE rb_req, VALUE url, VALUE body) {
236
+ CURLMcode r;
237
+ GET_MULTI_HANDLE(multi_handle);
238
+
239
+ /* We start getting errors if we have too many open connections at
240
+ once, so make a hard limit. */
241
+ if (FIX2INT(rb_funcall(self, id_size, 0)) > 500) return Qnil;
242
+
243
+ CURL *easy_handle = curl_easy_init();
244
+ char *c_url = StringValuePtr(url);
245
+
246
+ /* Pass it the URL */
247
+ curl_easy_setopt(easy_handle, CURLOPT_URL, c_url);
248
+
249
+ /* GET or POST? */
250
+ if (body != Qnil) {
251
+ char *c_body = StringValuePtr(body);
252
+ uint body_sz = RSTRING(body)->len;
253
+ curl_easy_setopt(easy_handle, CURLOPT_POST, 1);
254
+ curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, c_body);
255
+ curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDSIZE, body_sz);
256
+ }
257
+
258
+ /* Tell curl to use our callbacks */
259
+ curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, c_add_chunk);
260
+
261
+ /* Make curl give us a ruby pointer in the callbacks */
262
+ curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, rb_req);
263
+ curl_easy_setopt(easy_handle, CURLOPT_PRIVATE, rb_req);
264
+
265
+ /* Add it to the multi handle */
266
+ r = curl_multi_add_handle(multi_handle, easy_handle);
267
+ CHECKN(r);
268
+
269
+ return rb_req;
270
+ }
271
+ end
272
+
273
+ # Wait until one of the given fds or one of curl's fds is ready.
274
+ # Return two arrays of fds that are ready.
275
+ builder.c <<-end
276
+ void c_select(VALUE rfda, VALUE wfda) {
277
+ int i, r, n = -1;
278
+ long timeout;
279
+ fd_set rfds, wfds, efds;
280
+ CURLMcode cr;
281
+ VALUE ready_rfda, ready_wfda;
282
+ struct timeval tv = {0, 0};
283
+ GET_MULTI_HANDLE(multi_handle);
284
+
285
+ FD_ZERO(&rfds);
286
+ FD_ZERO(&wfds);
287
+ FD_ZERO(&efds);
288
+
289
+ /* Put curl's fds into the sets. */
290
+ cr = curl_multi_fdset(multi_handle, &rfds, &wfds, &efds, &n);
291
+ CHECKN(cr);
292
+
293
+ /* Put the given fds into the sets. */
294
+ for (i = 0; i < RARRAY(rfda)->len; i++) {
295
+ int fd = FIX2INT(RARRAY(rfda)->ptr[i]);
296
+ FD_SET(fd, &rfds);
297
+ n = n > fd ? n : fd;
298
+ }
299
+ for (i = 0; i < RARRAY(wfda)->len; i++) {
300
+ int fd = FIX2INT(RARRAY(wfda)->ptr[i]);
301
+ FD_SET(fd, &wfds);
302
+ n = n > fd ? n : fd;
303
+ }
304
+
305
+ cr = curl_multi_timeout(multi_handle, &timeout);
306
+ CHECKN(cr);
307
+
308
+ tv.tv_sec = timeout / 1000;
309
+ tv.tv_usec = (timeout * 1000) % 1000000;
310
+
311
+ /* Wait */
312
+ r = select(n + 1, &rfds, &wfds, &efds, (timeout < 0) ? NULL : &tv);
313
+ if (r < 0) rb_raise(rb_eRuntimeError, "select(): %s", strerror(errno));
314
+
315
+ ready_rfda = rb_ary_new();
316
+ ready_wfda = rb_ary_new();
317
+
318
+ /* Collect the fds that are ready */
319
+ for (i = 0; i < RARRAY(rfda)->len; i++) {
320
+ VALUE fd = FIX2INT(RARRAY(rfda)->ptr[i]);
321
+ if (FD_ISSET(fd, &rfds)) rb_ary_push(ready_rfda, INT2FIX(fd));
322
+ }
323
+ for (i = 0; i < RARRAY(wfda)->len; i++) {
324
+ VALUE fd = FIX2INT(RARRAY(wfda)->ptr[i]);
325
+ if (FD_ISSET(fd, &wfds)) rb_ary_push(ready_wfda, INT2FIX(fd));
326
+ }
327
+
328
+ return rb_ary_new3(2, ready_rfda, ready_wfda);
329
+ }
330
+ end
331
+
332
+ # Basically just a wrapper for curl_multi_perform().
333
+ builder.c <<-end
334
+ void perform() {
335
+ CURLMsg *msg;
336
+ CURLcode er;
337
+ CURLMcode r;
338
+ int status;
339
+ int running;
340
+ GET_MULTI_HANDLE(multi_handle);
341
+
342
+ /* do some work */
343
+ do {
344
+ r = curl_multi_perform(multi_handle, &running);
345
+ } while (r == CURLM_CALL_MULTI_PERFORM);
346
+ CHECK(r == CURLM_OK);
347
+
348
+ /* check which ones are done and mark them as done */
349
+ while ((msg = curl_multi_info_read(multi_handle, &r))) {
350
+ VALUE rb_req;
351
+ CURL *easy_handle;
352
+
353
+ if (msg->msg != CURLMSG_DONE) continue;
354
+
355
+ /* Save out the easy handle, because the msg struct becomes invalid
356
+ * whene we call curl_easy_cleanup, curl_multi_remove_handle, or
357
+ * curl_easy_cleanup. */
358
+ easy_handle = msg->easy_handle;
359
+
360
+ r = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &rb_req);
361
+ CHECKN(r);
362
+
363
+ er = curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE,
364
+ &status);
365
+ CHECKN(er);
366
+
367
+ rb_funcall(rb_req, id_done, 2, INT2FIX(msg->data.result),
368
+ INT2FIX(status));
369
+
370
+ r = curl_multi_remove_handle(DATA_PTR(self), easy_handle);
371
+ CHECKN(r);
372
+
373
+ /* Free the handle */
374
+ curl_easy_cleanup(easy_handle);
375
+ }
376
+ }
377
+ end
378
+ end
379
+ end
380
+ end