dwaite-em-http-request 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
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
+ #ifndef http11_parser_h
7
+ #define http11_parser_h
8
+
9
+ #include <sys/types.h>
10
+
11
+ #if defined(_WIN32)
12
+ #include <stddef.h>
13
+ #endif
14
+
15
+ typedef void (*element_cb)(void *data, const char *at, size_t length);
16
+ typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen);
17
+
18
+ typedef struct httpclient_parser {
19
+ int cs;
20
+ size_t body_start;
21
+ int content_len;
22
+ size_t nread;
23
+ size_t mark;
24
+ size_t field_start;
25
+ size_t field_len;
26
+
27
+ void *data;
28
+
29
+ field_cb http_field;
30
+ element_cb reason_phrase;
31
+ element_cb status_code;
32
+ element_cb chunk_size;
33
+ element_cb http_version;
34
+ element_cb header_done;
35
+ element_cb last_chunk;
36
+
37
+
38
+ } httpclient_parser;
39
+
40
+ int httpclient_parser_init(httpclient_parser *parser);
41
+ int httpclient_parser_finish(httpclient_parser *parser);
42
+ size_t httpclient_parser_execute(httpclient_parser *parser, const char *data, size_t len, size_t off);
43
+ int httpclient_parser_has_error(httpclient_parser *parser);
44
+ int httpclient_parser_is_finished(httpclient_parser *parser);
45
+
46
+ #define httpclient_parser_nread(parser) (parser)->nread
47
+
48
+ #endif
@@ -0,0 +1,173 @@
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";
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
+ %%write eof;
144
+ parser->nread++;
145
+ }
146
+
147
+ return(parser->nread);
148
+ }
149
+
150
+ int httpclient_parser_finish(httpclient_parser *parser)
151
+ {
152
+ int cs = parser->cs;
153
+
154
+ %%write eof;
155
+
156
+ parser->cs = cs;
157
+
158
+ if (httpclient_parser_has_error(parser) ) {
159
+ return -1;
160
+ } else if (httpclient_parser_is_finished(parser) ) {
161
+ return 1;
162
+ } else {
163
+ return 0;
164
+ }
165
+ }
166
+
167
+ int httpclient_parser_has_error(httpclient_parser *parser) {
168
+ return parser->cs == httpclient_parser_error;
169
+ }
170
+
171
+ int httpclient_parser_is_finished(httpclient_parser *parser) {
172
+ return parser->cs == httpclient_parser_first_final;
173
+ }
data/lib/em-http.rb ADDED
@@ -0,0 +1,18 @@
1
+ #--
2
+ # Copyright (C)2008 Ilya Grigorik
3
+ # You can redistribute this under the terms of the Ruby license
4
+ # See file LICENSE for details
5
+ #++
6
+
7
+ require 'rubygems'
8
+ require 'eventmachine'
9
+
10
+
11
+ require File.dirname(__FILE__) + '/http11_client'
12
+ require File.dirname(__FILE__) + '/em_buffer'
13
+
14
+ require File.dirname(__FILE__) + '/em-http/core_ext/hash'
15
+ require File.dirname(__FILE__) + '/em-http/client'
16
+ require File.dirname(__FILE__) + '/em-http/multi'
17
+ require File.dirname(__FILE__) + '/em-http/request'
18
+ require File.dirname(__FILE__) + '/em-http/decoders'
@@ -0,0 +1,547 @@
1
+ # #--
2
+ # Copyright (C)2008 Ilya Grigorik
3
+ #
4
+ # Includes portion originally Copyright (C)2007 Tony Arcieri
5
+ # Includes portion originally Copyright (C)2005 Zed Shaw
6
+ # You can redistribute this under the terms of the Ruby
7
+ # license See file LICENSE for details
8
+ # #--
9
+
10
+ module EventMachine
11
+
12
+ # A simple hash is returned for each request made by HttpClient with the
13
+ # headers that were given by the server for that request.
14
+ class HttpResponseHeader < Hash
15
+ # The reason returned in the http response ("OK","File not found",etc.)
16
+ attr_accessor :http_reason
17
+
18
+ # The HTTP version returned.
19
+ attr_accessor :http_version
20
+
21
+ # The status code (as a string!)
22
+ attr_accessor :http_status
23
+
24
+ # E-Tag
25
+ def etag
26
+ self["ETag"]
27
+ end
28
+
29
+ def last_modified
30
+ time = self["Last-Modified"]
31
+ Time.parse(time) if time
32
+ end
33
+
34
+ # HTTP response status as an integer
35
+ def status
36
+ Integer(http_status) rescue nil
37
+ end
38
+
39
+ # Length of content as an integer, or nil if chunked/unspecified
40
+ def content_length
41
+ Integer(self[HttpClient::CONTENT_LENGTH]) rescue nil
42
+ end
43
+
44
+ # Cookie header from the server
45
+ def cookie
46
+ self[HttpClient::SET_COOKIE]
47
+ end
48
+
49
+ # Is the transfer encoding chunked?
50
+ def chunked_encoding?
51
+ /chunked/i === self[HttpClient::TRANSFER_ENCODING]
52
+ end
53
+
54
+ def keep_alive?
55
+ /keep-alive/i === self[HttpClient::KEEP_ALIVE]
56
+ end
57
+
58
+ def compressed?
59
+ /gzip|compressed|deflate/i === self[HttpClient::CONTENT_ENCODING]
60
+ end
61
+
62
+ def location
63
+ self[HttpClient::LOCATION]
64
+ end
65
+ end
66
+
67
+ class HttpChunkHeader < Hash
68
+ # When parsing chunked encodings this is set
69
+ attr_accessor :http_chunk_size
70
+
71
+ # Size of the chunk as an integer
72
+ def chunk_size
73
+ return @chunk_size unless @chunk_size.nil?
74
+ @chunk_size = @http_chunk_size ? @http_chunk_size.to_i(base=16) : 0
75
+ end
76
+ end
77
+
78
+ # Methods for building HTTP requests
79
+ module HttpEncoding
80
+ HTTP_REQUEST_HEADER="%s %s HTTP/1.1\r\n"
81
+ FIELD_ENCODING = "%s: %s\r\n"
82
+ BASIC_AUTH_ENCODING = "%s: Basic %s\r\n"
83
+
84
+ # Escapes a URI.
85
+ def escape(s)
86
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
87
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
88
+ }.tr(' ', '+')
89
+ end
90
+
91
+ # Unescapes a URI escaped string.
92
+ def unescape(s)
93
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
94
+ [$1.delete('%')].pack('H*')
95
+ }
96
+ end
97
+
98
+ # Map all header keys to a downcased string version
99
+ def munge_header_keys(head)
100
+ head.inject({}) { |h, (k, v)| h[k.to_s.downcase] = v; h }
101
+ end
102
+
103
+ # HTTP is kind of retarded that you have to specify a Host header, but if
104
+ # you include port 80 then further redirects will tack on the :80 which is
105
+ # annoying.
106
+ def encode_host
107
+ @uri.host + (@uri.port != @uri.default_port ? ":#{@uri.port}" : "")
108
+ end
109
+
110
+ def encode_request(method, path, query)
111
+ HTTP_REQUEST_HEADER % [method.to_s.upcase, encode_query(path, query)]
112
+ end
113
+
114
+ def encode_query(path, query)
115
+ encoded_query = if query.kind_of?(Hash)
116
+ query.map { |k, v| encode_param(k, v) }.join('&')
117
+ else
118
+ query.to_s
119
+ end
120
+ if !@uri.query.to_s.empty?
121
+ encoded_query = [encoded_query, @uri.query].reject {|part| part.empty?}.join("&")
122
+ end
123
+ return path if encoded_query.to_s.empty?
124
+ "#{path}?#{encoded_query}"
125
+ end
126
+
127
+ # URL encodes query parameters:
128
+ # single k=v, or a URL encoded array, if v is an array of values
129
+ def encode_param(k, v)
130
+ if v.is_a?(Array)
131
+ v.map { |e| escape(k) + "[]=" + escape(e) }.join("&")
132
+ else
133
+ escape(k) + "=" + escape(v)
134
+ end
135
+ end
136
+
137
+ # Encode a field in an HTTP header
138
+ def encode_field(k, v)
139
+ FIELD_ENCODING % [k, v]
140
+ end
141
+
142
+ # Encode basic auth in an HTTP header
143
+ def encode_basic_auth(k,v)
144
+ BASIC_AUTH_ENCODING % [k, Base64.encode64(v.join(":")).chomp]
145
+ end
146
+
147
+ def encode_headers(head)
148
+ head.inject('') do |result, (key, value)|
149
+ # Munge keys from foo-bar-baz to Foo-Bar-Baz
150
+ key = key.split('-').map { |k| k.capitalize }.join('-')
151
+ unless key == "Authorization"
152
+ result << encode_field(key, value)
153
+ else
154
+ result << encode_basic_auth(key, value)
155
+ end
156
+ end
157
+ end
158
+
159
+ def encode_cookie(cookie)
160
+ if cookie.is_a? Hash
161
+ cookie.inject('') { |result, (k, v)| result << encode_param(k, v) + ";" }
162
+ else
163
+ cookie
164
+ end
165
+ end
166
+ def encode_body body
167
+ params = ''
168
+ stack = []
169
+ body.each do |k, v|
170
+ if v.is_a? Hash
171
+ stack << [k, v]
172
+ elsif v.is_a? Array
173
+ stack << [k, Hash.from_array v]
174
+ else
175
+ params << encode_param(k, v) << '&'
176
+ end
177
+ end
178
+ stack.each do |parent, hash|
179
+ hash.each do |k, v|
180
+ if v.is_a? Hash
181
+ stack << ["#{parent}[#{k}]", v]
182
+ else
183
+ params << encode_param("#{parent}[#{k}]", v) << '&'
184
+ end
185
+ end
186
+ end
187
+ params.chop! # trailing &
188
+ params
189
+ end
190
+ end
191
+
192
+ class HttpClient < Connection
193
+ include EventMachine::Deferrable
194
+ include HttpEncoding
195
+
196
+ TRANSFER_ENCODING="TRANSFER_ENCODING"
197
+ CONTENT_ENCODING="CONTENT_ENCODING"
198
+ CONTENT_LENGTH="CONTENT_LENGTH"
199
+ KEEP_ALIVE="CONNECTION"
200
+ SET_COOKIE="SET_COOKIE"
201
+ LOCATION="LOCATION"
202
+ HOST="HOST"
203
+ CRLF="\r\n"
204
+
205
+ attr_accessor :method, :options, :uri
206
+ attr_reader :response, :response_header, :errors
207
+
208
+ def post_init
209
+ @parser = HttpClientParser.new
210
+ @data = EventMachine::Buffer.new
211
+ @response_header = HttpResponseHeader.new
212
+ @chunk_header = HttpChunkHeader.new
213
+
214
+ @state = :response_header
215
+ @parser_nbytes = 0
216
+ @response = ''
217
+ @inflate = []
218
+ @errors = ''
219
+ @content_decoder = nil
220
+ @stream = nil
221
+ end
222
+
223
+ # start HTTP request once we establish connection to host
224
+ def connection_completed
225
+ ssl = @options[:tls] || @options[:ssl] || {}
226
+ start_tls(ssl) if @uri.scheme == "https" or @uri.port == 443
227
+
228
+ send_request_header
229
+ send_request_body
230
+ end
231
+
232
+ # request is done, invoke the callback
233
+ def on_request_complete
234
+ begin
235
+ @content_decoder.finalize! if @content_decoder
236
+ rescue HttpDecoders::DecoderError
237
+ on_error "Content-decoder error"
238
+ end
239
+ unbind
240
+ end
241
+
242
+ # request failed, invoke errback
243
+ def on_error(msg, dns_error = false)
244
+ @errors = msg
245
+
246
+ # no connection signature on DNS failures
247
+ # fail the connection directly
248
+ dns_error == true ? fail : unbind
249
+ end
250
+
251
+ # assign a stream processing block
252
+ def stream(&blk)
253
+ @stream = blk
254
+ end
255
+
256
+ def normalize_body
257
+ if @options[:body].is_a? Hash
258
+ encode_body @options[:body]
259
+ else
260
+ @options[:body]
261
+ end
262
+ end
263
+
264
+ def send_request_header
265
+ query = @options[:query]
266
+ head = @options[:head] ? munge_header_keys(@options[:head]) : {}
267
+ @options[:head] = head
268
+ body = normalize_body
269
+
270
+ # Set the Host header if it hasn't been specified already
271
+ head['host'] ||= encode_host
272
+
273
+ # Set the Content-Length if body is given
274
+ head['content-length'] = body.length if body
275
+
276
+ # Set the User-Agent if it hasn't been specified
277
+ head['user-agent'] ||= "EventMachine HttpClient"
278
+
279
+ # Set auto-inflate flags
280
+ if head['accept-encoding']
281
+ @inflate = head['accept-encoding'].split(',').map {|t| t.strip}
282
+ end
283
+
284
+ # Set the cookie header if provided
285
+ if cookie = head.delete('cookie')
286
+ head['cookie'] = encode_cookie(cookie)
287
+ end
288
+
289
+ # Build the request
290
+ request_header = encode_request(@method, @uri.path, query)
291
+ request_header << encode_headers(head)
292
+ request_header << CRLF
293
+
294
+ send_data request_header
295
+ if @options[:debug]
296
+ puts request_header
297
+ end
298
+ end
299
+
300
+ def send_request_body
301
+ return unless @options[:body]
302
+ body = normalize_body
303
+ send_data body
304
+ if @options[:debug]
305
+ puts body
306
+ end
307
+ end
308
+
309
+ def receive_data(data)
310
+ @data << data
311
+ dispatch
312
+ end
313
+
314
+ # Called when part of the body has been read
315
+ def on_body_data(data)
316
+ if @content_decoder
317
+ begin
318
+ @content_decoder << data
319
+ rescue HttpDecoders::DecoderError
320
+ on_error "Content-decoder error"
321
+ end
322
+ else
323
+ on_decoded_body_data(data)
324
+ end
325
+ end
326
+
327
+ def on_decoded_body_data(data)
328
+ if @stream
329
+ @stream.call(data)
330
+ else
331
+ @response << data
332
+ end
333
+ end
334
+
335
+ def unbind
336
+ if @state == :finished
337
+ succeed(self)
338
+ else
339
+ fail(self)
340
+ end
341
+ close_connection
342
+ end
343
+
344
+ #
345
+ # Response processing
346
+ #
347
+
348
+ def dispatch
349
+ while case @state
350
+ when :response_header
351
+ parse_response_header
352
+ when :chunk_header
353
+ parse_chunk_header
354
+ when :chunk_body
355
+ process_chunk_body
356
+ when :chunk_footer
357
+ process_chunk_footer
358
+ when :response_footer
359
+ process_response_footer
360
+ when :body
361
+ process_body
362
+ when :finished, :invalid
363
+ break
364
+ else raise RuntimeError, "invalid state: #{@state}"
365
+ end
366
+ end
367
+ end
368
+
369
+ def parse_header(header)
370
+ return false if @data.empty?
371
+
372
+ begin
373
+ @parser_nbytes = @parser.execute(header, @data.to_str, @parser_nbytes)
374
+ rescue EventMachine::HttpClientParserError
375
+ @state = :invalid
376
+ on_error "invalid HTTP format, parsing fails"
377
+ end
378
+
379
+ return false unless @parser.finished?
380
+
381
+ # Clear parsed data from the buffer
382
+ @data.read(@parser_nbytes)
383
+ @parser.reset
384
+ @parser_nbytes = 0
385
+
386
+ true
387
+ end
388
+
389
+ def parse_response_header
390
+ return false unless parse_header(@response_header)
391
+
392
+ unless @response_header.http_status and @response_header.http_reason
393
+ @state = :invalid
394
+ on_error "no HTTP response"
395
+ return false
396
+ end
397
+
398
+ if @options[:debug]
399
+ puts "Response headers: #{@response_header.inspect}"
400
+ end
401
+
402
+ # correct location header - some servers will incorrectly give a relative URI
403
+ if @response_header.location
404
+ begin
405
+ location = URI.parse @response_header.location
406
+ if location.relative?
407
+ location = (@uri.merge location).to_s
408
+ @response_header[LOCATION] = location
409
+ end
410
+ rescue
411
+ on_error "Location header format error"
412
+ return false
413
+ end
414
+ end
415
+
416
+ # shortcircuit on HEAD requests
417
+ if @method == "HEAD"
418
+ @state = :finished
419
+ on_request_complete
420
+ end
421
+
422
+ if @response_header.chunked_encoding?
423
+ @state = :chunk_header
424
+ else
425
+ if @response_header.content_length > 0
426
+ @state = :body
427
+ @bytes_remaining = @response_header.content_length
428
+ else
429
+ @state = :finished
430
+ on_request_complete
431
+ end
432
+ end
433
+
434
+ if @inflate.include?(response_header[CONTENT_ENCODING]) &&
435
+ decoder_class = HttpDecoders.decoder_for_encoding(response_header[CONTENT_ENCODING])
436
+ begin
437
+ @content_decoder = decoder_class.new do |s| on_decoded_body_data(s) end
438
+ rescue HttpDecoders::DecoderError
439
+ on_error "Content-decoder error"
440
+ end
441
+ end
442
+
443
+ true
444
+ end
445
+
446
+ def parse_chunk_header
447
+ return false unless parse_header(@chunk_header)
448
+
449
+ @bytes_remaining = @chunk_header.chunk_size
450
+ @chunk_header = HttpChunkHeader.new
451
+
452
+ @state = @bytes_remaining > 0 ? :chunk_body : :response_footer
453
+ true
454
+ end
455
+
456
+ def process_chunk_body
457
+ if @data.size < @bytes_remaining
458
+ @bytes_remaining -= @data.size
459
+ on_body_data @data.read
460
+ return false
461
+ end
462
+
463
+ on_body_data @data.read(@bytes_remaining)
464
+ @bytes_remaining = 0
465
+
466
+ @state = :chunk_footer
467
+ true
468
+ end
469
+
470
+ def process_chunk_footer
471
+ return false if @data.size < 2
472
+
473
+ if @data.read(2) == CRLF
474
+ @state = :chunk_header
475
+ else
476
+ @state = :invalid
477
+ on_error "non-CRLF chunk footer"
478
+ end
479
+
480
+ true
481
+ end
482
+
483
+ def process_response_footer
484
+ return false if @data.size < 2
485
+
486
+ if @data.read(2) == CRLF
487
+ if @data.empty?
488
+ @state = :finished
489
+ on_request_complete
490
+ else
491
+ @state = :invalid
492
+ on_error "garbage at end of chunked response"
493
+ end
494
+ else
495
+ @state = :invalid
496
+ on_error "non-CRLF response footer"
497
+ end
498
+
499
+ false
500
+ end
501
+
502
+ def process_body
503
+ if @bytes_remaining.nil?
504
+ on_body_data @data.read
505
+ return false
506
+ end
507
+
508
+ if @bytes_remaining.zero?
509
+ @state = :finished
510
+ on_request_complete
511
+ return false
512
+ end
513
+
514
+ if @data.size < @bytes_remaining
515
+ @bytes_remaining -= @data.size
516
+ on_body_data @data.read
517
+ return false
518
+ end
519
+
520
+ on_body_data @data.read(@bytes_remaining)
521
+ @bytes_remaining = 0
522
+
523
+ # If Keep-Alive is enabled, the server may be pushing more data to us
524
+ # after the first request is complete. Hence, finish first request, and
525
+ # reset state.
526
+ if @response_header.keep_alive?
527
+ @data.clear # hard reset, TODO: add support for keep-alive connections!
528
+ @state = :finished
529
+ on_request_complete
530
+
531
+ else
532
+ if @data.empty?
533
+ @state = :finished
534
+ on_request_complete
535
+ else
536
+ @state = :invalid
537
+ on_error "garbage at end of body"
538
+ end
539
+ end
540
+
541
+ false
542
+ end
543
+
544
+
545
+ end
546
+
547
+ end