eventmachine 0.12.6 → 0.12.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/{docs/README → README} +21 -13
  2. data/Rakefile +14 -4
  3. data/docs/DEFERRABLES +0 -5
  4. data/docs/INSTALL +2 -4
  5. data/docs/LEGAL +1 -1
  6. data/docs/LIGHTWEIGHT_CONCURRENCY +0 -2
  7. data/docs/PURE_RUBY +0 -2
  8. data/docs/RELEASE_NOTES +0 -2
  9. data/docs/SMTP +0 -7
  10. data/docs/SPAWNED_PROCESSES +0 -4
  11. data/docs/TODO +0 -2
  12. data/eventmachine.gemspec +17 -8
  13. data/examples/ex_channel.rb +43 -0
  14. data/examples/ex_queue.rb +2 -0
  15. data/examples/helper.rb +2 -0
  16. data/ext/cmain.cpp +119 -20
  17. data/ext/cplusplus.cpp +15 -6
  18. data/ext/ed.cpp +303 -93
  19. data/ext/ed.h +49 -22
  20. data/ext/em.cpp +368 -42
  21. data/ext/em.h +43 -6
  22. data/ext/eventmachine.h +21 -8
  23. data/ext/eventmachine_cpp.h +1 -0
  24. data/ext/extconf.rb +4 -0
  25. data/ext/kb.cpp +1 -2
  26. data/ext/pipe.cpp +1 -3
  27. data/ext/project.h +21 -0
  28. data/ext/rubymain.cpp +232 -32
  29. data/ext/ssl.cpp +38 -1
  30. data/ext/ssl.h +5 -1
  31. data/java/src/com/rubyeventmachine/Application.java +7 -3
  32. data/java/src/com/rubyeventmachine/EmReactor.java +16 -1
  33. data/java/src/com/rubyeventmachine/tests/ConnectTest.java +25 -3
  34. data/lib/{protocols → em}/buftok.rb +16 -5
  35. data/lib/em/callback.rb +26 -0
  36. data/lib/em/channel.rb +57 -0
  37. data/lib/em/connection.rb +505 -0
  38. data/lib/em/deferrable.rb +144 -165
  39. data/lib/em/file_watch.rb +54 -0
  40. data/lib/em/future.rb +24 -25
  41. data/lib/em/messages.rb +1 -1
  42. data/lib/em/process_watch.rb +44 -0
  43. data/lib/em/processes.rb +58 -52
  44. data/lib/em/protocols.rb +35 -0
  45. data/lib/em/protocols/header_and_content.rb +138 -0
  46. data/lib/em/protocols/httpclient.rb +263 -0
  47. data/lib/em/protocols/httpclient2.rb +582 -0
  48. data/lib/{protocols → em/protocols}/line_and_text.rb +2 -2
  49. data/lib/em/protocols/linetext2.rb +160 -0
  50. data/lib/{protocols → em/protocols}/memcache.rb +37 -7
  51. data/lib/em/protocols/object_protocol.rb +39 -0
  52. data/lib/em/protocols/postgres3.rb +247 -0
  53. data/lib/em/protocols/saslauth.rb +175 -0
  54. data/lib/em/protocols/smtpclient.rb +331 -0
  55. data/lib/em/protocols/smtpserver.rb +547 -0
  56. data/lib/em/protocols/stomp.rb +200 -0
  57. data/lib/{protocols → em/protocols}/tcptest.rb +21 -25
  58. data/lib/em/queue.rb +61 -0
  59. data/lib/em/spawnable.rb +53 -56
  60. data/lib/em/streamer.rb +92 -74
  61. data/lib/em/timers.rb +55 -0
  62. data/lib/em/version.rb +3 -0
  63. data/lib/eventmachine.rb +1008 -1298
  64. data/lib/evma.rb +1 -1
  65. data/lib/jeventmachine.rb +106 -101
  66. data/lib/pr_eventmachine.rb +47 -36
  67. data/tasks/project.rake +2 -1
  68. data/tests/client.crt +31 -0
  69. data/tests/client.key +51 -0
  70. data/tests/test_attach.rb +18 -0
  71. data/tests/test_basic.rb +108 -54
  72. data/tests/test_channel.rb +63 -0
  73. data/tests/test_connection_count.rb +2 -2
  74. data/tests/test_epoll.rb +109 -110
  75. data/tests/test_errors.rb +36 -36
  76. data/tests/test_exc.rb +22 -25
  77. data/tests/test_file_watch.rb +49 -0
  78. data/tests/test_futures.rb +77 -93
  79. data/tests/test_hc.rb +2 -2
  80. data/tests/test_httpclient.rb +55 -52
  81. data/tests/test_httpclient2.rb +110 -112
  82. data/tests/test_inactivity_timeout.rb +30 -0
  83. data/tests/test_kb.rb +8 -9
  84. data/tests/test_ltp2.rb +274 -277
  85. data/tests/test_next_tick.rb +91 -65
  86. data/tests/test_object_protocol.rb +37 -0
  87. data/tests/test_process_watch.rb +48 -0
  88. data/tests/test_processes.rb +56 -23
  89. data/tests/test_proxy_connection.rb +92 -0
  90. data/tests/test_pure.rb +1 -5
  91. data/tests/test_queue.rb +44 -0
  92. data/tests/test_running.rb +9 -14
  93. data/tests/test_sasl.rb +32 -34
  94. data/tests/test_send_file.rb +175 -176
  95. data/tests/test_servers.rb +37 -41
  96. data/tests/test_smtpserver.rb +47 -55
  97. data/tests/test_spawn.rb +284 -291
  98. data/tests/test_ssl_args.rb +1 -1
  99. data/tests/test_ssl_methods.rb +1 -1
  100. data/tests/test_ssl_verify.rb +82 -0
  101. data/tests/test_timers.rb +81 -88
  102. data/tests/test_ud.rb +0 -7
  103. data/tests/testem.rb +1 -1
  104. metadata +68 -39
  105. data/lib/em/eventable.rb +0 -39
  106. data/lib/eventmachine_version.rb +0 -31
  107. data/lib/protocols/header_and_content.rb +0 -129
  108. data/lib/protocols/httpcli2.rb +0 -803
  109. data/lib/protocols/httpclient.rb +0 -270
  110. data/lib/protocols/linetext2.rb +0 -161
  111. data/lib/protocols/postgres.rb +0 -261
  112. data/lib/protocols/saslauth.rb +0 -179
  113. data/lib/protocols/smtpclient.rb +0 -308
  114. data/lib/protocols/smtpserver.rb +0 -556
  115. data/lib/protocols/stomp.rb +0 -153
  116. data/tests/test_eventables.rb +0 -77
@@ -1,39 +0,0 @@
1
- # $Id$
2
- #
3
- # Author:: Francis Cianfrocca (gmail: blackhedd)
4
- # Homepage:: http://rubyeventmachine.com
5
- # Date:: 16 Jul 2006
6
- #
7
- # See EventMachine and EventMachine::Connection for documentation and
8
- # usage examples.
9
- #
10
- #----------------------------------------------------------------------------
11
- #
12
- # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
- # Gmail: blackhedd
14
- #
15
- # This program is free software; you can redistribute it and/or modify
16
- # it under the terms of either: 1) the GNU General Public License
17
- # as published by the Free Software Foundation; either version 2 of the
18
- # License, or (at your option) any later version; or 2) Ruby's License.
19
- #
20
- # See the file COPYING for complete licensing information.
21
- #
22
- #---------------------------------------------------------------------------
23
- #
24
- #
25
-
26
-
27
- module EventMachine
28
- module Eventable
29
-
30
- def listen_event event_name
31
- end
32
-
33
- def post_event event_name, arg
34
- end
35
-
36
- end
37
- end
38
-
39
-
@@ -1,31 +0,0 @@
1
- # $Id$
2
- #
3
- # Author:: Francis Cianfrocca (gmail: blackhedd)
4
- # Homepage:: http://rubyeventmachine.com
5
- # Date:: 8 Apr 2006
6
- #
7
- # See EventMachine and EventMachine::Connection for documentation and
8
- # usage examples.
9
- #
10
- #----------------------------------------------------------------------------
11
- #
12
- # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
- # Gmail: blackhedd
14
- #
15
- # This program is free software; you can redistribute it and/or modify
16
- # it under the terms of either: 1) the GNU General Public License
17
- # as published by the Free Software Foundation; either version 2 of the
18
- # License, or (at your option) any later version; or 2) Ruby's License.
19
- #
20
- # See the file COPYING for complete licensing information.
21
- #
22
- #---------------------------------------------------------------------------
23
- #
24
- #
25
-
26
- module EventMachine
27
-
28
- VERSION = "0.12.6"
29
-
30
- end
31
-
@@ -1,129 +0,0 @@
1
- # $Id$
2
- #
3
- # Author:: Francis Cianfrocca (gmail: blackhedd)
4
- # Homepage:: http://rubyeventmachine.com
5
- # Date:: 15 Nov 2006
6
- #
7
- # See EventMachine and EventMachine::Connection for documentation and
8
- # usage examples.
9
- #
10
- #----------------------------------------------------------------------------
11
- #
12
- # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
- # Gmail: blackhedd
14
- #
15
- # This program is free software; you can redistribute it and/or modify
16
- # it under the terms of either: 1) the GNU General Public License
17
- # as published by the Free Software Foundation; either version 2 of the
18
- # License, or (at your option) any later version; or 2) Ruby's License.
19
- #
20
- # See the file COPYING for complete licensing information.
21
- #
22
- #---------------------------------------------------------------------------
23
- #
24
- #
25
-
26
-
27
-
28
- module EventMachine
29
- module Protocols
30
-
31
- # Originally, this subclassed LineAndTextProtocol, which in
32
- # turn relies on BufferedTokenizer, which doesn't gracefully
33
- # handle the transitions between lines and binary text.
34
- # Changed 13Sep08 by FCianfrocca.
35
- class HeaderAndContentProtocol < Connection
36
- include LineText2
37
-
38
-
39
- ContentLengthPattern = /Content-length:\s*(\d+)/i
40
-
41
- def initialize *args
42
- super
43
- init_for_request
44
- end
45
-
46
- def receive_line line
47
- case @hc_mode
48
- when :discard_blanks
49
- unless line == ""
50
- @hc_mode = :headers
51
- receive_line line
52
- end
53
- when :headers
54
- if line == ""
55
- raise "unrecognized state" unless @hc_headers.length > 0
56
- if respond_to?(:receive_headers)
57
- receive_headers @hc_headers
58
- end
59
- # @hc_content_length will be nil, not 0, if there was no content-length header.
60
- if @hc_content_length.to_i > 0
61
- set_binary_mode @hc_content_length
62
- else
63
- dispatch_request
64
- end
65
- else
66
- @hc_headers << line
67
- if ContentLengthPattern =~ line
68
- # There are some attacks that rely on sending multiple content-length
69
- # headers. This is a crude protection, but needs to become tunable.
70
- raise "extraneous content-length header" if @hc_content_length
71
- @hc_content_length = $1.to_i
72
- end
73
- if @hc_headers.length == 1 and respond_to?(:receive_first_header_line)
74
- receive_first_header_line line
75
- end
76
- end
77
- else
78
- raise "internal error, unsupported mode"
79
- end
80
- end
81
-
82
- def receive_binary_data text
83
- @hc_content = text
84
- dispatch_request
85
- end
86
-
87
- def dispatch_request
88
- if respond_to?(:receive_request)
89
- receive_request @hc_headers, @hc_content
90
- end
91
- init_for_request
92
- end
93
- private :dispatch_request
94
-
95
- def init_for_request
96
- @hc_mode = :discard_blanks
97
- @hc_headers = []
98
- # originally was @hc_headers ||= []; @hc_headers.clear to get a performance
99
- # boost, but it's counterproductive because a subclassed handler will have to
100
- # call dup to use the header array we pass in receive_headers.
101
-
102
- @hc_content_length = nil
103
- @hc_content = ""
104
- end
105
- private :init_for_request
106
-
107
- # Basically a convenience method. We might create a subclass that does this
108
- # automatically. But it's such a performance killer.
109
- def headers_2_hash hdrs
110
- self.class.headers_2_hash hdrs
111
- end
112
-
113
- class << self
114
- def headers_2_hash hdrs
115
- hash = {}
116
- hdrs.each {|h|
117
- if /\A([^\s:]+)\s*:\s*/ =~ h
118
- tail = $'.dup
119
- hash[ $1.downcase.gsub(/-/,"_").intern ] = tail
120
- end
121
- }
122
- hash
123
- end
124
- end
125
-
126
- end
127
- end
128
- end
129
-
@@ -1,803 +0,0 @@
1
- # $Id$
2
- #
3
- # Author:: Francis Cianfrocca (gmail: blackhedd)
4
- # Homepage:: http://rubyeventmachine.com
5
- # Date:: 16 July 2006
6
- #
7
- # See EventMachine and EventMachine::Connection for documentation and
8
- # usage examples.
9
- #
10
- #----------------------------------------------------------------------------
11
- #
12
- # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
- # Gmail: blackhedd
14
- #
15
- # This program is free software; you can redistribute it and/or modify
16
- # it under the terms of either: 1) the GNU General Public License
17
- # as published by the Free Software Foundation; either version 2 of the
18
- # License, or (at your option) any later version; or 2) Ruby's License.
19
- #
20
- # See the file COPYING for complete licensing information.
21
- #
22
- #---------------------------------------------------------------------------
23
- #
24
- #
25
-
26
-
27
-
28
- module EventMachine
29
- module Protocols
30
-
31
- # = Example
32
- #
33
- #
34
- # EM.run{
35
- # include EM::Protocols
36
- # conn = HttpClient2.connect 'google.com', 80
37
- #
38
- # req = conn.get('/')
39
- # req.callback{
40
- # p(req.content)
41
- # }
42
- class HttpClient2 < Connection
43
- include LineText2
44
-
45
-
46
- class Request
47
- include Deferrable
48
-
49
- attr_reader :version
50
- attr_reader :status
51
- attr_reader :header_lines
52
- attr_reader :headers
53
- attr_reader :content
54
- attr_reader :internal_error
55
-
56
- def initialize conn, args
57
- @conn = conn
58
- @args = args
59
- @header_lines = []
60
- @headers = {}
61
- @blanks = 0
62
- end
63
-
64
- def send_request
65
- az = @args[:authorization] and az = "Authorization: #{az}\r\n"
66
-
67
- r = [
68
- "#{@args[:verb]} #{@args[:uri]} HTTP/#{@args[:version] || "1.1"}\r\n",
69
- "Host: #{@args[:host_header] || "_"}\r\n",
70
- az || "",
71
- "\r\n"
72
- ]
73
- @conn.send_data r.join
74
- end
75
-
76
-
77
- #--
78
- #
79
- def receive_line ln
80
- if @chunk_trailer
81
- receive_chunk_trailer(ln)
82
- elsif @chunking
83
- receive_chunk_header(ln)
84
- else
85
- receive_header_line(ln)
86
- end
87
- end
88
-
89
- #--
90
- #
91
- def receive_chunk_trailer ln
92
- if ln.length == 0
93
- @conn.pop_request
94
- succeed
95
- else
96
- p "Received chunk trailer line"
97
- end
98
- end
99
-
100
- #--
101
- # Allow up to ten blank lines before we get a real response line.
102
- # Allow no more than 100 lines in the header.
103
- #
104
- def receive_header_line ln
105
- if ln.length == 0
106
- if @header_lines.length > 0
107
- process_header
108
- else
109
- @blanks += 1
110
- if @blanks > 10
111
- @conn.close_connection
112
- end
113
- end
114
- else
115
- @header_lines << ln
116
- if @header_lines.length > 100
117
- @internal_error = :bad_header
118
- @conn.close_connection
119
- end
120
- end
121
- end
122
-
123
- #--
124
- # Cf RFC 2616 pgh 3.6.1 for the format of HTTP chunks.
125
- #
126
- def receive_chunk_header ln
127
- if ln.length > 0
128
- chunksize = ln.to_i(16)
129
- if chunksize > 0
130
- @conn.set_text_mode(ln.to_i(16))
131
- else
132
- @content = @content ? @content.join : ''
133
- @chunk_trailer = true
134
- end
135
- else
136
- # We correctly come here after each chunk gets read.
137
- # p "Got A BLANK chunk line"
138
- end
139
-
140
- end
141
-
142
-
143
- #--
144
- # We get a single chunk. Append it to the incoming content and switch back to line mode.
145
- #
146
- def receive_chunked_text text
147
- # p "RECEIVED #{text.length} CHUNK"
148
- (@content ||= []) << text
149
- end
150
-
151
-
152
- #--
153
- # TODO, inefficient how we're handling this. Part of it is done so as to
154
- # make sure we don't have problems in detecting chunked-encoding, content-length,
155
- # etc.
156
- #
157
- #
158
- HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i
159
- ClenRE = /\AContent-length:\s*(\d+)/i
160
- ChunkedRE = /\ATransfer-encoding:\s*chunked/i
161
- ColonRE = /\:\s*/
162
-
163
- def process_header
164
- unless @header_lines.first =~ HttpResponseRE
165
- @conn.close_connection
166
- @internal_error = :bad_request
167
- end
168
- @version = $1.dup
169
- @status = $2.dup.to_i
170
-
171
- clen = nil
172
- chunks = nil
173
- @header_lines.each_with_index do |e,ix|
174
- if ix > 0
175
- hdr,val = e.split(ColonRE,2)
176
- (@headers[hdr.downcase] ||= []) << val
177
- end
178
-
179
- if clen == nil and e =~ ClenRE
180
- clen = $1.dup.to_i
181
- end
182
- if e =~ ChunkedRE
183
- chunks = true
184
- end
185
- end
186
-
187
- if clen
188
- # If the content length is zero we should not call set_text_mode,
189
- # because a value of zero will make it wait forever, hanging the
190
- # connection. Just return success instead, with empty content.
191
- if clen == 0 then
192
- @content = ""
193
- @conn.pop_request
194
- succeed
195
- else
196
- @conn.set_text_mode clen
197
- end
198
- elsif chunks
199
- @chunking = true
200
- else
201
- # Chunked transfer, multipart, or end-of-connection.
202
- # For end-of-connection, we need to go the unbind
203
- # method and suppress its desire to fail us.
204
- p "NO CLEN"
205
- p @args[:uri]
206
- p @header_lines
207
- @internal_error = :unsupported_clen
208
- @conn.close_connection
209
- end
210
- end
211
- private :process_header
212
-
213
-
214
- def receive_text text
215
- @chunking ? receive_chunked_text(text) : receive_sized_text(text)
216
- end
217
-
218
- #--
219
- # At the present time, we only handle contents that have a length
220
- # specified by the content-length header.
221
- #
222
- def receive_sized_text text
223
- @content = text
224
- @conn.pop_request
225
- succeed
226
- end
227
- end
228
-
229
- # Make a connection to a remote HTTP server.
230
- # Can take either a pair of arguments (which will be interpreted as
231
- # a hostname/ip-address and a port), or a hash.
232
- # If the arguments are a hash, then supported values include:
233
- # :host => a hostname or ip-address;
234
- # :port => a port number
235
- #--
236
- # TODO, support optional encryption arguments like :ssl
237
- def self.connect *args
238
- if args.length == 2
239
- args = {:host=>args[0], :port=>args[1]}
240
- else
241
- args = args.first
242
- end
243
-
244
- h,prt,ssl = args[:host], Integer(args[:port]), (args[:tls] || args[:ssl])
245
- conn = EM.connect( h, prt, self )
246
- conn.start_tls if ssl
247
- conn.set_default_host_header( h, prt, ssl )
248
- conn
249
- end
250
-
251
-
252
- #--
253
- # Compute and remember a string to be used as the host header in HTTP requests
254
- # unless the user overrides it with an argument to #request.
255
- #
256
- def set_default_host_header host, port, ssl
257
- if (ssl and port != 443) or (!ssl and port != 80)
258
- @host_header = "#{host}:#{port}"
259
- else
260
- @host_header = host
261
- end
262
- end
263
-
264
-
265
- def post_init
266
- super
267
- @connected = EM::DefaultDeferrable.new
268
- end
269
-
270
- def connection_completed
271
- super
272
- @connected.succeed
273
- end
274
-
275
- #--
276
- # All pending requests, if any, must fail.
277
- # We might come here without ever passing through connection_completed
278
- # in case we can't connect to the server. We'll also get here when the
279
- # connection closes (either because the server closes it, or we close it
280
- # due to detecting an internal error or security violation).
281
- # In either case, run down all pending requests, if any, and signal failure
282
- # on them.
283
- #
284
- # Set and remember a flag (@closed) so we can immediately fail any
285
- # subsequent requests.
286
- #
287
- def unbind
288
- super
289
- @closed = true
290
- (@requests || []).each {|r| r.fail}
291
- end
292
-
293
-
294
- def get args
295
- if args.is_a?(String)
296
- args = {:uri=>args}
297
- end
298
- args[:verb] = "GET"
299
- request args
300
- end
301
-
302
- def post args
303
- if args.is_a?(String)
304
- args = {:uri=>args}
305
- end
306
- args[:verb] = "POST"
307
- request args
308
- end
309
-
310
- def request args
311
- args[:host_header] = @host_header unless args.has_key?(:host_header)
312
- args[:authorization] = @authorization unless args.has_key?(:authorization)
313
- r = Request.new self, args
314
- if @closed
315
- r.fail
316
- else
317
- (@requests ||= []).unshift r
318
- @connected.callback {r.send_request}
319
- end
320
- r
321
- end
322
-
323
- def receive_line ln
324
- if req = @requests.last
325
- req.receive_line ln
326
- else
327
- p "??????????"
328
- p ln
329
- end
330
-
331
- end
332
- def receive_binary_data text
333
- @requests.last.receive_text text
334
- end
335
-
336
- #--
337
- # Called by a Request object when it completes.
338
- #
339
- def pop_request
340
- @requests.pop
341
- end
342
- end
343
-
344
-
345
- =begin
346
- class HttpClient2x < Connection
347
- include LineText2
348
-
349
- # TODO: Make this behave appropriate in case a #connect fails.
350
- # Currently, this produces no errors.
351
-
352
- # Make a connection to a remote HTTP server.
353
- # Can take either a pair of arguments (which will be interpreted as
354
- # a hostname/ip-address and a port), or a hash.
355
- # If the arguments are a hash, then supported values include:
356
- # :host => a hostname or ip-address;
357
- # :port => a port number
358
- #--
359
- # TODO, support optional encryption arguments like :ssl
360
- def self.connect *args
361
- if args.length == 2
362
- args = {:host=>args[0], :port=>args[1]}
363
- else
364
- args = args.first
365
- end
366
-
367
- h,prt = args[:host],Integer(args[:port])
368
- EM.connect( h, prt, self, h, prt )
369
- end
370
-
371
-
372
- #--
373
- # Sugars a connection that makes a single request and then
374
- # closes the connection. Matches the behavior and the arguments
375
- # of the original implementation of class HttpClient.
376
- #
377
- # Intended primarily for back compatibility, but the idiom
378
- # is probably useful so it's not deprecated.
379
- # We return a Deferrable, as did the original implementation.
380
- #
381
- # Because we're improving the way we deal with errors and exceptions
382
- # (specifically, HTTP response codes other than 2xx will trigger the
383
- # errback rather than the callback), this may break some existing code.
384
- #
385
- def self.request args
386
- c = connect args
387
- end
388
-
389
- #--
390
- # Requests can be pipelined. When we get a request, add it to the
391
- # front of a queue as an array. The last element of the @requests
392
- # array is always the oldest request received. Each element of the
393
- # @requests array is a two-element array consisting of a hash with
394
- # the original caller's arguments, and an initially-empty Ostruct
395
- # containing the data we retrieve from the server's response.
396
- # Maintain the instance variable @current_response, which is the response
397
- # of the oldest pending request. That's just to make other code a little
398
- # easier. If the variable doesn't exist when we come here, we're
399
- # obviously the first request being made on the connection.
400
- #
401
- # The reason for keeping this method private (and requiring use of the
402
- # convenience methods #get, #post, #head, etc) is to avoid the small
403
- # performance penalty of canonicalizing the verb.
404
- #
405
- def request args
406
- d = EventMachine::DefaultDeferrable.new
407
-
408
- if @closed
409
- d.fail
410
- return d
411
- end
412
-
413
- o = OpenStruct.new
414
- o.deferrable = d
415
- (@requests ||= []).unshift [args, o]
416
- @current_response ||= @requests.last.last
417
- @connected.callback {
418
- az = args[:authorization] and az = "Authorization: #{az}\r\n"
419
-
420
- r = [
421
- "#{args[:verb]} #{args[:uri]} HTTP/#{args[:version] || "1.1"}\r\n",
422
- "Host: #{args[:host_header] || @host_header}\r\n",
423
- az || "",
424
- "\r\n"
425
- ]
426
- p r
427
- send_data r.join
428
- }
429
- o.deferrable
430
- end
431
- private :request
432
-
433
- def get args
434
- if args.is_a?(String)
435
- args = {:uri=>args}
436
- end
437
- args[:verb] = "GET"
438
- request args
439
- end
440
-
441
- def initialize host, port
442
- super
443
- @host_header = "#{host}:#{port}"
444
- end
445
- def post_init
446
- super
447
- @connected = EM::DefaultDeferrable.new
448
- end
449
-
450
-
451
- def connection_completed
452
- super
453
- @connected.succeed
454
- end
455
-
456
- #--
457
- # Make sure to throw away any leftover incoming data if we've
458
- # been closed due to recognizing an error.
459
- #
460
- # Generate an internal error if we get an unreasonable number of
461
- # header lines. It could be malicious.
462
- #
463
- def receive_line ln
464
- p ln
465
- return if @closed
466
-
467
- if ln.length > 0
468
- (@current_response.headers ||= []).push ln
469
- abort_connection if @current_response.headers.length > 100
470
- else
471
- process_received_headers
472
- end
473
- end
474
-
475
- #--
476
- # We come here when we've seen all the headers for a particular request.
477
- # What we do next depends on the response line (which should be the
478
- # first line in the header set), and whether there is content to read.
479
- # We may transition into a text-reading state to read content, or
480
- # we may abort the connection, or we may go right back into parsing
481
- # responses for the next response in the chain.
482
- #
483
- # We make an ASSUMPTION that the first line is an HTTP response.
484
- # Anything else produces an error that aborts the connection.
485
- # This may not be enough, because it may be that responses to pipelined
486
- # requests will come with a blank-line delimiter.
487
- #
488
- # Any non-2xx response will be treated as a fatal error, and abort the
489
- # connection. We will set up the status and other response parameters.
490
- # TODO: we will want to properly support 1xx responses, which some versions
491
- # of IIS copiously generate.
492
- # TODO: We need to give the option of not aborting the connection with certain
493
- # non-200 responses, in order to work with NTLM and other authentication
494
- # schemes that work at the level of individual connections.
495
- #
496
- # Some error responses will get sugarings. For example, we'll return the
497
- # Location header in the response in case of a 301/302 response.
498
- #
499
- # Possible dispositions here:
500
- # 1) No content to read (either content-length is zero or it's a HEAD request);
501
- # 2) Switch to text mode to read a specific number of bytes;
502
- # 3) Read a chunked or multipart response;
503
- # 4) Read till the server closes the connection.
504
- #
505
- # Our reponse to the client can be either to wait till all the content
506
- # has been read and then to signal caller's deferrable, or else to signal
507
- # it when we finish the processing the headers and then expect the caller
508
- # to have given us a block to call as the content comes in. And of course
509
- # the latter gets stickier with chunks and multiparts.
510
- #
511
- HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i
512
- ClenRE = /\AContent-length:\s*(\d+)/i
513
- def process_received_headers
514
- abort_connection unless @current_response.headers.first =~ HttpResponseRE
515
- @current_response.version = $1.dup
516
- st = $2.dup
517
- @current_response.status = st.to_i
518
- abort_connection unless st[0,1] == "2"
519
-
520
- clen = nil
521
- @current_response.headers.each do |e|
522
- if clen == nil and e =~ ClenRE
523
- clen = $1.dup.to_i
524
- end
525
- end
526
-
527
- if clen
528
- set_text_mode clen
529
- end
530
- end
531
- private :process_received_headers
532
-
533
-
534
- def receive_binary_data text
535
- @current_response.content = text
536
- @current_response.deferrable.succeed @current_response
537
- @requests.pop
538
- @current_response = (@requests.last || []).last
539
- set_line_mode
540
- end
541
-
542
-
543
-
544
- # We've received either a server error or an internal error.
545
- # Close the connection and abort any pending requests.
546
- #--
547
- # When should we call close_connection? It will cause #unbind
548
- # to be fired. Should the user expect to see #unbind before
549
- # we call #receive_http_error, or the other way around?
550
- #
551
- # Set instance variable @closed. That's used to inhibit further
552
- # processing of any inbound data after an error has been recognized.
553
- #
554
- # We shouldn't have to worry about any leftover outbound data,
555
- # because we call close_connection (not close_connection_after_writing).
556
- # That ensures that any pipelined requests received after an error
557
- # DO NOT get streamed out to the server on this connection.
558
- # Very important. TODO, write a unit-test to establish that behavior.
559
- #
560
- def abort_connection
561
- close_connection
562
- @closed = true
563
- @current_response.deferrable.fail( @current_response )
564
- end
565
-
566
-
567
- #------------------------
568
- # Below here are user-overridable methods.
569
-
570
- end
571
- =end
572
- end
573
- end
574
-
575
-
576
- =begin
577
- module EventMachine
578
- module Protocols
579
-
580
- class HttpClient < Connection
581
- include EventMachine::Deferrable
582
-
583
-
584
- MaxPostContentLength = 20 * 1024 * 1024
585
-
586
- # USAGE SAMPLE:
587
- #
588
- # EventMachine.run {
589
- # http = EventMachine::Protocols::HttpClient.request(
590
- # :host => server,
591
- # :port => 80,
592
- # :request => "/index.html",
593
- # :query_string => "parm1=value1&parm2=value2"
594
- # )
595
- # http.callback {|response|
596
- # puts response[:status]
597
- # puts response[:headers]
598
- # puts response[:content]
599
- # }
600
- # }
601
- #
602
-
603
- # TODO:
604
- # Add streaming so we can support enormous POSTs. Current max is 20meg.
605
- # Timeout for connections that run too long or hang somewhere in the middle.
606
- # Persistent connections (HTTP/1.1), may need a associated delegate object.
607
- # DNS: Some way to cache DNS lookups for hostnames we connect to. Ruby's
608
- # DNS lookups are unbelievably slow.
609
- # HEAD requests.
610
- # Chunked transfer encoding.
611
- # Convenience methods for requests. get, post, url, etc.
612
- # SSL.
613
- # Handle status codes like 304, 100, etc.
614
- # Refactor this code so that protocol errors all get handled one way (an exception?),
615
- # instead of sprinkling set_deferred_status :failed calls everywhere.
616
-
617
- def self.request( args = {} )
618
- args[:port] ||= 80
619
- EventMachine.connect( args[:host], args[:port], self ) {|c|
620
- # According to the docs, we will get here AFTER post_init is called.
621
- c.instance_eval {@args = args}
622
- }
623
- end
624
-
625
- def post_init
626
- @start_time = Time.now
627
- @data = ""
628
- @read_state = :base
629
- end
630
-
631
- # We send the request when we get a connection.
632
- # AND, we set an instance variable to indicate we passed through here.
633
- # That allows #unbind to know whether there was a successful connection.
634
- # NB: This naive technique won't work when we have to support multiple
635
- # requests on a single connection.
636
- def connection_completed
637
- @connected = true
638
- send_request @args
639
- end
640
-
641
- def send_request args
642
- args[:verb] ||= args[:method] # Support :method as an alternative to :verb.
643
- args[:verb] ||= :get # IS THIS A GOOD IDEA, to default to GET if nothing was specified?
644
-
645
- verb = args[:verb].to_s.upcase
646
- unless ["GET", "POST", "PUT", "DELETE", "HEAD"].include?(verb)
647
- set_deferred_status :failed, {:status => 0} # TODO, not signalling the error type
648
- return # NOTE THE EARLY RETURN, we're not sending any data.
649
- end
650
-
651
- request = args[:request] || "/"
652
- unless request[0,1] == "/"
653
- request = "/" + request
654
- end
655
-
656
- qs = args[:query_string] || ""
657
- if qs.length > 0 and qs[0,1] != '?'
658
- qs = "?" + qs
659
- end
660
-
661
- # Allow an override for the host header if it's not the connect-string.
662
- host = args[:host_header] || args[:host] || "_"
663
- # For now, ALWAYS tuck in the port string, although we may want to omit it if it's the default.
664
- port = args[:port]
665
-
666
- # POST items.
667
- postcontenttype = args[:contenttype] || "application/octet-stream"
668
- postcontent = args[:content] || ""
669
- raise "oversized content in HTTP POST" if postcontent.length > MaxPostContentLength
670
-
671
- # ESSENTIAL for the request's line-endings to be CRLF, not LF. Some servers misbehave otherwise.
672
- # TODO: We ASSUME the caller wants to send a 1.1 request. May not be a good assumption.
673
- req = [
674
- "#{verb} #{request}#{qs} HTTP/1.1",
675
- "Host: #{host}:#{port}",
676
- "User-agent: Ruby EventMachine",
677
- ]
678
-
679
- if verb == "POST" || verb == "PUT"
680
- req << "Content-type: #{postcontenttype}"
681
- req << "Content-length: #{postcontent.length}"
682
- end
683
-
684
- # TODO, this cookie handler assumes it's getting a single, semicolon-delimited string.
685
- # Eventually we will want to deal intelligently with arrays and hashes.
686
- if args[:cookie]
687
- req << "Cookie: #{args[:cookie]}"
688
- end
689
-
690
- req << ""
691
- reqstring = req.map {|l| "#{l}\r\n"}.join
692
- send_data reqstring
693
-
694
- if verb == "POST" || verb == "PUT"
695
- send_data postcontent
696
- end
697
- end
698
-
699
-
700
- def receive_data data
701
- while data and data.length > 0
702
- case @read_state
703
- when :base
704
- # Perform any per-request initialization here and don't consume any data.
705
- @data = ""
706
- @headers = []
707
- @content_length = nil # not zero
708
- @content = ""
709
- @status = nil
710
- @read_state = :header
711
- when :header
712
- ary = data.split( /\r?\n/m, 2 )
713
- if ary.length == 2
714
- data = ary.last
715
- if ary.first == ""
716
- if @content_length and @content_length > 0
717
- @read_state = :content
718
- else
719
- dispatch_response
720
- @read_state = :base
721
- end
722
- else
723
- @headers << ary.first
724
- if @headers.length == 1
725
- parse_response_line
726
- elsif ary.first =~ /\Acontent-length:\s*/i
727
- # Only take the FIRST content-length header that appears,
728
- # which we can distinguish because @content_length is nil.
729
- # TODO, it's actually a fatal error if there is more than one
730
- # content-length header, because the caller is presumptively
731
- # a bad guy. (There is an exploit that depends on multiple
732
- # content-length headers.)
733
- @content_length ||= $'.to_i
734
- end
735
- end
736
- else
737
- @data << data
738
- data = ""
739
- end
740
- when :content
741
- # If there was no content-length header, we have to wait until the connection
742
- # closes. Everything we get until that point is content.
743
- # TODO: Must impose a content-size limit, and also must implement chunking.
744
- # Also, must support either temporary files for large content, or calling
745
- # a content-consumer block supplied by the user.
746
- if @content_length
747
- bytes_needed = @content_length - @content.length
748
- @content += data[0, bytes_needed]
749
- data = data[bytes_needed..-1] || ""
750
- if @content_length == @content.length
751
- dispatch_response
752
- @read_state = :base
753
- end
754
- else
755
- @content << data
756
- data = ""
757
- end
758
- end
759
- end
760
- end
761
-
762
-
763
- # We get called here when we have received an HTTP response line.
764
- # It's an opportunity to throw an exception or trigger other exceptional
765
- # handling.
766
- def parse_response_line
767
- if @headers.first =~ /\AHTTP\/1\.[01] ([\d]{3})/
768
- @status = $1.to_i
769
- else
770
- set_deferred_status :failed, {
771
- :status => 0 # crappy way of signifying an unrecognized response. TODO, find a better way to do this.
772
- }
773
- close_connection
774
- end
775
- end
776
- private :parse_response_line
777
-
778
- def dispatch_response
779
- @read_state = :base
780
- set_deferred_status :succeeded, {
781
- :content => @content,
782
- :headers => @headers,
783
- :status => @status
784
- }
785
- # TODO, we close the connection for now, but this is wrong for persistent clients.
786
- close_connection
787
- end
788
-
789
- def unbind
790
- if !@connected
791
- set_deferred_status :failed, {:status => 0} # YECCCCH. Find a better way to signal no-connect/network error.
792
- elsif (@read_state == :content and @content_length == nil)
793
- dispatch_response
794
- end
795
- end
796
- end
797
-
798
-
799
- end
800
- end
801
-
802
- =end
803
-