em-http-request 0.2.14 → 0.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of em-http-request might be problematic. Click here for more details.
- data/Changelog.md +5 -0
- data/Gemfile +15 -0
- data/Rakefile +6 -7
- data/VERSION +1 -1
- data/autotest/discover.rb +1 -0
- data/em-http-request.gemspec +3 -2
- data/ext/http11_client/http11_parser.c +418 -418
- data/ext/http11_client/http11_parser.rl +170 -170
- data/lib/em-http/client.rb +20 -4
- data/lib/em-http/multi.rb +55 -55
- data/lib/em-http/request.rb +1 -1
- data/spec/helper.rb +2 -2
- data/spec/mock_spec.rb +15 -11
- data/spec/request_spec.rb +20 -0
- data/spec/stub_server.rb +22 -22
- metadata +4 -3
- data/spec/spec.opts +0 -7
@@ -1,170 +1,170 @@
|
|
1
|
-
/**
|
2
|
-
* Copyright (c) 2005 Zed A. Shaw
|
3
|
-
* You can redistribute it and/or modify it under the same terms as Ruby.
|
4
|
-
*/
|
5
|
-
|
6
|
-
#include "http11_parser.h"
|
7
|
-
#include <stdio.h>
|
8
|
-
#include <assert.h>
|
9
|
-
#include <stdlib.h>
|
10
|
-
#include <ctype.h>
|
11
|
-
#include <string.h>
|
12
|
-
|
13
|
-
#define LEN(AT, FPC) (FPC - buffer - parser->AT)
|
14
|
-
#define MARK(M,FPC) (parser->M = (FPC) - buffer)
|
15
|
-
#define PTR_TO(F) (buffer + parser->F)
|
16
|
-
#define L(M) fprintf(stderr, "" # M "\n");
|
17
|
-
|
18
|
-
|
19
|
-
/** machine **/
|
20
|
-
%%{
|
21
|
-
machine httpclient_parser;
|
22
|
-
|
23
|
-
action mark {MARK(mark, fpc); }
|
24
|
-
|
25
|
-
action start_field { MARK(field_start, fpc); }
|
26
|
-
|
27
|
-
action write_field {
|
28
|
-
parser->field_len = LEN(field_start, fpc);
|
29
|
-
}
|
30
|
-
|
31
|
-
action start_value { MARK(mark, fpc); }
|
32
|
-
|
33
|
-
action write_value {
|
34
|
-
parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc));
|
35
|
-
}
|
36
|
-
|
37
|
-
action reason_phrase {
|
38
|
-
parser->reason_phrase(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
39
|
-
}
|
40
|
-
|
41
|
-
action status_code {
|
42
|
-
parser->status_code(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
43
|
-
}
|
44
|
-
|
45
|
-
action http_version {
|
46
|
-
parser->http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
47
|
-
}
|
48
|
-
|
49
|
-
action chunk_size {
|
50
|
-
parser->chunk_size(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
51
|
-
}
|
52
|
-
|
53
|
-
action last_chunk {
|
54
|
-
parser->last_chunk(parser->data, NULL, 0);
|
55
|
-
}
|
56
|
-
|
57
|
-
action done {
|
58
|
-
parser->body_start = fpc - buffer + 1;
|
59
|
-
if(parser->header_done != NULL)
|
60
|
-
parser->header_done(parser->data, fpc + 1, pe - fpc - 1);
|
61
|
-
fbreak;
|
62
|
-
}
|
63
|
-
|
64
|
-
# line endings
|
65
|
-
CRLF = ("\r\n" | "\n");
|
66
|
-
|
67
|
-
# character types
|
68
|
-
CTL = (cntrl | 127);
|
69
|
-
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
|
70
|
-
|
71
|
-
# elements
|
72
|
-
token = (ascii -- (CTL | tspecials));
|
73
|
-
|
74
|
-
Reason_Phrase = (any -- CRLF)* >mark %reason_phrase;
|
75
|
-
Status_Code = digit{3} >mark %status_code;
|
76
|
-
http_number = (digit+ "." digit+) ;
|
77
|
-
HTTP_Version = ("HTTP/" http_number) >mark %http_version ;
|
78
|
-
Status_Line = HTTP_Version " " Status_Code " "? Reason_Phrase :> CRLF;
|
79
|
-
|
80
|
-
field_name = token+ >start_field %write_field;
|
81
|
-
field_value = any* >start_value %write_value;
|
82
|
-
message_header = field_name ":" " "* field_value :> CRLF;
|
83
|
-
|
84
|
-
Response = Status_Line (message_header)* (CRLF @done);
|
85
|
-
|
86
|
-
chunk_ext_val = token+;
|
87
|
-
chunk_ext_name = token+;
|
88
|
-
chunk_extension = (";" chunk_ext_name >start_field %write_field %start_value ("=" chunk_ext_val >start_value)? %write_value )*;
|
89
|
-
last_chunk = "0"? chunk_extension :> (CRLF @last_chunk @done);
|
90
|
-
chunk_size = xdigit+;
|
91
|
-
chunk = chunk_size >mark %chunk_size chunk_extension space* :> (CRLF @done);
|
92
|
-
Chunked_Header = (chunk | last_chunk);
|
93
|
-
|
94
|
-
main := Response | Chunked_Header;
|
95
|
-
}%%
|
96
|
-
|
97
|
-
/** Data **/
|
98
|
-
%% write data;
|
99
|
-
|
100
|
-
int httpclient_parser_init(httpclient_parser *parser) {
|
101
|
-
int cs = 0;
|
102
|
-
%% write init;
|
103
|
-
parser->cs = cs;
|
104
|
-
parser->body_start = 0;
|
105
|
-
parser->content_len = 0;
|
106
|
-
parser->mark = 0;
|
107
|
-
parser->nread = 0;
|
108
|
-
parser->field_len = 0;
|
109
|
-
parser->field_start = 0;
|
110
|
-
|
111
|
-
return(1);
|
112
|
-
}
|
113
|
-
|
114
|
-
|
115
|
-
/** exec **/
|
116
|
-
size_t httpclient_parser_execute(httpclient_parser *parser, const char *buffer, size_t len, size_t off) {
|
117
|
-
const char *p, *pe;
|
118
|
-
int cs = parser->cs;
|
119
|
-
|
120
|
-
assert(off <= len && "offset past end of buffer");
|
121
|
-
|
122
|
-
p = buffer+off;
|
123
|
-
pe = buffer+len;
|
124
|
-
|
125
|
-
assert(*pe == '\0' && "pointer does not end on NUL");
|
126
|
-
assert(pe - p == len - off && "pointers aren't same distance");
|
127
|
-
|
128
|
-
|
129
|
-
%% write exec;
|
130
|
-
|
131
|
-
parser->cs = cs;
|
132
|
-
parser->nread += p - (buffer + off);
|
133
|
-
|
134
|
-
assert(p <= pe && "buffer overflow after parsing execute");
|
135
|
-
assert(parser->nread <= len && "nread longer than length");
|
136
|
-
assert(parser->body_start <= len && "body starts after buffer end");
|
137
|
-
assert(parser->mark < len && "mark is after buffer end");
|
138
|
-
assert(parser->field_len <= len && "field has length longer than whole buffer");
|
139
|
-
assert(parser->field_start < len && "field starts after buffer end");
|
140
|
-
|
141
|
-
if(parser->body_start) {
|
142
|
-
/* final \r\n combo encountered so stop right here */
|
143
|
-
parser->nread = parser->body_start;
|
144
|
-
}
|
145
|
-
|
146
|
-
return(parser->nread);
|
147
|
-
}
|
148
|
-
|
149
|
-
int httpclient_parser_finish(httpclient_parser *parser)
|
150
|
-
{
|
151
|
-
int cs = parser->cs;
|
152
|
-
|
153
|
-
parser->cs = cs;
|
154
|
-
|
155
|
-
if (httpclient_parser_has_error(parser) ) {
|
156
|
-
return -1;
|
157
|
-
} else if (httpclient_parser_is_finished(parser) ) {
|
158
|
-
return 1;
|
159
|
-
} else {
|
160
|
-
return 0;
|
161
|
-
}
|
162
|
-
}
|
163
|
-
|
164
|
-
int httpclient_parser_has_error(httpclient_parser *parser) {
|
165
|
-
return parser->cs == httpclient_parser_error;
|
166
|
-
}
|
167
|
-
|
168
|
-
int httpclient_parser_is_finished(httpclient_parser *parser) {
|
169
|
-
return parser->cs == httpclient_parser_first_final;
|
170
|
-
}
|
1
|
+
/**
|
2
|
+
* Copyright (c) 2005 Zed A. Shaw
|
3
|
+
* You can redistribute it and/or modify it under the same terms as Ruby.
|
4
|
+
*/
|
5
|
+
|
6
|
+
#include "http11_parser.h"
|
7
|
+
#include <stdio.h>
|
8
|
+
#include <assert.h>
|
9
|
+
#include <stdlib.h>
|
10
|
+
#include <ctype.h>
|
11
|
+
#include <string.h>
|
12
|
+
|
13
|
+
#define LEN(AT, FPC) (FPC - buffer - parser->AT)
|
14
|
+
#define MARK(M,FPC) (parser->M = (FPC) - buffer)
|
15
|
+
#define PTR_TO(F) (buffer + parser->F)
|
16
|
+
#define L(M) fprintf(stderr, "" # M "\n");
|
17
|
+
|
18
|
+
|
19
|
+
/** machine **/
|
20
|
+
%%{
|
21
|
+
machine httpclient_parser;
|
22
|
+
|
23
|
+
action mark {MARK(mark, fpc); }
|
24
|
+
|
25
|
+
action start_field { MARK(field_start, fpc); }
|
26
|
+
|
27
|
+
action write_field {
|
28
|
+
parser->field_len = LEN(field_start, fpc);
|
29
|
+
}
|
30
|
+
|
31
|
+
action start_value { MARK(mark, fpc); }
|
32
|
+
|
33
|
+
action write_value {
|
34
|
+
parser->http_field(parser->data, PTR_TO(field_start), parser->field_len, PTR_TO(mark), LEN(mark, fpc));
|
35
|
+
}
|
36
|
+
|
37
|
+
action reason_phrase {
|
38
|
+
parser->reason_phrase(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
39
|
+
}
|
40
|
+
|
41
|
+
action status_code {
|
42
|
+
parser->status_code(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
43
|
+
}
|
44
|
+
|
45
|
+
action http_version {
|
46
|
+
parser->http_version(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
47
|
+
}
|
48
|
+
|
49
|
+
action chunk_size {
|
50
|
+
parser->chunk_size(parser->data, PTR_TO(mark), LEN(mark, fpc));
|
51
|
+
}
|
52
|
+
|
53
|
+
action last_chunk {
|
54
|
+
parser->last_chunk(parser->data, NULL, 0);
|
55
|
+
}
|
56
|
+
|
57
|
+
action done {
|
58
|
+
parser->body_start = fpc - buffer + 1;
|
59
|
+
if(parser->header_done != NULL)
|
60
|
+
parser->header_done(parser->data, fpc + 1, pe - fpc - 1);
|
61
|
+
fbreak;
|
62
|
+
}
|
63
|
+
|
64
|
+
# line endings
|
65
|
+
CRLF = ("\r\n" | "\n");
|
66
|
+
|
67
|
+
# character types
|
68
|
+
CTL = (cntrl | 127);
|
69
|
+
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
|
70
|
+
|
71
|
+
# elements
|
72
|
+
token = (ascii -- (CTL | tspecials));
|
73
|
+
|
74
|
+
Reason_Phrase = (any -- CRLF)* >mark %reason_phrase;
|
75
|
+
Status_Code = digit{3} >mark %status_code;
|
76
|
+
http_number = (digit+ "." digit+) ;
|
77
|
+
HTTP_Version = ("HTTP/" http_number) >mark %http_version ;
|
78
|
+
Status_Line = HTTP_Version " " Status_Code " "? Reason_Phrase :> CRLF;
|
79
|
+
|
80
|
+
field_name = token+ >start_field %write_field;
|
81
|
+
field_value = any* >start_value %write_value;
|
82
|
+
message_header = field_name ":" " "* field_value :> CRLF;
|
83
|
+
|
84
|
+
Response = Status_Line (message_header)* (CRLF @done);
|
85
|
+
|
86
|
+
chunk_ext_val = token+;
|
87
|
+
chunk_ext_name = token+;
|
88
|
+
chunk_extension = (";" chunk_ext_name >start_field %write_field %start_value ("=" chunk_ext_val >start_value)? %write_value )*;
|
89
|
+
last_chunk = "0"? chunk_extension :> (CRLF @last_chunk @done);
|
90
|
+
chunk_size = xdigit+;
|
91
|
+
chunk = chunk_size >mark %chunk_size chunk_extension space* :> (CRLF @done);
|
92
|
+
Chunked_Header = (chunk | last_chunk);
|
93
|
+
|
94
|
+
main := Response | Chunked_Header;
|
95
|
+
}%%
|
96
|
+
|
97
|
+
/** Data **/
|
98
|
+
%% write data;
|
99
|
+
|
100
|
+
int httpclient_parser_init(httpclient_parser *parser) {
|
101
|
+
int cs = 0;
|
102
|
+
%% write init;
|
103
|
+
parser->cs = cs;
|
104
|
+
parser->body_start = 0;
|
105
|
+
parser->content_len = 0;
|
106
|
+
parser->mark = 0;
|
107
|
+
parser->nread = 0;
|
108
|
+
parser->field_len = 0;
|
109
|
+
parser->field_start = 0;
|
110
|
+
|
111
|
+
return(1);
|
112
|
+
}
|
113
|
+
|
114
|
+
|
115
|
+
/** exec **/
|
116
|
+
size_t httpclient_parser_execute(httpclient_parser *parser, const char *buffer, size_t len, size_t off) {
|
117
|
+
const char *p, *pe;
|
118
|
+
int cs = parser->cs;
|
119
|
+
|
120
|
+
assert(off <= len && "offset past end of buffer");
|
121
|
+
|
122
|
+
p = buffer+off;
|
123
|
+
pe = buffer+len;
|
124
|
+
|
125
|
+
assert(*pe == '\0' && "pointer does not end on NUL");
|
126
|
+
assert(pe - p == len - off && "pointers aren't same distance");
|
127
|
+
|
128
|
+
|
129
|
+
%% write exec;
|
130
|
+
|
131
|
+
parser->cs = cs;
|
132
|
+
parser->nread += p - (buffer + off);
|
133
|
+
|
134
|
+
assert(p <= pe && "buffer overflow after parsing execute");
|
135
|
+
assert(parser->nread <= len && "nread longer than length");
|
136
|
+
assert(parser->body_start <= len && "body starts after buffer end");
|
137
|
+
assert(parser->mark < len && "mark is after buffer end");
|
138
|
+
assert(parser->field_len <= len && "field has length longer than whole buffer");
|
139
|
+
assert(parser->field_start < len && "field starts after buffer end");
|
140
|
+
|
141
|
+
if(parser->body_start) {
|
142
|
+
/* final \r\n combo encountered so stop right here */
|
143
|
+
parser->nread = parser->body_start;
|
144
|
+
}
|
145
|
+
|
146
|
+
return(parser->nread);
|
147
|
+
}
|
148
|
+
|
149
|
+
int httpclient_parser_finish(httpclient_parser *parser)
|
150
|
+
{
|
151
|
+
int cs = parser->cs;
|
152
|
+
|
153
|
+
parser->cs = cs;
|
154
|
+
|
155
|
+
if (httpclient_parser_has_error(parser) ) {
|
156
|
+
return -1;
|
157
|
+
} else if (httpclient_parser_is_finished(parser) ) {
|
158
|
+
return 1;
|
159
|
+
} else {
|
160
|
+
return 0;
|
161
|
+
}
|
162
|
+
}
|
163
|
+
|
164
|
+
int httpclient_parser_has_error(httpclient_parser *parser) {
|
165
|
+
return parser->cs == httpclient_parser_error;
|
166
|
+
}
|
167
|
+
|
168
|
+
int httpclient_parser_is_finished(httpclient_parser *parser) {
|
169
|
+
return parser->cs == httpclient_parser_first_final;
|
170
|
+
}
|
data/lib/em-http/client.rb
CHANGED
@@ -68,10 +68,14 @@ module EventMachine
|
|
68
68
|
# When parsing chunked encodings this is set
|
69
69
|
attr_accessor :http_chunk_size
|
70
70
|
|
71
|
+
def initialize
|
72
|
+
super
|
73
|
+
@http_chunk_size = '0'
|
74
|
+
end
|
75
|
+
|
71
76
|
# Size of the chunk as an integer
|
72
77
|
def chunk_size
|
73
|
-
|
74
|
-
@chunk_size = @http_chunk_size ? @http_chunk_size.to_i(base=16) : 0
|
78
|
+
@http_chunk_size.to_i(base=16)
|
75
79
|
end
|
76
80
|
end
|
77
81
|
|
@@ -221,6 +225,7 @@ module EventMachine
|
|
221
225
|
TRANSFER_ENCODING="TRANSFER_ENCODING"
|
222
226
|
CONTENT_ENCODING="CONTENT_ENCODING"
|
223
227
|
CONTENT_LENGTH="CONTENT_LENGTH"
|
228
|
+
CONTENT_TYPE="CONTENT_TYPE".freeze
|
224
229
|
LAST_MODIFIED="LAST_MODIFIED"
|
225
230
|
KEEP_ALIVE="CONNECTION"
|
226
231
|
SET_COOKIE="SET_COOKIE"
|
@@ -242,8 +247,10 @@ module EventMachine
|
|
242
247
|
@redirects = 0
|
243
248
|
@response = ''
|
244
249
|
@error = ''
|
250
|
+
@headers = nil
|
245
251
|
@last_effective_url = nil
|
246
252
|
@content_decoder = nil
|
253
|
+
@content_charset = nil
|
247
254
|
@stream = nil
|
248
255
|
@disconnect = nil
|
249
256
|
@state = :response_header
|
@@ -467,6 +474,7 @@ module EventMachine
|
|
467
474
|
end
|
468
475
|
|
469
476
|
def on_decoded_body_data(data)
|
477
|
+
data.force_encoding @content_charset if @content_charset
|
470
478
|
if @stream
|
471
479
|
@stream.call(data)
|
472
480
|
else
|
@@ -474,8 +482,12 @@ module EventMachine
|
|
474
482
|
end
|
475
483
|
end
|
476
484
|
|
485
|
+
def finished?
|
486
|
+
@state == :finished || (@state == :body && @bytes_remaining.nil?)
|
487
|
+
end
|
488
|
+
|
477
489
|
def unbind
|
478
|
-
if
|
490
|
+
if finished? && (@last_effective_url != @uri) && (@redirects < @options[:redirects])
|
479
491
|
begin
|
480
492
|
# update uri to redirect location if we're allowed to traverse deeper
|
481
493
|
@uri = @last_effective_url
|
@@ -495,7 +507,7 @@ module EventMachine
|
|
495
507
|
on_error(e.message, true)
|
496
508
|
end
|
497
509
|
else
|
498
|
-
if
|
510
|
+
if finished?
|
499
511
|
succeed(self)
|
500
512
|
else
|
501
513
|
@disconnect.call(self) if @state == :websocket and @disconnect
|
@@ -639,6 +651,10 @@ module EventMachine
|
|
639
651
|
end
|
640
652
|
end
|
641
653
|
|
654
|
+
if ''.respond_to?(:force_encoding) && /;\s*charset=\s*(.+?)\s*(;|$)/.match(response_header[CONTENT_TYPE])
|
655
|
+
@content_charset = Encoding.find $1
|
656
|
+
end
|
657
|
+
|
642
658
|
true
|
643
659
|
end
|
644
660
|
|
data/lib/em-http/multi.rb
CHANGED
@@ -1,55 +1,55 @@
|
|
1
|
-
module EventMachine
|
2
|
-
|
3
|
-
# EventMachine based Multi request client, based on a streaming HTTPRequest class,
|
4
|
-
# which allows you to open multiple parallel connections and return only when all
|
5
|
-
# of them finish. (i.e. ideal for parallelizing workloads)
|
6
|
-
#
|
7
|
-
# == Example
|
8
|
-
#
|
9
|
-
# EventMachine.run {
|
10
|
-
#
|
11
|
-
# multi = EventMachine::MultiRequest.new
|
12
|
-
#
|
13
|
-
# # add multiple requests to the multi-handler
|
14
|
-
# multi.add(EventMachine::HttpRequest.new('http://www.google.com/').get)
|
15
|
-
# multi.add(EventMachine::HttpRequest.new('http://www.yahoo.com/').get)
|
16
|
-
#
|
17
|
-
# multi.callback {
|
18
|
-
# p multi.responses[:succeeded]
|
19
|
-
# p multi.responses[:failed]
|
20
|
-
#
|
21
|
-
# EventMachine.stop
|
22
|
-
# }
|
23
|
-
# }
|
24
|
-
#
|
25
|
-
|
26
|
-
class MultiRequest
|
27
|
-
include EventMachine::Deferrable
|
28
|
-
|
29
|
-
attr_reader :requests, :responses
|
30
|
-
|
31
|
-
def initialize(conns=[], &block)
|
32
|
-
@requests = []
|
33
|
-
@responses = {:succeeded => [], :failed => []}
|
34
|
-
|
35
|
-
conns.each {|conn| add(conn)}
|
36
|
-
callback(&block) if block_given?
|
37
|
-
end
|
38
|
-
|
39
|
-
def add(conn)
|
40
|
-
@requests.push(conn)
|
41
|
-
|
42
|
-
conn.callback { @responses[:succeeded].push(conn); check_progress }
|
43
|
-
conn.errback { @responses[:failed].push(conn); check_progress }
|
44
|
-
end
|
45
|
-
|
46
|
-
protected
|
47
|
-
|
48
|
-
# invoke callback if all requests have completed
|
49
|
-
def check_progress
|
50
|
-
succeed(self) if (@responses[:succeeded].size +
|
51
|
-
@responses[:failed].size) == @requests.size
|
52
|
-
end
|
53
|
-
|
54
|
-
end
|
55
|
-
end
|
1
|
+
module EventMachine
|
2
|
+
|
3
|
+
# EventMachine based Multi request client, based on a streaming HTTPRequest class,
|
4
|
+
# which allows you to open multiple parallel connections and return only when all
|
5
|
+
# of them finish. (i.e. ideal for parallelizing workloads)
|
6
|
+
#
|
7
|
+
# == Example
|
8
|
+
#
|
9
|
+
# EventMachine.run {
|
10
|
+
#
|
11
|
+
# multi = EventMachine::MultiRequest.new
|
12
|
+
#
|
13
|
+
# # add multiple requests to the multi-handler
|
14
|
+
# multi.add(EventMachine::HttpRequest.new('http://www.google.com/').get)
|
15
|
+
# multi.add(EventMachine::HttpRequest.new('http://www.yahoo.com/').get)
|
16
|
+
#
|
17
|
+
# multi.callback {
|
18
|
+
# p multi.responses[:succeeded]
|
19
|
+
# p multi.responses[:failed]
|
20
|
+
#
|
21
|
+
# EventMachine.stop
|
22
|
+
# }
|
23
|
+
# }
|
24
|
+
#
|
25
|
+
|
26
|
+
class MultiRequest
|
27
|
+
include EventMachine::Deferrable
|
28
|
+
|
29
|
+
attr_reader :requests, :responses
|
30
|
+
|
31
|
+
def initialize(conns=[], &block)
|
32
|
+
@requests = []
|
33
|
+
@responses = {:succeeded => [], :failed => []}
|
34
|
+
|
35
|
+
conns.each {|conn| add(conn)}
|
36
|
+
callback(&block) if block_given?
|
37
|
+
end
|
38
|
+
|
39
|
+
def add(conn)
|
40
|
+
@requests.push(conn)
|
41
|
+
|
42
|
+
conn.callback { @responses[:succeeded].push(conn); check_progress }
|
43
|
+
conn.errback { @responses[:failed].push(conn); check_progress }
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
# invoke callback if all requests have completed
|
49
|
+
def check_progress
|
50
|
+
succeed(self) if (@responses[:succeeded].size +
|
51
|
+
@responses[:failed].size) == @requests.size
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|