cool.io 1.2.4 → 1.3.0

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.
@@ -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