cool.io 1.2.4 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,173 +0,0 @@
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
- }
@@ -1,234 +0,0 @@
1
- #--
2
- # Copyright (C)2007-10 Tony Arcieri, Roger Pack
3
- # You can redistribute this under the terms of the Ruby license
4
- # See file LICENSE for details
5
- #++
6
-
7
- require 'cool.io'
8
-
9
- # EventMachine emulation for Cool.io:
10
- #
11
- # require 'coolio/eventmachine'
12
- #
13
- # Drawbacks: slightly slower than EM.
14
- # Benefits: timers are more accurate using libev than using EM
15
- # TODO: some things like connection timeouts aren't implemented yet
16
- # DONE: timers and normal socket functions are implemented.
17
- module EventMachine
18
- class << self
19
- # Start the Reactor loop
20
- def run
21
- yield if block_given?
22
- Coolio::Loop.default.run
23
- end
24
-
25
- # Stop the Reactor loop
26
- def stop_event_loop
27
- Coolio::Loop.default.stop
28
- end
29
-
30
- class OneShotEMTimer < Coolio::TimerWatcher
31
- def setup(proc)
32
- @proc = proc
33
- end
34
-
35
- def on_timer
36
- @proc.call
37
- end
38
- end
39
-
40
- # ltodo: use Coolio's PeriodicTimer to wrap EM's two similar to it
41
- # todo: close all connections on 'stop', I believe
42
-
43
- def add_timer(interval, proc = nil, &block)
44
- block ||= proc
45
- t = OneShotEMTimer.new(interval, false) # non repeating
46
- t.setup(block)
47
-
48
- # fire 'er off ltodo: do we keep track of these timers in memory?
49
- t.attach(Coolio::Loop.default)
50
- t
51
- end
52
-
53
- def cancel_timer(t)
54
- # guess there's a case where EM you can say 'cancel' but it's already fired?
55
- # kind of odd but it happens
56
- t.detach if t.attached?
57
- end
58
-
59
- def set_comm_inactivity_timeout(*args); end # TODO
60
-
61
- # Make an outgoing connection
62
- def connect(addr, port, handler = Connection, *args, &block)
63
- block = args.pop if Proc === args[-1]
64
-
65
- # make sure we're a 'real' class here
66
- klass = if (handler and handler.is_a?(Class))
67
- handler
68
- else
69
- Class.new( Connection ) {handler and include handler}
70
- end
71
-
72
- wrapped_child = CallsBackToEM.connect(addr, port, *args) # ltodo: args? what? they're used? also TODOC TODO FIX
73
- conn = klass.new(wrapped_child) # ltodo [?] addr, port, *args)
74
- wrapped_child.attach(Coolio::Loop.default) # necessary
75
- conn.heres_your_socket(wrapped_child)
76
- wrapped_child.call_back_to_this(conn) # calls post_init for us
77
- yield conn if block_given?
78
- end
79
-
80
- # Start a TCP server on the given address and port
81
- def start_server(addr, port, handler = Connection, *args, &block)
82
- # make sure we're a 'real' class here
83
- klass = if (handler and handler.is_a?(Class))
84
- handler
85
- else
86
- Class.new( Connection ) {handler and include handler}
87
- end
88
-
89
- server = Coolio::TCPServer.new(addr, port, CallsBackToEM, *args) do |wrapped_child|
90
- conn = klass.new(wrapped_child)
91
- conn.heres_your_socket(wrapped_child) # ideally NOT have this :)
92
- wrapped_child.call_back_to_this(conn)
93
- block.call(conn) if block
94
- end
95
-
96
- server.attach(Coolio::Loop.default)
97
- end
98
-
99
- def stop_server(server)
100
- server.close
101
- end
102
-
103
- # Set the maximum number of descriptors available to this process
104
- def set_descriptor_table_size(nfds)
105
- Coolio::Utils.maxfds = nfds
106
- end
107
-
108
- # Compatibility noop. Handled automatically by libev
109
- def epoll; end
110
-
111
- # Compatibility noop. Handled automatically by libev
112
- def kqueue; end
113
- end
114
-
115
- class CallsBackToEM < Coolio::TCPSocket
116
- class ConnectTimer < Coolio::TimerWatcher
117
- attr_accessor :parent
118
- def on_timer
119
- @parent.connection_has_timed_out
120
- end
121
- end
122
-
123
- def call_back_to_this parent
124
- @call_back_to_this = parent
125
- parent.post_init
126
- end
127
-
128
- def on_connect
129
- # @connection_timer.detach if @connection_timer
130
- # won't need that anymore :) -- with server connecteds we don't have it, anyway
131
-
132
- # TODO should server accepted's call this? They don't currently
133
- # [and can't, since on_connect gets called basically in the initializer--needs some code love for that to happen :)
134
- @call_back_to_this.connection_completed if @call_back_to_this
135
- end
136
-
137
- def connection_has_timed_out
138
- return if closed?
139
-
140
- # wonder if this works when you're within a half-connected phase.
141
- # I think it does. What about TCP state?
142
- close unless closed?
143
- @call_back_to_this.unbind
144
- end
145
-
146
- def on_write_complete
147
- close if @should_close_after_writing
148
- end
149
-
150
- def should_close_after_writing
151
- @should_close_after_writing = true;
152
- end
153
-
154
- def on_close
155
- @call_back_to_this.unbind # about the same ltodo check if they ARE the same here
156
- end
157
-
158
- def on_resolve_failed
159
- fail
160
- end
161
-
162
- def on_connect_failed
163
- fail
164
- end
165
-
166
- def on_read(data)
167
- @call_back_to_this.receive_data data
168
- end
169
-
170
- def fail
171
- #@connection_timer.detch if @connection_timer
172
- @call_back_to_this.unbind
173
- end
174
-
175
- def self.connect(*args)
176
- a = super *args
177
- # the connect timer currently kills TCPServer classes. I'm not sure why.
178
- #@connection_timer = ConnectTimer.new(14) # needs to be at least higher than 12 :)
179
- #@connection_timer.parent = a
180
- #@connection_timer.attach(Coolio::Loop.default)
181
- a
182
- end
183
- end
184
-
185
- class Connection
186
- def self.new(*args)
187
- allocate#.instance_eval do
188
- # initialize *args
189
- #end
190
- end
191
-
192
- # we will need to call 'their functions' appropriately -- the commented out ones, here
193
- #
194
- # Callback fired when connection is created
195
- def post_init
196
- # I thought we were 'overriding' EM's existing methods, here.
197
- # Huh? Why do we have to define these then?
198
- end
199
-
200
- # Callback fired when connection is closed
201
- def unbind; end
202
-
203
- # Callback fired when data is received
204
- # def receive_data(data); end
205
- def heres_your_socket(instantiated_coolio_socket)
206
- instantiated_coolio_socket.call_back_to_this self
207
- @wrapped_coolio = instantiated_coolio_socket
208
- end
209
-
210
- # Send data to the current connection -- called by them
211
- def send_data(data)
212
- @wrapped_coolio.write data
213
- end
214
-
215
- # Close the connection, optionally after writing
216
- def close_connection(after_writing = false)
217
- return close_connection_after_writing if after_writing
218
- @wrapped_coolio.close
219
- end
220
-
221
- # Close the connection after all data has been written
222
- def close_connection_after_writing
223
- @wrapped_coolio.output_buffer_size.zero? ? @wrapped_coolio.close : @wrapped_coolio.should_close_after_writing
224
- end
225
-
226
- def get_peername
227
- family, port, host_name, host_ip = @wrapped_coolio.peeraddr
228
- Socket.pack_sockaddr_in(port, host_ip) # pack it up :)
229
- end
230
- end
231
- end
232
-
233
- # Shortcut constant
234
- EM = EventMachine
@@ -1,430 +0,0 @@
1
- #--
2
- # Copyright (C)2007-10 Tony Arcieri
3
- # Includes portions originally Copyright (C)2005 Zed Shaw
4
- # You can redistribute this under the terms of the Ruby license
5
- # See file LICENSE for details
6
- #++
7
-
8
- require "cool.io/custom_require"
9
- cool_require 'http11_client'
10
-
11
- module Coolio
12
- # A simple hash is returned for each request made by HttpClient with
13
- # the 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
- # HTTP response status as an integer
25
- def status
26
- Integer(http_status) rescue nil
27
- end
28
-
29
- # Length of content as an integer, or nil if chunked/unspecified
30
- def content_length
31
- Integer(self[HttpClient::CONTENT_LENGTH]) rescue nil
32
- end
33
-
34
- # Is the transfer encoding chunked?
35
- def chunked_encoding?
36
- /chunked/i === self[HttpClient::TRANSFER_ENCODING]
37
- end
38
- end
39
-
40
- class HttpChunkHeader < Hash
41
- # When parsing chunked encodings this is set
42
- attr_accessor :http_chunk_size
43
-
44
- # Size of the chunk as an integer
45
- def chunk_size
46
- return @chunk_size unless @chunk_size.nil?
47
- @chunk_size = @http_chunk_size ? @http_chunk_size.to_i(base=16) : 0
48
- end
49
- end
50
-
51
- # Methods for building HTTP requests
52
- module HttpEncoding
53
- HTTP_REQUEST_HEADER="%s %s HTTP/1.1\r\n"
54
- FIELD_ENCODING = "%s: %s\r\n"
55
-
56
- # Escapes a URI.
57
- def escape(s)
58
- s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
59
- '%'+$1.unpack('H2'*$1.size).join('%').upcase
60
- }.tr(' ', '+')
61
- end
62
-
63
- # Unescapes a URI escaped string.
64
- def unescape(s)
65
- s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
66
- [$1.delete('%')].pack('H*')
67
- }
68
- end
69
-
70
- # Map all header keys to a downcased string version
71
- def munge_header_keys(head)
72
- head.inject({}) { |h, (k, v)| h[k.to_s.downcase] = v; h }
73
- end
74
-
75
- # HTTP is kind of retarded that you have to specify
76
- # a Host header, but if you include port 80 then further
77
- # redirects will tack on the :80 which is annoying.
78
- def encode_host
79
- remote_host + (remote_port.to_i != 80 ? ":#{remote_port}" : "")
80
- end
81
-
82
- def encode_request(method, path, query)
83
- HTTP_REQUEST_HEADER % [method.to_s.upcase, encode_query(path, query)]
84
- end
85
-
86
- def encode_query(path, query)
87
- return path unless query
88
- path + "?" + query.map { |k, v| encode_param(k, v) }.join('&')
89
- end
90
-
91
- # URL encodes a single k=v parameter.
92
- def encode_param(k, v)
93
- escape(k) + "=" + escape(v)
94
- end
95
-
96
- # Encode a field in an HTTP header
97
- def encode_field(k, v)
98
- FIELD_ENCODING % [k, v]
99
- end
100
-
101
- def encode_headers(head)
102
- head.inject('') do |result, (key, value)|
103
- # Munge keys from foo-bar-baz to Foo-Bar-Baz
104
- key = key.split('-').map { |k| k.capitalize }.join('-')
105
- result << encode_field(key, value)
106
- end
107
- end
108
-
109
- def encode_cookies(cookies)
110
- cookies.inject('') { |result, (k, v)| result << encode_field('Cookie', encode_param(k, v)) }
111
- end
112
- end
113
-
114
- # HttpClient is tested on only CRuby. AFAIK, there is no HttpClient users.
115
- # So HttpClient will not be maintained in the future.
116
-
117
- # HTTP client class implemented as a subclass of Coolio::TCPSocket. Encodes
118
- # requests and allows streaming consumption of the response. Response is
119
- # parsed with a Ragel-generated whitelist parser which supports chunked
120
- # HTTP encoding.
121
- #
122
- # == Example
123
- #
124
- # loop = Coolio::Loop.default
125
- # client = Coolio::HttpClient.connect("www.google.com").attach(loop)
126
- # client.request('GET', '/search', query: {q: 'foobar'})
127
- # loop.run
128
- #
129
- class HttpClient < TCPSocket
130
- include HttpEncoding
131
-
132
- ALLOWED_METHODS=[:put, :get, :post, :delete, :head]
133
- TRANSFER_ENCODING="TRANSFER_ENCODING"
134
- CONTENT_LENGTH="CONTENT_LENGTH"
135
- SET_COOKIE="SET_COOKIE"
136
- LOCATION="LOCATION"
137
- HOST="HOST"
138
- CRLF="\r\n"
139
-
140
- # Connect to the given server, with port 80 as the default
141
- def self.connect(addr, port = 80, *args)
142
- super
143
- end
144
-
145
- def initialize(socket)
146
- super
147
-
148
- @parser = HttpClientParser.new
149
- @parser_nbytes = 0
150
-
151
- @state = :response_header
152
- @data = ::IO::Buffer.new
153
-
154
- @response_header = HttpResponseHeader.new
155
- @chunk_header = HttpChunkHeader.new
156
- end
157
-
158
- # Send an HTTP request and consume the response.
159
- # Supports the following options:
160
- #
161
- # head: {Key: Value}
162
- # Specify an HTTP header, e.g. {'Connection': 'close'}
163
- #
164
- # query: {Key: Value}
165
- # Specify query string parameters (auto-escaped)
166
- #
167
- # cookies: {Key: Value}
168
- # Specify hash of cookies (auto-escaped)
169
- #
170
- # body: String
171
- # Specify the request body (you must encode it for now)
172
- #
173
- def request(method, path, options = {})
174
- raise ArgumentError, "invalid request path" unless /^\// === path
175
- raise RuntimeError, "request already sent" if @requested
176
-
177
- @method, @path, @options = method, path, options
178
- @requested = true
179
-
180
- return unless @connected
181
- send_request
182
- end
183
-
184
- # Enable the HttpClient if it has been disabled
185
- def enable
186
- super
187
- dispatch unless @data.empty?
188
- end
189
-
190
- # Called when response header has been received
191
- def on_response_header(response_header)
192
- end
193
-
194
- # Called when part of the body has been read
195
- def on_body_data(data)
196
- STDOUT.write data
197
- STDOUT.flush
198
- end
199
-
200
- # Called when the request has completed
201
- def on_request_complete
202
- @state == :finished ? close : @state = :finished
203
- end
204
-
205
- # called by close
206
- def on_close
207
- if @state != :finished and @state == :body
208
- on_request_complete
209
- end
210
- end
211
-
212
- # Called when an error occurs dispatching the request
213
- def on_error(reason)
214
- close
215
- raise RuntimeError, reason
216
- end
217
-
218
- #########
219
- protected
220
- #########
221
-
222
- #
223
- # Coolio callbacks
224
- #
225
-
226
- def on_connect
227
- @connected = true
228
- send_request if @method and @path
229
- end
230
-
231
- def on_read(data)
232
- @data << data
233
- dispatch
234
- end
235
-
236
- #
237
- # Request sending
238
- #
239
-
240
- def send_request
241
- send_request_header
242
- send_request_body
243
- end
244
-
245
- def send_request_header
246
- query = @options[:query]
247
- head = @options[:head] ? munge_header_keys(@options[:head]) : {}
248
- cookies = @options[:cookies]
249
- body = @options[:body]
250
-
251
- # Set the Host header if it hasn't been specified already
252
- head['host'] ||= encode_host
253
-
254
- # Set the Content-Length if it hasn't been specified already and a body was given
255
- head['content-length'] ||= body ? body.length : 0
256
-
257
- # Set the User-Agent if it hasn't been specified
258
- head['user-agent'] ||= "Coolio #{Coolio::VERSION}"
259
-
260
- # Default to Connection: close
261
- head['connection'] ||= 'close'
262
-
263
- # Build the request
264
- request_header = encode_request(@method, @path, query)
265
- request_header << encode_headers(head)
266
- request_header << encode_cookies(cookies) if cookies
267
- request_header << CRLF
268
-
269
- write request_header
270
- end
271
-
272
- def send_request_body
273
- write @options[:body] if @options[:body]
274
- end
275
-
276
- #
277
- # Response processing
278
- #
279
-
280
- def dispatch
281
- while enabled? and case @state
282
- when :response_header
283
- parse_response_header
284
- when :chunk_header
285
- parse_chunk_header
286
- when :chunk_body
287
- process_chunk_body
288
- when :chunk_footer
289
- process_chunk_footer
290
- when :response_footer
291
- process_response_footer
292
- when :body
293
- process_body
294
- when :finished, :invalid
295
- break
296
- else raise RuntimeError, "invalid state: #{@state}"
297
- end
298
- end
299
- end
300
-
301
- def parse_header(header)
302
- return false if @data.empty?
303
-
304
- begin
305
- @parser_nbytes = @parser.execute(header, @data.to_str, @parser_nbytes)
306
- rescue Coolio::HttpClientParserError
307
- on_error "invalid HTTP format, parsing fails"
308
- @state = :invalid
309
- end
310
-
311
- return false unless @parser.finished?
312
-
313
- # Clear parsed data from the buffer
314
- @data.read(@parser_nbytes)
315
- @parser.reset
316
- @parser_nbytes = 0
317
-
318
- true
319
- end
320
-
321
- def parse_response_header
322
- return false unless parse_header(@response_header)
323
-
324
- unless @response_header.http_status and @response_header.http_reason
325
- on_error "no HTTP response"
326
- @state = :invalid
327
- return false
328
- end
329
-
330
- on_response_header(@response_header)
331
-
332
- if @response_header.chunked_encoding?
333
- @state = :chunk_header
334
- else
335
- @state = :body
336
- @bytes_remaining = @response_header.content_length
337
- end
338
-
339
- true
340
- end
341
-
342
- def parse_chunk_header
343
- return false unless parse_header(@chunk_header)
344
-
345
- @bytes_remaining = @chunk_header.chunk_size
346
- @chunk_header = HttpChunkHeader.new
347
-
348
- @state = @bytes_remaining > 0 ? :chunk_body : :response_footer
349
- true
350
- end
351
-
352
- def process_chunk_body
353
- if @data.size < @bytes_remaining
354
- @bytes_remaining -= @data.size
355
- on_body_data @data.read
356
- return false
357
- end
358
-
359
- on_body_data @data.read(@bytes_remaining)
360
- @bytes_remaining = 0
361
-
362
- @state = :chunk_footer
363
- true
364
- end
365
-
366
- def process_chunk_footer
367
- return false if @data.size < 2
368
-
369
- if @data.read(2) == CRLF
370
- @state = :chunk_header
371
- else
372
- on_error "non-CRLF chunk footer"
373
- @state = :invalid
374
- end
375
-
376
- true
377
- end
378
-
379
- def process_response_footer
380
- return false if @data.size < 2
381
-
382
- if @data.read(2) == CRLF
383
- if @data.empty?
384
- on_request_complete
385
- @state = :finished
386
- else
387
- on_error "garbage at end of chunked response"
388
- @state = :invalid
389
- end
390
- else
391
- on_error "non-CRLF response footer"
392
- @state = :invalid
393
- end
394
-
395
- false
396
- end
397
-
398
- def process_body
399
- if @bytes_remaining.nil?
400
- on_body_data @data.read
401
- return false
402
- end
403
-
404
- if @bytes_remaining.zero?
405
- on_request_complete
406
- @state = :finished
407
- return false
408
- end
409
-
410
- if @data.size < @bytes_remaining
411
- @bytes_remaining -= @data.size
412
- on_body_data @data.read
413
- return false
414
- end
415
-
416
- on_body_data @data.read(@bytes_remaining)
417
- @bytes_remaining = 0
418
-
419
- if @data.empty?
420
- on_request_complete
421
- @state = :finished
422
- else
423
- on_error "garbage at end of body"
424
- @state = :invalid
425
- end
426
-
427
- false
428
- end
429
- end
430
- end