cool.io 1.2.3-x86-mingw32 → 1.4.1-x86-mingw32
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.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.travis.yml +3 -3
- data/CHANGES.md +35 -0
- data/README.md +1 -3
- data/Rakefile +11 -13
- data/cool.io.gemspec +3 -2
- data/examples/callbacked_echo_server.rb +24 -0
- data/ext/cool.io/extconf.rb +8 -24
- data/ext/cool.io/loop.c +1 -1
- data/ext/iobuffer/iobuffer.c +2 -0
- data/ext/libev/Changes +123 -4
- data/ext/libev/LICENSE +2 -1
- data/ext/libev/README +8 -8
- data/ext/libev/ev.c +313 -144
- data/ext/libev/ev.h +18 -10
- data/ext/libev/ev_epoll.c +4 -1
- data/ext/libev/ev_kqueue.c +1 -1
- data/ext/libev/ev_select.c +3 -4
- data/ext/libev/ev_vars.h +3 -2
- data/ext/libev/ev_win32.c +1 -1
- data/ext/libev/win_select.patch +115 -0
- data/lib/cool.io.rb +6 -4
- data/lib/cool.io/dns_resolver.rb +4 -10
- data/lib/cool.io/dsl.rb +6 -2
- data/lib/cool.io/io.rb +36 -16
- data/lib/cool.io/loop.rb +3 -11
- data/lib/cool.io/meta.rb +2 -2
- data/lib/cool.io/version.rb +4 -2
- data/spec/async_watcher_spec.rb +5 -5
- data/spec/dns_spec.rb +11 -7
- data/spec/iobuffer_spec.rb +147 -0
- data/spec/spec_helper.rb +2 -2
- data/spec/stat_watcher_spec.rb +3 -3
- data/spec/tcp_server_spec.rb +98 -5
- data/spec/tcp_socket_spec.rb +185 -0
- data/spec/timer_watcher_spec.rb +23 -19
- data/spec/udp_socket_spec.rb +58 -0
- data/spec/unix_listener_spec.rb +7 -7
- data/spec/unix_server_spec.rb +7 -7
- metadata +83 -103
- data/examples/httpclient.rb +0 -38
- data/ext/http11_client/.gitignore +0 -5
- data/ext/http11_client/LICENSE +0 -31
- data/ext/http11_client/ext_help.h +0 -14
- data/ext/http11_client/extconf.rb +0 -6
- data/ext/http11_client/http11_client.c +0 -300
- data/ext/http11_client/http11_parser.c +0 -403
- data/ext/http11_client/http11_parser.h +0 -48
- data/ext/http11_client/http11_parser.rl +0 -173
- data/lib/cool.io/eventmachine.rb +0 -234
- data/lib/cool.io/http_client.rb +0 -427
data/lib/cool.io/http_client.rb
DELETED
@@ -1,427 +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
|
-
# HTTP client class implemented as a subclass of Coolio::TCPSocket. Encodes
|
115
|
-
# requests and allows streaming consumption of the response. Response is
|
116
|
-
# parsed with a Ragel-generated whitelist parser which supports chunked
|
117
|
-
# HTTP encoding.
|
118
|
-
#
|
119
|
-
# == Example
|
120
|
-
#
|
121
|
-
# loop = Coolio::Loop.default
|
122
|
-
# client = Coolio::HttpClient.connect("www.google.com").attach(loop)
|
123
|
-
# client.request('GET', '/search', query: {q: 'foobar'})
|
124
|
-
# loop.run
|
125
|
-
#
|
126
|
-
class HttpClient < TCPSocket
|
127
|
-
include HttpEncoding
|
128
|
-
|
129
|
-
ALLOWED_METHODS=[:put, :get, :post, :delete, :head]
|
130
|
-
TRANSFER_ENCODING="TRANSFER_ENCODING"
|
131
|
-
CONTENT_LENGTH="CONTENT_LENGTH"
|
132
|
-
SET_COOKIE="SET_COOKIE"
|
133
|
-
LOCATION="LOCATION"
|
134
|
-
HOST="HOST"
|
135
|
-
CRLF="\r\n"
|
136
|
-
|
137
|
-
# Connect to the given server, with port 80 as the default
|
138
|
-
def self.connect(addr, port = 80, *args)
|
139
|
-
super
|
140
|
-
end
|
141
|
-
|
142
|
-
def initialize(socket)
|
143
|
-
super
|
144
|
-
|
145
|
-
@parser = HttpClientParser.new
|
146
|
-
@parser_nbytes = 0
|
147
|
-
|
148
|
-
@state = :response_header
|
149
|
-
@data = ::IO::Buffer.new
|
150
|
-
|
151
|
-
@response_header = HttpResponseHeader.new
|
152
|
-
@chunk_header = HttpChunkHeader.new
|
153
|
-
end
|
154
|
-
|
155
|
-
# Send an HTTP request and consume the response.
|
156
|
-
# Supports the following options:
|
157
|
-
#
|
158
|
-
# head: {Key: Value}
|
159
|
-
# Specify an HTTP header, e.g. {'Connection': 'close'}
|
160
|
-
#
|
161
|
-
# query: {Key: Value}
|
162
|
-
# Specify query string parameters (auto-escaped)
|
163
|
-
#
|
164
|
-
# cookies: {Key: Value}
|
165
|
-
# Specify hash of cookies (auto-escaped)
|
166
|
-
#
|
167
|
-
# body: String
|
168
|
-
# Specify the request body (you must encode it for now)
|
169
|
-
#
|
170
|
-
def request(method, path, options = {})
|
171
|
-
raise ArgumentError, "invalid request path" unless /^\// === path
|
172
|
-
raise RuntimeError, "request already sent" if @requested
|
173
|
-
|
174
|
-
@method, @path, @options = method, path, options
|
175
|
-
@requested = true
|
176
|
-
|
177
|
-
return unless @connected
|
178
|
-
send_request
|
179
|
-
end
|
180
|
-
|
181
|
-
# Enable the HttpClient if it has been disabled
|
182
|
-
def enable
|
183
|
-
super
|
184
|
-
dispatch unless @data.empty?
|
185
|
-
end
|
186
|
-
|
187
|
-
# Called when response header has been received
|
188
|
-
def on_response_header(response_header)
|
189
|
-
end
|
190
|
-
|
191
|
-
# Called when part of the body has been read
|
192
|
-
def on_body_data(data)
|
193
|
-
STDOUT.write data
|
194
|
-
STDOUT.flush
|
195
|
-
end
|
196
|
-
|
197
|
-
# Called when the request has completed
|
198
|
-
def on_request_complete
|
199
|
-
@state == :finished ? close : @state = :finished
|
200
|
-
end
|
201
|
-
|
202
|
-
# called by close
|
203
|
-
def on_close
|
204
|
-
if @state != :finished and @state == :body
|
205
|
-
on_request_complete
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
# Called when an error occurs dispatching the request
|
210
|
-
def on_error(reason)
|
211
|
-
close
|
212
|
-
raise RuntimeError, reason
|
213
|
-
end
|
214
|
-
|
215
|
-
#########
|
216
|
-
protected
|
217
|
-
#########
|
218
|
-
|
219
|
-
#
|
220
|
-
# Coolio callbacks
|
221
|
-
#
|
222
|
-
|
223
|
-
def on_connect
|
224
|
-
@connected = true
|
225
|
-
send_request if @method and @path
|
226
|
-
end
|
227
|
-
|
228
|
-
def on_read(data)
|
229
|
-
@data << data
|
230
|
-
dispatch
|
231
|
-
end
|
232
|
-
|
233
|
-
#
|
234
|
-
# Request sending
|
235
|
-
#
|
236
|
-
|
237
|
-
def send_request
|
238
|
-
send_request_header
|
239
|
-
send_request_body
|
240
|
-
end
|
241
|
-
|
242
|
-
def send_request_header
|
243
|
-
query = @options[:query]
|
244
|
-
head = @options[:head] ? munge_header_keys(@options[:head]) : {}
|
245
|
-
cookies = @options[:cookies]
|
246
|
-
body = @options[:body]
|
247
|
-
|
248
|
-
# Set the Host header if it hasn't been specified already
|
249
|
-
head['host'] ||= encode_host
|
250
|
-
|
251
|
-
# Set the Content-Length if it hasn't been specified already and a body was given
|
252
|
-
head['content-length'] ||= body ? body.length : 0
|
253
|
-
|
254
|
-
# Set the User-Agent if it hasn't been specified
|
255
|
-
head['user-agent'] ||= "Coolio #{Coolio::VERSION}"
|
256
|
-
|
257
|
-
# Default to Connection: close
|
258
|
-
head['connection'] ||= 'close'
|
259
|
-
|
260
|
-
# Build the request
|
261
|
-
request_header = encode_request(@method, @path, query)
|
262
|
-
request_header << encode_headers(head)
|
263
|
-
request_header << encode_cookies(cookies) if cookies
|
264
|
-
request_header << CRLF
|
265
|
-
|
266
|
-
write request_header
|
267
|
-
end
|
268
|
-
|
269
|
-
def send_request_body
|
270
|
-
write @options[:body] if @options[:body]
|
271
|
-
end
|
272
|
-
|
273
|
-
#
|
274
|
-
# Response processing
|
275
|
-
#
|
276
|
-
|
277
|
-
def dispatch
|
278
|
-
while enabled? and case @state
|
279
|
-
when :response_header
|
280
|
-
parse_response_header
|
281
|
-
when :chunk_header
|
282
|
-
parse_chunk_header
|
283
|
-
when :chunk_body
|
284
|
-
process_chunk_body
|
285
|
-
when :chunk_footer
|
286
|
-
process_chunk_footer
|
287
|
-
when :response_footer
|
288
|
-
process_response_footer
|
289
|
-
when :body
|
290
|
-
process_body
|
291
|
-
when :finished, :invalid
|
292
|
-
break
|
293
|
-
else raise RuntimeError, "invalid state: #{@state}"
|
294
|
-
end
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
def parse_header(header)
|
299
|
-
return false if @data.empty?
|
300
|
-
|
301
|
-
begin
|
302
|
-
@parser_nbytes = @parser.execute(header, @data.to_str, @parser_nbytes)
|
303
|
-
rescue Coolio::HttpClientParserError
|
304
|
-
on_error "invalid HTTP format, parsing fails"
|
305
|
-
@state = :invalid
|
306
|
-
end
|
307
|
-
|
308
|
-
return false unless @parser.finished?
|
309
|
-
|
310
|
-
# Clear parsed data from the buffer
|
311
|
-
@data.read(@parser_nbytes)
|
312
|
-
@parser.reset
|
313
|
-
@parser_nbytes = 0
|
314
|
-
|
315
|
-
true
|
316
|
-
end
|
317
|
-
|
318
|
-
def parse_response_header
|
319
|
-
return false unless parse_header(@response_header)
|
320
|
-
|
321
|
-
unless @response_header.http_status and @response_header.http_reason
|
322
|
-
on_error "no HTTP response"
|
323
|
-
@state = :invalid
|
324
|
-
return false
|
325
|
-
end
|
326
|
-
|
327
|
-
on_response_header(@response_header)
|
328
|
-
|
329
|
-
if @response_header.chunked_encoding?
|
330
|
-
@state = :chunk_header
|
331
|
-
else
|
332
|
-
@state = :body
|
333
|
-
@bytes_remaining = @response_header.content_length
|
334
|
-
end
|
335
|
-
|
336
|
-
true
|
337
|
-
end
|
338
|
-
|
339
|
-
def parse_chunk_header
|
340
|
-
return false unless parse_header(@chunk_header)
|
341
|
-
|
342
|
-
@bytes_remaining = @chunk_header.chunk_size
|
343
|
-
@chunk_header = HttpChunkHeader.new
|
344
|
-
|
345
|
-
@state = @bytes_remaining > 0 ? :chunk_body : :response_footer
|
346
|
-
true
|
347
|
-
end
|
348
|
-
|
349
|
-
def process_chunk_body
|
350
|
-
if @data.size < @bytes_remaining
|
351
|
-
@bytes_remaining -= @data.size
|
352
|
-
on_body_data @data.read
|
353
|
-
return false
|
354
|
-
end
|
355
|
-
|
356
|
-
on_body_data @data.read(@bytes_remaining)
|
357
|
-
@bytes_remaining = 0
|
358
|
-
|
359
|
-
@state = :chunk_footer
|
360
|
-
true
|
361
|
-
end
|
362
|
-
|
363
|
-
def process_chunk_footer
|
364
|
-
return false if @data.size < 2
|
365
|
-
|
366
|
-
if @data.read(2) == CRLF
|
367
|
-
@state = :chunk_header
|
368
|
-
else
|
369
|
-
on_error "non-CRLF chunk footer"
|
370
|
-
@state = :invalid
|
371
|
-
end
|
372
|
-
|
373
|
-
true
|
374
|
-
end
|
375
|
-
|
376
|
-
def process_response_footer
|
377
|
-
return false if @data.size < 2
|
378
|
-
|
379
|
-
if @data.read(2) == CRLF
|
380
|
-
if @data.empty?
|
381
|
-
on_request_complete
|
382
|
-
@state = :finished
|
383
|
-
else
|
384
|
-
on_error "garbage at end of chunked response"
|
385
|
-
@state = :invalid
|
386
|
-
end
|
387
|
-
else
|
388
|
-
on_error "non-CRLF response footer"
|
389
|
-
@state = :invalid
|
390
|
-
end
|
391
|
-
|
392
|
-
false
|
393
|
-
end
|
394
|
-
|
395
|
-
def process_body
|
396
|
-
if @bytes_remaining.nil?
|
397
|
-
on_body_data @data.read
|
398
|
-
return false
|
399
|
-
end
|
400
|
-
|
401
|
-
if @bytes_remaining.zero?
|
402
|
-
on_request_complete
|
403
|
-
@state = :finished
|
404
|
-
return false
|
405
|
-
end
|
406
|
-
|
407
|
-
if @data.size < @bytes_remaining
|
408
|
-
@bytes_remaining -= @data.size
|
409
|
-
on_body_data @data.read
|
410
|
-
return false
|
411
|
-
end
|
412
|
-
|
413
|
-
on_body_data @data.read(@bytes_remaining)
|
414
|
-
@bytes_remaining = 0
|
415
|
-
|
416
|
-
if @data.empty?
|
417
|
-
on_request_complete
|
418
|
-
@state = :finished
|
419
|
-
else
|
420
|
-
on_error "garbage at end of body"
|
421
|
-
@state = :invalid
|
422
|
-
end
|
423
|
-
|
424
|
-
false
|
425
|
-
end
|
426
|
-
end
|
427
|
-
end
|