julien51-em-http-request 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
@@ -0,0 +1 @@
1
+ require 'autotest/redgreen'
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.bundle
2
+ *.o
3
+ Makefile
4
+ mkmf.log
data/LICENSE ADDED
@@ -0,0 +1,58 @@
1
+ Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.co.jp>.
2
+ You can redistribute it and/or modify it under either the terms of the GPL
3
+ (see COPYING.txt file), or the conditions below:
4
+
5
+ 1. You may make and give away verbatim copies of the source form of the
6
+ software without restriction, provided that you duplicate all of the
7
+ original copyright notices and associated disclaimers.
8
+
9
+ 2. You may modify your copy of the software in any way, provided that
10
+ you do at least ONE of the following:
11
+
12
+ a) place your modifications in the Public Domain or otherwise
13
+ make them Freely Available, such as by posting said
14
+ modifications to Usenet or an equivalent medium, or by allowing
15
+ the author to include your modifications in the software.
16
+
17
+ b) use the modified software only within your corporation or
18
+ organization.
19
+
20
+ c) rename any non-standard executables so the names do not conflict
21
+ with standard executables, which must also be provided.
22
+
23
+ d) make other distribution arrangements with the author.
24
+
25
+ 3. You may distribute the software in object code or executable
26
+ form, provided that you do at least ONE of the following:
27
+
28
+ a) distribute the executables and library files of the software,
29
+ together with instructions (in the manual page or equivalent)
30
+ on where to get the original distribution.
31
+
32
+ b) accompany the distribution with the machine-readable source of
33
+ the software.
34
+
35
+ c) give non-standard executables non-standard names, with
36
+ instructions on where to get the original software distribution.
37
+
38
+ d) make other distribution arrangements with the author.
39
+
40
+ 4. You may modify and include the part of the software into any other
41
+ software (possibly commercial). But some files in the distribution
42
+ are not written by the author, so that they are not under this terms.
43
+
44
+ They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
45
+ files under the ./missing directory. See each file for the copying
46
+ condition.
47
+
48
+ 5. The scripts and library files supplied as input to or produced as
49
+ output from the software do not automatically fall under the
50
+ copyright of the software, but belong to whomever generated them,
51
+ and may be sold commercially, and may be aggregated with this
52
+ software.
53
+
54
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
55
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
56
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
57
+ PURPOSE.
58
+
data/README.rdoc ADDED
@@ -0,0 +1,71 @@
1
+ = EM-HTTP-Client
2
+
3
+ EventMachine based HTTP Request interface. Supports streaming response processing, uses Ragel HTTP parser.
4
+ - Simple interface for single & parallel requests via deferred callbacks
5
+ - Automatic gzip & deflate decoding
6
+ - Basic-Auth support
7
+ - Custom timeouts
8
+
9
+ Screencast / Demo of using EM-HTTP-Request:
10
+ - http://everburning.com/news/eventmachine-screencast-em-http-request/
11
+
12
+ == Simple client example
13
+
14
+ EventMachine.run {
15
+ http = EventMachine::HttpRequest.new('http://127.0.0.1/').get :query => {'keyname' => 'value'}, :timeout => 10
16
+
17
+ http.callback {
18
+ p http.response_header.status
19
+ p http.response_header
20
+ p http.response
21
+
22
+ EventMachine.stop
23
+ }
24
+ }
25
+
26
+ == Multi request example
27
+
28
+ EventMachine.run {
29
+ multi = EventMachine::MultiRequest.new
30
+
31
+ # add multiple requests to the multi-handler
32
+ multi.add(EventMachine::HttpRequest.new('http://www.google.com/').get)
33
+ multi.add(EventMachine::HttpRequest.new('http://www.yahoo.com/').get)
34
+
35
+ multi.callback {
36
+ p multi.responses[:succeeded]
37
+ p multi.responses[:failed]
38
+
39
+ EventMachine.stop
40
+ }
41
+ }
42
+
43
+ == Basic-Auth example
44
+
45
+ EventMachine.run {
46
+ http = EventMachine::HttpRequest.new('http://www.website.com/').get :head => {'authorization' => ['user', 'pass']}
47
+
48
+ http.errback { failed }
49
+ http.callback {
50
+ p http.response_header
51
+
52
+ EventMachine.stop
53
+ }
54
+ }
55
+
56
+ == POST example
57
+
58
+ EventMachine.run {
59
+ http1 = EventMachine::HttpRequest.new('http://www.website.com/').post :body => {"key1" => 1, "key2" => [2,3]}
60
+ http2 = EventMachine::HttpRequest.new('http://www.website.com/').post :body => "some data"
61
+
62
+ # ...
63
+ }
64
+
65
+ == Streaming body processing
66
+ EventMachine.run {
67
+ http = EventMachine::HttpRequest.new('http://www.website.com/').get
68
+ http.stream { |chunk| print chunk }
69
+
70
+ # ...
71
+ }
data/Rakefile ADDED
@@ -0,0 +1,84 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+ require 'fileutils'
6
+ include FileUtils
7
+
8
+ # copied from EventMachine.
9
+ MAKE = ENV['MAKE'] || if RUBY_PLATFORM =~ /mswin/ # mingw uses make.
10
+ 'nmake'
11
+ else
12
+ 'make'
13
+ end
14
+
15
+ # Default Rake task is compile
16
+ task :default => :compile
17
+
18
+ # RDoc
19
+ Rake::RDocTask.new(:rdoc) do |task|
20
+ task.rdoc_dir = 'doc'
21
+ task.title = 'EventMachine::HttpRequest'
22
+ task.options = %w(--title HttpRequest --main README --line-numbers)
23
+ task.rdoc_files.include(['lib/**/*.rb'])
24
+ task.rdoc_files.include(['README', 'LICENSE'])
25
+ end
26
+
27
+ # Rebuild parser Ragel
28
+ task :ragel do
29
+ Dir.chdir "ext/http11_client" do
30
+ target = "http11_parser.c"
31
+ File.unlink target if File.exist? target
32
+ sh "ragel http11_parser.rl | rlgen-cd -G2 -o #{target}"
33
+ raise "Failed to build C source" unless File.exist? target
34
+ end
35
+ end
36
+
37
+ task :spec do
38
+ sh 'spec -c test/test_*.rb'
39
+ end
40
+
41
+ def make(makedir)
42
+ Dir.chdir(makedir) { sh MAKE }
43
+ end
44
+
45
+ def extconf(dir)
46
+ Dir.chdir(dir) { ruby "extconf.rb" }
47
+ end
48
+
49
+ def setup_extension(dir, extension)
50
+ ext = "ext/#{dir}"
51
+ ext_so = "#{ext}/#{extension}.#{Config::CONFIG['DLEXT']}"
52
+ ext_files = FileList[
53
+ "#{ext}/*.c",
54
+ "#{ext}/*.h",
55
+ "#{ext}/extconf.rb",
56
+ "#{ext}/Makefile",
57
+ "lib"
58
+ ]
59
+
60
+ task "lib" do
61
+ directory "lib"
62
+ end
63
+
64
+ desc "Builds just the #{extension} extension"
65
+
66
+ mf = (extension + '_makefile').to_sym
67
+
68
+ task mf do |t|
69
+ extconf "#{ext}"
70
+ end
71
+
72
+ task extension.to_sym => [mf] do
73
+ make "#{ext}"
74
+ cp ext_so, "lib"
75
+ end
76
+ end
77
+
78
+ setup_extension("buffer", "em_buffer")
79
+ setup_extension("http11_client", "http11_client")
80
+
81
+ task :compile => [:em_buffer, :http11_client]
82
+
83
+ CLEAN.include ['build/*', '**/*.o', '**/*.so', '**/*.a', '**/*.log', 'pkg']
84
+ CLEAN.include ['ext/buffer/Makefile', 'lib/em_buffer.*', 'lib/http11_client.*']
@@ -0,0 +1,42 @@
1
+ spec = Gem::Specification.new do |s|
2
+ s.name = 'em-http-request'
3
+ s.version = '0.1.9'
4
+ s.date = '2009-03-20'
5
+ s.summary = 'EventMachine based HTTP Request interface'
6
+ s.description = s.summary
7
+ s.email = 'ilya@igvita.com'
8
+ s.homepage = "http://github.com/igrigorik/em-http-request"
9
+ s.has_rdoc = true
10
+ s.authors = ["Ilya Grigorik"]
11
+ s.add_dependency('eventmachine', '>= 0.12.2')
12
+ s.extensions = ["ext/buffer/extconf.rb" , "ext/http11_client/extconf.rb"]
13
+ s.rubyforge_project = "em-http-request"
14
+
15
+ # ruby -rpp -e' pp `git ls-files`.split("\n") '
16
+ s.files = [
17
+ ".autotest",
18
+ ".gitignore",
19
+ "LICENSE",
20
+ "README.rdoc",
21
+ "Rakefile",
22
+ "em-http-request.gemspec",
23
+ "ext/buffer/em_buffer.c",
24
+ "ext/buffer/extconf.rb",
25
+ "ext/http11_client/ext_help.h",
26
+ "ext/http11_client/extconf.rb",
27
+ "ext/http11_client/http11_client.c",
28
+ "ext/http11_client/http11_parser.c",
29
+ "ext/http11_client/http11_parser.h",
30
+ "ext/http11_client/http11_parser.rl",
31
+ "lib/em-http.rb",
32
+ "lib/em-http/client.rb",
33
+ "lib/em-http/core_ext/hash.rb",
34
+ "lib/em-http/decoders.rb",
35
+ "lib/em-http/multi.rb",
36
+ "lib/em-http/request.rb",
37
+ "test/test_hash.rb",
38
+ "test/helper.rb",
39
+ "test/stallion.rb",
40
+ "test/test_multi.rb",
41
+ "test/test_request.rb"]
42
+ end
@@ -0,0 +1,630 @@
1
+ /*
2
+ * Copyright (C) 2007 Tony Arcieri
3
+ * You may redistribute this under the terms of the Ruby license.
4
+ * See LICENSE for details
5
+ */
6
+
7
+ #include "ruby.h"
8
+ #include "rubyio.h"
9
+
10
+ #include <assert.h>
11
+
12
+ #include <string.h>
13
+ #include <time.h>
14
+ #include <errno.h>
15
+
16
+ #ifndef GetReadFile
17
+ #define FPTR_TO_FD(fptr) (fptr->fd)
18
+ #else
19
+ #define FPTR_TO_FD(fptr) (fileno(GetReadFile(fptr)))
20
+ #endif
21
+
22
+ /* Default number of bytes in each node's buffer */
23
+ #define DEFAULT_NODE_SIZE 16384
24
+
25
+ /* Maximum age of a buffer node in a memory pool, in seconds */
26
+ #define MAX_AGE 60
27
+
28
+ /* How often to scan the pool for old nodes */
29
+ #define PURGE_INTERVAL 10
30
+
31
+ struct buffer {
32
+ time_t last_purged_at;
33
+ unsigned size, node_size;
34
+ struct buffer_node *head, *tail;
35
+ struct buffer_node *pool_head, *pool_tail;
36
+
37
+ };
38
+
39
+ struct buffer_node {
40
+ time_t last_used_at;
41
+ unsigned start, end;
42
+ struct buffer_node *next;
43
+ unsigned char data[0];
44
+ };
45
+
46
+ static VALUE mEm = Qnil;
47
+ static VALUE cEm_Buffer = Qnil;
48
+
49
+ static VALUE Em_Buffer_allocate(VALUE klass);
50
+ static void Em_Buffer_mark(struct buffer *);
51
+ static void Em_Buffer_free(struct buffer *);
52
+
53
+ static VALUE Em_Buffer_initialize(int argc, VALUE *argv, VALUE self);
54
+ static VALUE Em_Buffer_clear(VALUE self);
55
+ static VALUE Em_Buffer_size(VALUE self);
56
+ static VALUE Em_Buffer_empty(VALUE self);
57
+ static VALUE Em_Buffer_append(VALUE self, VALUE data);
58
+ static VALUE Em_Buffer_prepend(VALUE self, VALUE data);
59
+ static VALUE Em_Buffer_read(int argc, VALUE *argv, VALUE self);
60
+ static VALUE Em_Buffer_to_str(VALUE self);
61
+ static VALUE Em_Buffer_read_from(VALUE self, VALUE io);
62
+ static VALUE Em_Buffer_write_to(VALUE self, VALUE io);
63
+
64
+ static struct buffer *buffer_new(void);
65
+ static void buffer_clear(struct buffer *buf);
66
+ static void buffer_free(struct buffer *buf);
67
+ static void buffer_gc(struct buffer *buf);
68
+ static void buffer_prepend(struct buffer *buf, char *str, unsigned len);
69
+ static void buffer_append(struct buffer *buf, char *str, unsigned len);
70
+ static void buffer_read(struct buffer *buf, char *str, unsigned len);
71
+ static void buffer_copy(struct buffer *buf, char *str, unsigned len);
72
+ static int buffer_read_from(struct buffer *buf, int fd);
73
+ static int buffer_write_to(struct buffer *buf, int fd);
74
+
75
+ /*
76
+ * High speed buffering geared towards non-blocking I/O.
77
+ *
78
+ * Data is stored in a byte queue implemented as a linked list of equal size
79
+ * chunks. Since every node in the list is the same size they are easily
80
+ * memory pooled. Routines are provided for high speed non-blocking reads
81
+ * and writes from Ruby IO objects.
82
+ */
83
+ void Init_em_buffer()
84
+ {
85
+ mEm = rb_define_module("EventMachine");
86
+ cEm_Buffer = rb_define_class_under(mEm, "Buffer", rb_cObject);
87
+ rb_define_alloc_func(cEm_Buffer, Em_Buffer_allocate);
88
+
89
+ rb_define_method(cEm_Buffer, "initialize", Em_Buffer_initialize, -1);
90
+ rb_define_method(cEm_Buffer, "clear", Em_Buffer_clear, 0);
91
+ rb_define_method(cEm_Buffer, "size", Em_Buffer_size, 0);
92
+ rb_define_method(cEm_Buffer, "empty?", Em_Buffer_empty, 0);
93
+ rb_define_method(cEm_Buffer, "<<", Em_Buffer_append, 1);
94
+ rb_define_method(cEm_Buffer, "append", Em_Buffer_append, 1);
95
+ rb_define_method(cEm_Buffer, "prepend", Em_Buffer_prepend, 1);
96
+ rb_define_method(cEm_Buffer, "read", Em_Buffer_read, -1);
97
+ rb_define_method(cEm_Buffer, "to_str", Em_Buffer_to_str, 0);
98
+ rb_define_method(cEm_Buffer, "read_from", Em_Buffer_read_from, 1);
99
+ rb_define_method(cEm_Buffer, "write_to", Em_Buffer_write_to, 1);
100
+ }
101
+
102
+ static VALUE Em_Buffer_allocate(VALUE klass)
103
+ {
104
+ return Data_Wrap_Struct(klass, Em_Buffer_mark, Em_Buffer_free, buffer_new());
105
+ }
106
+
107
+ static void Em_Buffer_mark(struct buffer *buf)
108
+ {
109
+ /* Walks the pool of unused chunks and frees any that are beyond a certain age */
110
+ buffer_gc(buf);
111
+ }
112
+
113
+ static void Em_Buffer_free(struct buffer *buf)
114
+ {
115
+ buffer_free(buf);
116
+ }
117
+
118
+ /**
119
+ * call-seq:
120
+ * EventMachine::Buffer.new(size = DEFAULT_NODE_SIZE) -> EventMachine::Buffer
121
+ *
122
+ * Create a new EventMachine::Buffer with linked segments of the given size
123
+ */
124
+ static VALUE Em_Buffer_initialize(int argc, VALUE *argv, VALUE self)
125
+ {
126
+ VALUE node_size_obj;
127
+ int node_size;
128
+ struct buffer *buf;
129
+
130
+ if(rb_scan_args(argc, argv, "01", &node_size_obj) == 1) {
131
+ node_size = NUM2INT(node_size_obj);
132
+
133
+ if(node_size < 1) rb_raise(rb_eArgError, "invalid buffer size");
134
+
135
+ Data_Get_Struct(self, struct buffer, buf);
136
+
137
+ /* Make sure we're not changing the buffer size after data has been allocated */
138
+ assert(!buf->head);
139
+ assert(!buf->pool_head);
140
+
141
+ buf->node_size = node_size;
142
+ }
143
+
144
+ return Qnil;
145
+ }
146
+
147
+ /**
148
+ * call-seq:
149
+ * EventMachine::Buffer#clear -> nil
150
+ *
151
+ * Clear all data from the EventMachine::Buffer
152
+ */
153
+ static VALUE Em_Buffer_clear(VALUE self)
154
+ {
155
+ struct buffer *buf;
156
+ Data_Get_Struct(self, struct buffer, buf);
157
+
158
+ buffer_clear(buf);
159
+
160
+ return Qnil;
161
+ }
162
+
163
+ /**
164
+ * call-seq:
165
+ * EventMachine::Buffer#size -> Integer
166
+ *
167
+ * Return the size of the buffer in bytes
168
+ */
169
+ static VALUE Em_Buffer_size(VALUE self)
170
+ {
171
+ struct buffer *buf;
172
+ Data_Get_Struct(self, struct buffer, buf);
173
+
174
+ return INT2NUM(buf->size);
175
+ }
176
+
177
+ /**
178
+ * call-seq:
179
+ * EventMachine::Buffer#empty? -> Boolean
180
+ *
181
+ * Is the buffer empty?
182
+ */
183
+ static VALUE Em_Buffer_empty(VALUE self)
184
+ {
185
+ struct buffer *buf;
186
+ Data_Get_Struct(self, struct buffer, buf);
187
+
188
+ return buf->size > 0 ? Qfalse : Qtrue;
189
+ }
190
+
191
+ /**
192
+ * call-seq:
193
+ * EventMachine::Buffer#append(data) -> String
194
+ *
195
+ * Append the given data to the end of the buffer
196
+ */
197
+ static VALUE Em_Buffer_append(VALUE self, VALUE data)
198
+ {
199
+ struct buffer *buf;
200
+ Data_Get_Struct(self, struct buffer, buf);
201
+
202
+ /* Is this needed? Never seen anyone else do it... */
203
+ data = rb_convert_type(data, T_STRING, "String", "to_str");
204
+ buffer_append(buf, RSTRING_PTR(data), RSTRING_LEN(data));
205
+
206
+ return data;
207
+ }
208
+
209
+ /**
210
+ * call-seq:
211
+ * EventMachine::Buffer#prepend(data) -> String
212
+ *
213
+ * Prepend the given data to the beginning of the buffer
214
+ */
215
+ static VALUE Em_Buffer_prepend(VALUE self, VALUE data)
216
+ {
217
+ struct buffer *buf;
218
+ Data_Get_Struct(self, struct buffer, buf);
219
+
220
+ data = rb_convert_type(data, T_STRING, "String", "to_str");
221
+ buffer_prepend(buf, RSTRING_PTR(data), RSTRING_LEN(data));
222
+
223
+ return data;
224
+ }
225
+
226
+ /**
227
+ * call-seq:
228
+ * EventMachine::Buffer#read(length = nil) -> String
229
+ *
230
+ * Read the specified abount of data from the buffer. If no value
231
+ * is given the entire contents of the buffer are returned. Any data
232
+ * read from the buffer is cleared.
233
+ */
234
+ static VALUE Em_Buffer_read(int argc, VALUE *argv, VALUE self)
235
+ {
236
+ VALUE length_obj, str;
237
+ int length;
238
+ struct buffer *buf;
239
+
240
+ Data_Get_Struct(self, struct buffer, buf);
241
+
242
+ if(rb_scan_args(argc, argv, "01", &length_obj) == 1) {
243
+ length = NUM2INT(length_obj);
244
+ } else {
245
+ if(buf->size == 0)
246
+ return rb_str_new2("");
247
+
248
+ length = buf->size;
249
+ }
250
+
251
+ if(length > buf->size)
252
+ length = buf->size;
253
+
254
+ if(length < 1)
255
+ rb_raise(rb_eArgError, "length must be greater than zero");
256
+
257
+ str = rb_str_new(0, length);
258
+ buffer_read(buf, RSTRING_PTR(str), length);
259
+
260
+ return str;
261
+ }
262
+
263
+ /**
264
+ * call-seq:
265
+ * EventMachine::Buffer#to_str -> String
266
+ *
267
+ * Convert the Buffer to a String. The original buffer is unmodified.
268
+ */
269
+ static VALUE Em_Buffer_to_str(VALUE self) {
270
+ VALUE str;
271
+ struct buffer *buf;
272
+
273
+ Data_Get_Struct(self, struct buffer, buf);
274
+
275
+ str = rb_str_new(0, buf->size);
276
+ buffer_copy(buf, RSTRING_PTR(str), buf->size);
277
+
278
+ return str;
279
+ }
280
+
281
+ /**
282
+ * call-seq:
283
+ * EventMachine::Buffer#read_from(io) -> Integer
284
+ *
285
+ * Perform a nonblocking read of the the given IO object and fill
286
+ * the buffer with any data received. The call will read as much
287
+ * data as it can until the read would block.
288
+ */
289
+ static VALUE Em_Buffer_read_from(VALUE self, VALUE io) {
290
+ struct buffer *buf;
291
+ #if HAVE_RB_IO_T
292
+ rb_io_t *fptr;
293
+ #else
294
+ OpenFile *fptr;
295
+ #endif
296
+
297
+ Data_Get_Struct(self, struct buffer, buf);
298
+ GetOpenFile(rb_convert_type(io, T_FILE, "IO", "to_io"), fptr);
299
+ rb_io_set_nonblock(fptr);
300
+
301
+ return INT2NUM(buffer_read_from(buf, FPTR_TO_FD(fptr)));
302
+ }
303
+
304
+ /**
305
+ * call-seq:
306
+ * EventMachine::Buffer#write_to(io) -> Integer
307
+ *
308
+ * Perform a nonblocking write of the buffer to the given IO object.
309
+ * As much data as possible is written until the call would block.
310
+ * Any data which is written is removed from the buffer.
311
+ */
312
+ static VALUE Em_Buffer_write_to(VALUE self, VALUE io) {
313
+ struct buffer *buf;
314
+ #if HAVE_RB_IO_T
315
+ rb_io_t *fptr;
316
+ #else
317
+ OpenFile *fptr;
318
+ #endif
319
+
320
+ Data_Get_Struct(self, struct buffer, buf);
321
+ GetOpenFile(rb_convert_type(io, T_FILE, "IO", "to_io"), fptr);
322
+ rb_io_set_nonblock(fptr);
323
+
324
+ return INT2NUM(buffer_write_to(buf, FPTR_TO_FD(fptr)));
325
+ }
326
+
327
+ /*
328
+ * Ruby bindings end here. Below is the actual implementation of
329
+ * the underlying data structures.
330
+ */
331
+
332
+ /* Create a new buffer */
333
+ static struct buffer *buffer_new(void)
334
+ {
335
+ struct buffer *buf;
336
+
337
+ buf = (struct buffer *)xmalloc(sizeof(struct buffer));
338
+ buf->head = buf->tail = buf->pool_head = buf->pool_tail = 0;
339
+ buf->size = 0;
340
+ buf->node_size = DEFAULT_NODE_SIZE;
341
+ time(&buf->last_purged_at);
342
+
343
+ return buf;
344
+ }
345
+
346
+ /* Clear all data from a buffer */
347
+ static void buffer_clear(struct buffer *buf)
348
+ {
349
+ struct buffer_node *tmp;
350
+
351
+ /* Move everything into the buffer pool */
352
+ if(!buf->pool_tail)
353
+ buf->pool_head = buf->pool_tail = buf->head;
354
+ else
355
+ buf->pool_tail->next = buf->head;
356
+
357
+ buf->head = buf->tail = 0;
358
+ buf->size = 0;
359
+ }
360
+
361
+ /* Free a buffer */
362
+ static void buffer_free(struct buffer *buf)
363
+ {
364
+ struct buffer_node *tmp;
365
+
366
+ buffer_clear(buf);
367
+
368
+ while(buf->pool_head) {
369
+ tmp = buf->pool_head;
370
+ buf->pool_head = tmp->next;
371
+ free(tmp);
372
+ }
373
+
374
+ free(buf);
375
+ }
376
+
377
+ /* Run through the pool and find elements that haven't been used for awhile */
378
+ static void buffer_gc(struct buffer *buf)
379
+ {
380
+ struct buffer_node *cur, *tmp;
381
+ time_t now;
382
+ time(&now);
383
+
384
+ /* Only purge if we've passed the purge interval */
385
+ if(now - buf->last_purged_at < PURGE_INTERVAL)
386
+ return;
387
+
388
+ buf->last_purged_at = now;
389
+
390
+ while(buf->pool_head && now - buf->pool_head->last_used_at >= MAX_AGE) {
391
+ tmp = buf->pool_head;
392
+ buf->pool_head = buf->pool_head->next;
393
+ free(tmp);
394
+ }
395
+
396
+ if(!buf->pool_head)
397
+ buf->pool_tail = 0;
398
+ }
399
+
400
+ /* Create a new buffer_node (or pull one from the memory pool) */
401
+ static struct buffer_node *buffer_node_new(struct buffer *buf)
402
+ {
403
+ struct buffer_node *node;
404
+
405
+ /* Pull from the memory pool if available */
406
+ if(buf->pool_head) {
407
+ node = buf->pool_head;
408
+ buf->pool_head = node->next;
409
+
410
+ if(node->next)
411
+ node->next = 0;
412
+ else
413
+ buf->pool_tail = 0;
414
+ } else {
415
+ node = (struct buffer_node *)xmalloc(sizeof(struct buffer_node) + buf->node_size);
416
+ node->next = 0;
417
+ }
418
+
419
+ node->start = node->end = 0;
420
+ return node;
421
+ }
422
+
423
+ /* Free a buffer node (i.e. return it to the memory pool) */
424
+ static void buffer_node_free(struct buffer *buf, struct buffer_node *node)
425
+ {
426
+ /* Store when the node was freed */
427
+ time(&node->last_used_at);
428
+
429
+ node->next = buf->pool_head;
430
+ buf->pool_head = node;
431
+
432
+ if(!buf->pool_tail)
433
+ buf->pool_tail = node;
434
+ }
435
+
436
+ /* Prepend data to the front of the buffer */
437
+ static void buffer_prepend(struct buffer *buf, char *str, unsigned len)
438
+ {
439
+ struct buffer_node *node, *tmp;
440
+ buf->size += len;
441
+
442
+ /* If it fits in the beginning of the head */
443
+ if(buf->head && buf->head->start >= len) {
444
+ buf->head->start -= len;
445
+ memcpy(buf->head->data + buf->head->start, str, len);
446
+ } else {
447
+ node = buffer_node_new(buf);
448
+ node->next = buf->head;
449
+ buf->head = node;
450
+ if(!buf->tail) buf->tail = node;
451
+
452
+ while(len > buf->node_size) {
453
+ memcpy(node->data, str, buf->node_size);
454
+ node->end = buf->node_size;
455
+
456
+ tmp = buffer_node_new(buf);
457
+ tmp->next = node->next;
458
+ node->next = tmp;
459
+
460
+ if(buf->tail == node) buf->tail = tmp;
461
+ node = tmp;
462
+
463
+ str += buf->node_size;
464
+ len -= buf->node_size;
465
+ }
466
+
467
+ if(len > 0) {
468
+ memcpy(node->data, str, len);
469
+ node->end = len;
470
+ }
471
+ }
472
+ }
473
+
474
+ /* Append data to the front of the buffer */
475
+ static void buffer_append(struct buffer *buf, char *str, unsigned len)
476
+ {
477
+ unsigned nbytes;
478
+ buf->size += len;
479
+
480
+ /* If it fits in the remaining space in the tail */
481
+ if(buf->tail && len <= buf->node_size - buf->tail->end) {
482
+ memcpy(buf->tail->data + buf->tail->end, str, len);
483
+ buf->tail->end += len;
484
+ return;
485
+ }
486
+
487
+ /* Empty list needs initialized */
488
+ if(!buf->head) {
489
+ buf->head = buffer_node_new(buf);
490
+ buf->tail = buf->head;
491
+ }
492
+
493
+ /* Build links out of the data */
494
+ while(len > 0) {
495
+ nbytes = buf->node_size - buf->tail->end;
496
+ if(len < nbytes) nbytes = len;
497
+
498
+ memcpy(buf->tail->data + buf->tail->end, str, nbytes);
499
+ str += nbytes;
500
+ len -= nbytes;
501
+
502
+ buf->tail->end += nbytes;
503
+
504
+ if(len > 0) {
505
+ buf->tail->next = buffer_node_new(buf);
506
+ buf->tail = buf->tail->next;
507
+ }
508
+ }
509
+ }
510
+
511
+ /* Read data from the buffer (and clear what we've read) */
512
+ static void buffer_read(struct buffer *buf, char *str, unsigned len)
513
+ {
514
+ unsigned nbytes;
515
+ struct buffer_node *tmp;
516
+
517
+ while(buf->size > 0 && len > 0) {
518
+ nbytes = buf->head->end - buf->head->start;
519
+ if(len < nbytes) nbytes = len;
520
+
521
+ memcpy(str, buf->head->data + buf->head->start, nbytes);
522
+ str += nbytes;
523
+ len -= nbytes;
524
+
525
+ buf->head->start += nbytes;
526
+ buf->size -= nbytes;
527
+
528
+ if(buf->head->start == buf->head->end) {
529
+ tmp = buf->head;
530
+ buf->head = tmp->next;
531
+ buffer_node_free(buf, tmp);
532
+
533
+ if(!buf->head) buf->tail = 0;
534
+ }
535
+ }
536
+ }
537
+
538
+ /* Copy data from the buffer without clearing it */
539
+ static void buffer_copy(struct buffer *buf, char *str, unsigned len)
540
+ {
541
+ unsigned nbytes;
542
+ struct buffer_node *node;
543
+
544
+ node = buf->head;
545
+ while(node && len > 0) {
546
+ nbytes = node->end - node->start;
547
+ if(len < nbytes) nbytes = len;
548
+
549
+ memcpy(str, node->data + node->start, nbytes);
550
+ str += nbytes;
551
+ len -= nbytes;
552
+
553
+ if(node->start + nbytes == node->end)
554
+ node = node->next;
555
+ }
556
+ }
557
+
558
+ /* Write data from the buffer to a file descriptor */
559
+ static int buffer_write_to(struct buffer *buf, int fd)
560
+ {
561
+ int bytes_written, total_bytes_written = 0;
562
+ struct buffer_node *tmp;
563
+
564
+ while(buf->head) {
565
+ bytes_written = write(fd, buf->head->data + buf->head->start, buf->head->end - buf->head->start);
566
+
567
+ /* If the write failed... */
568
+ if(bytes_written < 0) {
569
+ if(errno != EAGAIN)
570
+ rb_sys_fail("write");
571
+
572
+ return total_bytes_written;
573
+ }
574
+
575
+ total_bytes_written += bytes_written;
576
+ buf->size -= bytes_written;
577
+
578
+ /* If the write blocked... */
579
+ if(bytes_written < buf->head->end - buf->head->start) {
580
+ buf->head->start += bytes_written;
581
+ return total_bytes_written;
582
+ }
583
+
584
+ /* Otherwise we wrote the whole buffer */
585
+ tmp = buf->head;
586
+ buf->head = tmp->next;
587
+ buffer_node_free(buf, tmp);
588
+
589
+ if(!buf->head) buf->tail = 0;
590
+ }
591
+
592
+ return total_bytes_written;
593
+ }
594
+
595
+ /* Read data from a file descriptor to a buffer */
596
+ /* Append data to the front of the buffer */
597
+ static int buffer_read_from(struct buffer *buf, int fd)
598
+ {
599
+ int bytes_read, total_bytes_read = 0;
600
+ unsigned nbytes;
601
+
602
+ /* Empty list needs initialized */
603
+ if(!buf->head) {
604
+ buf->head = buffer_node_new(buf);
605
+ buf->tail = buf->head;
606
+ }
607
+
608
+ do {
609
+ nbytes = buf->node_size - buf->tail->end;
610
+ bytes_read = read(fd, buf->tail->data + buf->tail->end, nbytes);
611
+
612
+ if(bytes_read < 1) {
613
+ if(errno != EAGAIN)
614
+ rb_sys_fail("read");
615
+
616
+ return total_bytes_read;
617
+ }
618
+
619
+ total_bytes_read += bytes_read;
620
+ buf->tail->end += nbytes;
621
+ buf->size += nbytes;
622
+
623
+ if(buf->tail->end == buf->node_size) {
624
+ buf->tail->next = buffer_node_new(buf);
625
+ buf->tail = buf->tail->next;
626
+ }
627
+ } while(bytes_read == nbytes);
628
+
629
+ return total_bytes_read;
630
+ }