wj_eventmachine 1.3.0.dev.1

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.
Files changed (180) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +179 -0
  3. data/GNU +281 -0
  4. data/LICENSE +60 -0
  5. data/README.md +110 -0
  6. data/docs/DocumentationGuidesIndex.md +27 -0
  7. data/docs/GettingStarted.md +520 -0
  8. data/docs/old/ChangeLog +211 -0
  9. data/docs/old/DEFERRABLES +246 -0
  10. data/docs/old/EPOLL +141 -0
  11. data/docs/old/INSTALL +13 -0
  12. data/docs/old/KEYBOARD +42 -0
  13. data/docs/old/LEGAL +25 -0
  14. data/docs/old/LIGHTWEIGHT_CONCURRENCY +130 -0
  15. data/docs/old/PURE_RUBY +75 -0
  16. data/docs/old/RELEASE_NOTES +94 -0
  17. data/docs/old/SMTP +4 -0
  18. data/docs/old/SPAWNED_PROCESSES +148 -0
  19. data/docs/old/TODO +8 -0
  20. data/examples/guides/getting_started/01_eventmachine_echo_server.rb +18 -0
  21. data/examples/guides/getting_started/02_eventmachine_echo_server_that_recognizes_exit_command.rb +22 -0
  22. data/examples/guides/getting_started/03_simple_chat_server.rb +149 -0
  23. data/examples/guides/getting_started/04_simple_chat_server_step_one.rb +27 -0
  24. data/examples/guides/getting_started/05_simple_chat_server_step_two.rb +43 -0
  25. data/examples/guides/getting_started/06_simple_chat_server_step_three.rb +98 -0
  26. data/examples/guides/getting_started/07_simple_chat_server_step_four.rb +121 -0
  27. data/examples/guides/getting_started/08_simple_chat_server_step_five.rb +141 -0
  28. data/examples/old/ex_channel.rb +43 -0
  29. data/examples/old/ex_queue.rb +2 -0
  30. data/examples/old/ex_tick_loop_array.rb +15 -0
  31. data/examples/old/ex_tick_loop_counter.rb +32 -0
  32. data/examples/old/helper.rb +2 -0
  33. data/ext/binder.cpp +124 -0
  34. data/ext/binder.h +52 -0
  35. data/ext/cmain.cpp +1046 -0
  36. data/ext/ed.cpp +2238 -0
  37. data/ext/ed.h +460 -0
  38. data/ext/em.cpp +2378 -0
  39. data/ext/em.h +266 -0
  40. data/ext/eventmachine.h +152 -0
  41. data/ext/extconf.rb +285 -0
  42. data/ext/fastfilereader/extconf.rb +120 -0
  43. data/ext/fastfilereader/mapper.cpp +214 -0
  44. data/ext/fastfilereader/mapper.h +59 -0
  45. data/ext/fastfilereader/rubymain.cpp +126 -0
  46. data/ext/kb.cpp +79 -0
  47. data/ext/page.cpp +107 -0
  48. data/ext/page.h +51 -0
  49. data/ext/pipe.cpp +354 -0
  50. data/ext/project.h +174 -0
  51. data/ext/rubymain.cpp +1610 -0
  52. data/ext/ssl.cpp +627 -0
  53. data/ext/ssl.h +103 -0
  54. data/ext/wait_for_single_fd.h +36 -0
  55. data/java/.classpath +8 -0
  56. data/java/.project +17 -0
  57. data/java/src/com/rubyeventmachine/EmReactor.java +625 -0
  58. data/java/src/com/rubyeventmachine/EmReactorException.java +40 -0
  59. data/java/src/com/rubyeventmachine/EmReactorInterface.java +70 -0
  60. data/java/src/com/rubyeventmachine/EventableChannel.java +72 -0
  61. data/java/src/com/rubyeventmachine/EventableDatagramChannel.java +201 -0
  62. data/java/src/com/rubyeventmachine/EventableSocketChannel.java +415 -0
  63. data/java/src/com/rubyeventmachine/NullEmReactor.java +157 -0
  64. data/java/src/com/rubyeventmachine/NullEventableChannel.java +81 -0
  65. data/lib/em/buftok.rb +59 -0
  66. data/lib/em/callback.rb +58 -0
  67. data/lib/em/channel.rb +69 -0
  68. data/lib/em/completion.rb +307 -0
  69. data/lib/em/connection.rb +776 -0
  70. data/lib/em/deferrable.rb +210 -0
  71. data/lib/em/deferrable/pool.rb +2 -0
  72. data/lib/em/file_watch.rb +73 -0
  73. data/lib/em/future.rb +61 -0
  74. data/lib/em/io_streamer.rb +68 -0
  75. data/lib/em/iterator.rb +252 -0
  76. data/lib/em/messages.rb +66 -0
  77. data/lib/em/pool.rb +151 -0
  78. data/lib/em/process_watch.rb +45 -0
  79. data/lib/em/processes.rb +123 -0
  80. data/lib/em/protocols.rb +37 -0
  81. data/lib/em/protocols/header_and_content.rb +138 -0
  82. data/lib/em/protocols/httpclient.rb +303 -0
  83. data/lib/em/protocols/httpclient2.rb +602 -0
  84. data/lib/em/protocols/line_and_text.rb +125 -0
  85. data/lib/em/protocols/line_protocol.rb +33 -0
  86. data/lib/em/protocols/linetext2.rb +179 -0
  87. data/lib/em/protocols/memcache.rb +331 -0
  88. data/lib/em/protocols/object_protocol.rb +46 -0
  89. data/lib/em/protocols/postgres3.rb +246 -0
  90. data/lib/em/protocols/saslauth.rb +175 -0
  91. data/lib/em/protocols/smtpclient.rb +394 -0
  92. data/lib/em/protocols/smtpserver.rb +666 -0
  93. data/lib/em/protocols/socks4.rb +66 -0
  94. data/lib/em/protocols/stomp.rb +205 -0
  95. data/lib/em/protocols/tcptest.rb +54 -0
  96. data/lib/em/pure_ruby.rb +1299 -0
  97. data/lib/em/queue.rb +80 -0
  98. data/lib/em/resolver.rb +232 -0
  99. data/lib/em/spawnable.rb +84 -0
  100. data/lib/em/streamer.rb +118 -0
  101. data/lib/em/threaded_resource.rb +90 -0
  102. data/lib/em/tick_loop.rb +85 -0
  103. data/lib/em/timers.rb +61 -0
  104. data/lib/em/version.rb +3 -0
  105. data/lib/eventmachine.rb +1602 -0
  106. data/lib/jeventmachine.rb +318 -0
  107. data/rakelib/package.rake +120 -0
  108. data/rakelib/test.rake +6 -0
  109. data/rakelib/test_pure.rake +11 -0
  110. data/tests/client.crt +31 -0
  111. data/tests/client.key +51 -0
  112. data/tests/dhparam.pem +13 -0
  113. data/tests/em_ssl_handlers.rb +153 -0
  114. data/tests/em_test_helper.rb +198 -0
  115. data/tests/jruby/test_jeventmachine.rb +38 -0
  116. data/tests/test_attach.rb +199 -0
  117. data/tests/test_basic.rb +321 -0
  118. data/tests/test_channel.rb +75 -0
  119. data/tests/test_completion.rb +178 -0
  120. data/tests/test_connection_count.rb +83 -0
  121. data/tests/test_connection_write.rb +35 -0
  122. data/tests/test_defer.rb +35 -0
  123. data/tests/test_deferrable.rb +35 -0
  124. data/tests/test_epoll.rb +141 -0
  125. data/tests/test_error_handler.rb +38 -0
  126. data/tests/test_exc.rb +37 -0
  127. data/tests/test_file_watch.rb +86 -0
  128. data/tests/test_fork.rb +75 -0
  129. data/tests/test_futures.rb +170 -0
  130. data/tests/test_handler_check.rb +35 -0
  131. data/tests/test_hc.rb +155 -0
  132. data/tests/test_httpclient.rb +238 -0
  133. data/tests/test_httpclient2.rb +132 -0
  134. data/tests/test_idle_connection.rb +31 -0
  135. data/tests/test_inactivity_timeout.rb +102 -0
  136. data/tests/test_io_streamer.rb +47 -0
  137. data/tests/test_ipv4.rb +96 -0
  138. data/tests/test_ipv6.rb +107 -0
  139. data/tests/test_iterator.rb +122 -0
  140. data/tests/test_kb.rb +28 -0
  141. data/tests/test_keepalive.rb +113 -0
  142. data/tests/test_line_protocol.rb +33 -0
  143. data/tests/test_ltp.rb +155 -0
  144. data/tests/test_ltp2.rb +332 -0
  145. data/tests/test_many_fds.rb +21 -0
  146. data/tests/test_next_tick.rb +104 -0
  147. data/tests/test_object_protocol.rb +36 -0
  148. data/tests/test_pause.rb +109 -0
  149. data/tests/test_pending_connect_timeout.rb +52 -0
  150. data/tests/test_pool.rb +196 -0
  151. data/tests/test_process_watch.rb +50 -0
  152. data/tests/test_processes.rb +128 -0
  153. data/tests/test_proxy_connection.rb +180 -0
  154. data/tests/test_pure.rb +156 -0
  155. data/tests/test_queue.rb +64 -0
  156. data/tests/test_resolver.rb +129 -0
  157. data/tests/test_running.rb +14 -0
  158. data/tests/test_sasl.rb +46 -0
  159. data/tests/test_send_file.rb +217 -0
  160. data/tests/test_servers.rb +32 -0
  161. data/tests/test_shutdown_hooks.rb +23 -0
  162. data/tests/test_smtpclient.rb +75 -0
  163. data/tests/test_smtpserver.rb +90 -0
  164. data/tests/test_sock_opt.rb +53 -0
  165. data/tests/test_spawn.rb +290 -0
  166. data/tests/test_ssl_args.rb +41 -0
  167. data/tests/test_ssl_dhparam.rb +57 -0
  168. data/tests/test_ssl_ecdh_curve.rb +57 -0
  169. data/tests/test_ssl_extensions.rb +24 -0
  170. data/tests/test_ssl_methods.rb +31 -0
  171. data/tests/test_ssl_protocols.rb +190 -0
  172. data/tests/test_ssl_verify.rb +52 -0
  173. data/tests/test_stomp.rb +38 -0
  174. data/tests/test_system.rb +46 -0
  175. data/tests/test_threaded_resource.rb +68 -0
  176. data/tests/test_tick_loop.rb +58 -0
  177. data/tests/test_timers.rb +150 -0
  178. data/tests/test_ud.rb +8 -0
  179. data/tests/test_unbind_reason.rb +40 -0
  180. metadata +384 -0
@@ -0,0 +1,303 @@
1
+ #--
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
+ module EventMachine
28
+ module Protocols
29
+
30
+ #
31
+ # @example
32
+ #
33
+ # EventMachine.run {
34
+ # http = EventMachine::Protocols::HttpClient.request(
35
+ # :host => server,
36
+ # :port => 80,
37
+ # :request => "/index.html",
38
+ # :query_string => "parm1=value1&parm2=value2"
39
+ # )
40
+ # http.callback {|response|
41
+ # puts response[:status]
42
+ # puts response[:headers]
43
+ # puts response[:content]
44
+ # }
45
+ # }
46
+ #--
47
+ # @todo Add streaming so we can support enormous POSTs. Current max is 20meg.
48
+ # Timeout for connections that run too long or hang somewhere in the middle.
49
+ # Persistent connections (HTTP/1.1), may need a associated delegate object.
50
+ # DNS: Some way to cache DNS lookups for hostnames we connect to. Ruby's
51
+ # DNS lookups are unbelievably slow.
52
+ # HEAD requests.
53
+ # Convenience methods for requests. get, post, url, etc.
54
+ # SSL.
55
+ # Handle status codes like 304, 100, etc.
56
+ # Refactor this code so that protocol errors all get handled one way (an exception?),
57
+ # instead of sprinkling set_deferred_status :failed calls everywhere.
58
+ #
59
+ # @deprecated Please use [EM-HTTP-Request](https://github.com/igrigorik/em-http-request) instead.
60
+ #
61
+ class HttpClient < Connection
62
+ include EventMachine::Deferrable
63
+
64
+ MaxPostContentLength = 20 * 1024 * 1024
65
+
66
+ def initialize
67
+ warn "HttpClient is deprecated and will be removed. EM-Http-Request should be used instead."
68
+ @connected = false
69
+ end
70
+
71
+ # @param args [Hash] The request arguments
72
+ # @option args [String] :host The host IP/DNS name
73
+ # @option args [Integer] :port The port to connect too
74
+ # @option args [String] :verb The request type [GET | POST | DELETE | PUT]
75
+ # @option args [String] :request The request path
76
+ # @option args [Hash] :basic_auth The basic auth credentials (:username and :password)
77
+ # @option args [String] :content The request content
78
+ # @option args [String] :contenttype The content type (e.g. text/plain)
79
+ # @option args [String] :query_string The query string
80
+ # @option args [String] :host_header The host header to set
81
+ # @option args [String] :cookie Cookies to set
82
+ def self.request( args = {} )
83
+ args[:port] ||= 80
84
+ EventMachine.connect( args[:host], args[:port], self ) {|c|
85
+ # According to the docs, we will get here AFTER post_init is called.
86
+ c.instance_eval {@args = args}
87
+ }
88
+ end
89
+
90
+ def post_init
91
+ @start_time = Time.now
92
+ @data = ""
93
+ @read_state = :base
94
+ end
95
+
96
+ # We send the request when we get a connection.
97
+ # AND, we set an instance variable to indicate we passed through here.
98
+ # That allows #unbind to know whether there was a successful connection.
99
+ # NB: This naive technique won't work when we have to support multiple
100
+ # requests on a single connection.
101
+ def connection_completed
102
+ @connected = true
103
+ send_request @args
104
+ end
105
+
106
+ def send_request args
107
+ args[:verb] ||= args[:method] # Support :method as an alternative to :verb.
108
+ args[:verb] ||= :get # IS THIS A GOOD IDEA, to default to GET if nothing was specified?
109
+
110
+ verb = args[:verb].to_s.upcase
111
+ unless ["GET", "POST", "PUT", "DELETE", "HEAD"].include?(verb)
112
+ set_deferred_status :failed, {:status => 0} # TODO, not signalling the error type
113
+ return # NOTE THE EARLY RETURN, we're not sending any data.
114
+ end
115
+
116
+ request = args[:request] || "/"
117
+ unless request[0,1] == "/"
118
+ request = "/" + request
119
+ end
120
+
121
+ qs = args[:query_string] || ""
122
+ if qs.length > 0 and qs[0,1] != '?'
123
+ qs = "?" + qs
124
+ end
125
+
126
+ version = args[:version] || "1.1"
127
+
128
+ # Allow an override for the host header if it's not the connect-string.
129
+ host = args[:host_header] || args[:host] || "_"
130
+ # For now, ALWAYS tuck in the port string, although we may want to omit it if it's the default.
131
+ port = args[:port].to_i != 80 ? ":#{args[:port]}" : ""
132
+
133
+ # POST items.
134
+ postcontenttype = args[:contenttype] || "application/octet-stream"
135
+ postcontent = args[:content] || ""
136
+ raise "oversized content in HTTP POST" if postcontent.length > MaxPostContentLength
137
+
138
+ # ESSENTIAL for the request's line-endings to be CRLF, not LF. Some servers misbehave otherwise.
139
+ # TODO: We ASSUME the caller wants to send a 1.1 request. May not be a good assumption.
140
+ req = [
141
+ "#{verb} #{request}#{qs} HTTP/#{version}",
142
+ "Host: #{host}#{port}",
143
+ "User-agent: Ruby EventMachine",
144
+ ]
145
+
146
+ if verb == "POST" || verb == "PUT"
147
+ req << "Content-type: #{postcontenttype}"
148
+ req << "Content-length: #{postcontent.length}"
149
+ end
150
+
151
+ # TODO, this cookie handler assumes it's getting a single, semicolon-delimited string.
152
+ # Eventually we will want to deal intelligently with arrays and hashes.
153
+ if args[:cookie]
154
+ req << "Cookie: #{args[:cookie]}"
155
+ end
156
+
157
+ # Allow custom HTTP headers, e.g. SOAPAction
158
+ args[:custom_headers].each do |k,v|
159
+ req << "#{k}: #{v}"
160
+ end if args[:custom_headers]
161
+
162
+ # Basic-auth stanza contributed by Matt Murphy.
163
+ if args[:basic_auth]
164
+ basic_auth_string = ["#{args[:basic_auth][:username]}:#{args[:basic_auth][:password]}"].pack('m').strip.gsub(/\n/,'')
165
+ req << "Authorization: Basic #{basic_auth_string}"
166
+ end
167
+
168
+ req << ""
169
+ reqstring = req.map {|l| "#{l}\r\n"}.join
170
+ send_data reqstring
171
+
172
+ if verb == "POST" || verb == "PUT"
173
+ send_data postcontent
174
+ end
175
+ end
176
+
177
+
178
+ def receive_data data
179
+ while data and data.length > 0
180
+ case @read_state
181
+ when :base
182
+ # Perform any per-request initialization here and don't consume any data.
183
+ @data = ""
184
+ @headers = []
185
+ @content_length = nil # not zero
186
+ @content = ""
187
+ @status = nil
188
+ @chunked = false
189
+ @chunk_length = nil
190
+ @read_state = :header
191
+ @connection_close = nil
192
+ when :header
193
+ ary = data.split( /\r?\n/m, 2 )
194
+ if ary.length == 2
195
+ data = ary.last
196
+ if ary.first == ""
197
+ if (@content_length and @content_length > 0) || @chunked || @connection_close
198
+ @read_state = :content
199
+ else
200
+ dispatch_response
201
+ @read_state = :base
202
+ end
203
+ else
204
+ @headers << ary.first
205
+ if @headers.length == 1
206
+ parse_response_line
207
+ elsif ary.first =~ /\Acontent-length:\s*/i
208
+ # Only take the FIRST content-length header that appears,
209
+ # which we can distinguish because @content_length is nil.
210
+ # TODO, it's actually a fatal error if there is more than one
211
+ # content-length header, because the caller is presumptively
212
+ # a bad guy. (There is an exploit that depends on multiple
213
+ # content-length headers.)
214
+ @content_length ||= $'.to_i
215
+ elsif ary.first =~ /\Aconnection:\s*close/i
216
+ @connection_close = true
217
+ elsif ary.first =~ /\Atransfer-encoding:\s*chunked/i
218
+ @chunked = true
219
+ end
220
+ end
221
+ else
222
+ @data << data
223
+ data = ""
224
+ end
225
+ when :content
226
+ if @chunked && @chunk_length
227
+ bytes_needed = @chunk_length - @chunk_read
228
+ new_data = data[0, bytes_needed]
229
+ @chunk_read += new_data.length
230
+ @content += new_data
231
+ data = data[bytes_needed..-1] || ""
232
+ if @chunk_length == @chunk_read && data[0,2] == "\r\n"
233
+ @chunk_length = nil
234
+ data = data[2..-1]
235
+ end
236
+ elsif @chunked
237
+ if (m = data.match(/\A(\S*)\r\n/m))
238
+ data = data[m[0].length..-1]
239
+ @chunk_length = m[1].to_i(16)
240
+ @chunk_read = 0
241
+ if @chunk_length == 0
242
+ dispatch_response
243
+ @read_state = :base
244
+ end
245
+ end
246
+ elsif @content_length
247
+ # If there was no content-length header, we have to wait until the connection
248
+ # closes. Everything we get until that point is content.
249
+ # TODO: Must impose a content-size limit, and also must implement chunking.
250
+ # Also, must support either temporary files for large content, or calling
251
+ # a content-consumer block supplied by the user.
252
+ bytes_needed = @content_length - @content.length
253
+ @content += data[0, bytes_needed]
254
+ data = data[bytes_needed..-1] || ""
255
+ if @content_length == @content.length
256
+ dispatch_response
257
+ @read_state = :base
258
+ end
259
+ else
260
+ @content << data
261
+ data = ""
262
+ end
263
+ end
264
+ end
265
+ end
266
+
267
+
268
+ # We get called here when we have received an HTTP response line.
269
+ # It's an opportunity to throw an exception or trigger other exceptional
270
+ # handling.
271
+ def parse_response_line
272
+ if @headers.first =~ /\AHTTP\/1\.[01] ([\d]{3})/
273
+ @status = $1.to_i
274
+ else
275
+ set_deferred_status :failed, {
276
+ :status => 0 # crappy way of signifying an unrecognized response. TODO, find a better way to do this.
277
+ }
278
+ close_connection
279
+ end
280
+ end
281
+ private :parse_response_line
282
+
283
+ def dispatch_response
284
+ @read_state = :base
285
+ set_deferred_status :succeeded, {
286
+ :content => @content,
287
+ :headers => @headers,
288
+ :status => @status
289
+ }
290
+ # TODO, we close the connection for now, but this is wrong for persistent clients.
291
+ close_connection
292
+ end
293
+
294
+ def unbind
295
+ if !@connected
296
+ set_deferred_status :failed, {:status => 0} # YECCCCH. Find a better way to signal no-connect/network error.
297
+ elsif (@read_state == :content and @content_length == nil)
298
+ dispatch_response
299
+ end
300
+ end
301
+ end
302
+ end
303
+ end
@@ -0,0 +1,602 @@
1
+ #--
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
+ module EventMachine
27
+ module Protocols
28
+
29
+ # ### Usage
30
+ #
31
+ # ```ruby
32
+ # EM.run{
33
+ # conn = EM::Protocols::HttpClient2.connect 'google.com', 80
34
+ #
35
+ # req = conn.get('/')
36
+ # req.callback{ |response|
37
+ # p(response.status)
38
+ # p(response.headers)
39
+ # p(response.content)
40
+ # }
41
+ # }
42
+ # ```
43
+ #
44
+ # @deprecated Please use [EM-HTTP-Request](https://github.com/igrigorik/em-http-request) instead.
45
+ #
46
+ class HttpClient2 < Connection
47
+ include LineText2
48
+
49
+ def initialize
50
+ warn "HttpClient2 is deprecated and will be removed. EM-Http-Request should be used instead."
51
+
52
+ @authorization = nil
53
+ @closed = nil
54
+ @requests = nil
55
+ end
56
+
57
+ # @private
58
+ class Request
59
+ include Deferrable
60
+
61
+ attr_reader :version
62
+ attr_reader :status
63
+ attr_reader :header_lines
64
+ attr_reader :headers
65
+ attr_reader :content
66
+ attr_reader :internal_error
67
+
68
+ def initialize conn, args
69
+ @conn = conn
70
+ @args = args
71
+ @header_lines = []
72
+ @headers = {}
73
+ @blanks = 0
74
+ @chunk_trailer = nil
75
+ @chunking = nil
76
+ end
77
+
78
+ def send_request
79
+ az = @args[:authorization] and az = "Authorization: #{az}\r\n"
80
+
81
+ r = [
82
+ "#{@args[:verb]} #{@args[:uri]} HTTP/#{@args[:version] || "1.1"}\r\n",
83
+ "Host: #{@args[:host_header] || "_"}\r\n",
84
+ az || "",
85
+ "\r\n"
86
+ ]
87
+ @conn.send_data r.join
88
+ end
89
+
90
+ #--
91
+ #
92
+ def receive_line ln
93
+ if @chunk_trailer
94
+ receive_chunk_trailer(ln)
95
+ elsif @chunking
96
+ receive_chunk_header(ln)
97
+ else
98
+ receive_header_line(ln)
99
+ end
100
+ end
101
+
102
+ #--
103
+ #
104
+ def receive_chunk_trailer ln
105
+ if ln.length == 0
106
+ @conn.pop_request
107
+ succeed(self)
108
+ else
109
+ p "Received chunk trailer line"
110
+ end
111
+ end
112
+
113
+ #--
114
+ # Allow up to ten blank lines before we get a real response line.
115
+ # Allow no more than 100 lines in the header.
116
+ #
117
+ def receive_header_line ln
118
+ if ln.length == 0
119
+ if @header_lines.length > 0
120
+ process_header
121
+ else
122
+ @blanks += 1
123
+ if @blanks > 10
124
+ @conn.close_connection
125
+ end
126
+ end
127
+ else
128
+ @header_lines << ln
129
+ if @header_lines.length > 100
130
+ @internal_error = :bad_header
131
+ @conn.close_connection
132
+ end
133
+ end
134
+ end
135
+
136
+ #--
137
+ # Cf RFC 2616 pgh 3.6.1 for the format of HTTP chunks.
138
+ #
139
+ def receive_chunk_header ln
140
+ if ln.length > 0
141
+ chunksize = ln.to_i(16)
142
+ if chunksize > 0
143
+ @conn.set_text_mode(ln.to_i(16))
144
+ else
145
+ @content = @content ? @content.join : ''
146
+ @chunk_trailer = true
147
+ end
148
+ else
149
+ # We correctly come here after each chunk gets read.
150
+ # p "Got A BLANK chunk line"
151
+ end
152
+
153
+ end
154
+
155
+
156
+ #--
157
+ # We get a single chunk. Append it to the incoming content and switch back to line mode.
158
+ #
159
+ def receive_chunked_text text
160
+ # p "RECEIVED #{text.length} CHUNK"
161
+ (@content ||= []) << text
162
+ end
163
+
164
+
165
+ #--
166
+ # TODO, inefficient how we're handling this. Part of it is done so as to
167
+ # make sure we don't have problems in detecting chunked-encoding, content-length,
168
+ # etc.
169
+ #
170
+ HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i
171
+ ClenRE = /\AContent-length:\s*(\d+)/i
172
+ ChunkedRE = /\ATransfer-encoding:\s*chunked/i
173
+ ColonRE = /\:\s*/
174
+
175
+ def process_header
176
+ unless @header_lines.first =~ HttpResponseRE
177
+ @conn.close_connection
178
+ @internal_error = :bad_request
179
+ end
180
+ @version = $1.dup
181
+ @status = $2.dup.to_i
182
+
183
+ clen = nil
184
+ chunks = nil
185
+ @header_lines.each_with_index do |e,ix|
186
+ if ix > 0
187
+ hdr,val = e.split(ColonRE,2)
188
+ (@headers[hdr.downcase] ||= []) << val
189
+ end
190
+
191
+ if clen == nil and e =~ ClenRE
192
+ clen = $1.dup.to_i
193
+ end
194
+ if e =~ ChunkedRE
195
+ chunks = true
196
+ end
197
+ end
198
+
199
+ if clen
200
+ # If the content length is zero we should not call set_text_mode,
201
+ # because a value of zero will make it wait forever, hanging the
202
+ # connection. Just return success instead, with empty content.
203
+ if clen == 0 then
204
+ @content = ""
205
+ @conn.pop_request
206
+ succeed(self)
207
+ else
208
+ @conn.set_text_mode clen
209
+ end
210
+ elsif chunks
211
+ @chunking = true
212
+ else
213
+ # Chunked transfer, multipart, or end-of-connection.
214
+ # For end-of-connection, we need to go the unbind
215
+ # method and suppress its desire to fail us.
216
+ p "NO CLEN"
217
+ p @args[:uri]
218
+ p @header_lines
219
+ @internal_error = :unsupported_clen
220
+ @conn.close_connection
221
+ end
222
+ end
223
+ private :process_header
224
+
225
+
226
+ def receive_text text
227
+ @chunking ? receive_chunked_text(text) : receive_sized_text(text)
228
+ end
229
+
230
+ #--
231
+ # At the present time, we only handle contents that have a length
232
+ # specified by the content-length header.
233
+ #
234
+ def receive_sized_text text
235
+ @content = text
236
+ @conn.pop_request
237
+ succeed(self)
238
+ end
239
+ end
240
+
241
+ # Make a connection to a remote HTTP server.
242
+ # Can take either a pair of arguments (which will be interpreted as
243
+ # a hostname/ip-address and a port), or a hash.
244
+ # If the arguments are a hash, then supported values include:
245
+ # :host => a hostname or ip-address
246
+ # :port => a port number
247
+ # :ssl => true to enable ssl
248
+ def self.connect *args
249
+ if args.length == 2
250
+ args = {:host=>args[0], :port=>args[1]}
251
+ else
252
+ args = args.first
253
+ end
254
+
255
+ h,prt,ssl = args[:host], Integer(args[:port]), (args[:tls] || args[:ssl])
256
+ conn = EM.connect( h, prt, self )
257
+ conn.start_tls if ssl
258
+ conn.set_default_host_header( h, prt, ssl )
259
+ conn
260
+ end
261
+
262
+ # Get a url
263
+ #
264
+ # req = conn.get(:uri => '/')
265
+ # req.callback{|response| puts response.content }
266
+ #
267
+ def get args
268
+ if args.is_a?(String)
269
+ args = {:uri=>args}
270
+ end
271
+ args[:verb] = "GET"
272
+ request args
273
+ end
274
+
275
+ # Post to a url
276
+ #
277
+ # req = conn.post('/data')
278
+ # req.callback{|response| puts response.content }
279
+ #--
280
+ # XXX there's no way to supply a POST body.. wtf?
281
+ def post args
282
+ if args.is_a?(String)
283
+ args = {:uri=>args}
284
+ end
285
+ args[:verb] = "POST"
286
+ request args
287
+ end
288
+
289
+
290
+ #--
291
+ # Compute and remember a string to be used as the host header in HTTP requests
292
+ # unless the user overrides it with an argument to #request.
293
+ #
294
+ # @private
295
+ def set_default_host_header host, port, ssl
296
+ if (ssl and port != 443) or (!ssl and port != 80)
297
+ @host_header = "#{host}:#{port}"
298
+ else
299
+ @host_header = host
300
+ end
301
+ end
302
+
303
+
304
+ # @private
305
+ def post_init
306
+ super
307
+ @connected = EM::DefaultDeferrable.new
308
+ end
309
+
310
+ # @private
311
+ def connection_completed
312
+ super
313
+ @connected.succeed
314
+ end
315
+
316
+ #--
317
+ # All pending requests, if any, must fail.
318
+ # We might come here without ever passing through connection_completed
319
+ # in case we can't connect to the server. We'll also get here when the
320
+ # connection closes (either because the server closes it, or we close it
321
+ # due to detecting an internal error or security violation).
322
+ # In either case, run down all pending requests, if any, and signal failure
323
+ # on them.
324
+ #
325
+ # Set and remember a flag (@closed) so we can immediately fail any
326
+ # subsequent requests.
327
+ #
328
+ # @private
329
+ def unbind
330
+ super
331
+ @closed = true
332
+ (@requests || []).each {|r| r.fail}
333
+ end
334
+
335
+ # @private
336
+ def request args
337
+ args[:host_header] = @host_header unless args.has_key?(:host_header)
338
+ args[:authorization] = @authorization unless args.has_key?(:authorization)
339
+ r = Request.new self, args
340
+ if @closed
341
+ r.fail
342
+ else
343
+ (@requests ||= []).unshift r
344
+ @connected.callback {r.send_request}
345
+ end
346
+ r
347
+ end
348
+
349
+ # @private
350
+ def receive_line ln
351
+ if req = @requests.last
352
+ req.receive_line ln
353
+ else
354
+ p "??????????"
355
+ p ln
356
+ end
357
+ end
358
+
359
+ # @private
360
+ def receive_binary_data text
361
+ @requests.last.receive_text text
362
+ end
363
+
364
+ #--
365
+ # Called by a Request object when it completes.
366
+ #
367
+ # @private
368
+ def pop_request
369
+ @requests.pop
370
+ end
371
+ end
372
+
373
+
374
+ =begin
375
+ class HttpClient2x < Connection
376
+ include LineText2
377
+
378
+ # TODO: Make this behave appropriate in case a #connect fails.
379
+ # Currently, this produces no errors.
380
+
381
+ # Make a connection to a remote HTTP server.
382
+ # Can take either a pair of arguments (which will be interpreted as
383
+ # a hostname/ip-address and a port), or a hash.
384
+ # If the arguments are a hash, then supported values include:
385
+ # :host => a hostname or ip-address;
386
+ # :port => a port number
387
+ #--
388
+ # TODO, support optional encryption arguments like :ssl
389
+ def self.connect *args
390
+ if args.length == 2
391
+ args = {:host=>args[0], :port=>args[1]}
392
+ else
393
+ args = args.first
394
+ end
395
+
396
+ h,prt = args[:host],Integer(args[:port])
397
+ EM.connect( h, prt, self, h, prt )
398
+ end
399
+
400
+
401
+ #--
402
+ # Sugars a connection that makes a single request and then
403
+ # closes the connection. Matches the behavior and the arguments
404
+ # of the original implementation of class HttpClient.
405
+ #
406
+ # Intended primarily for back compatibility, but the idiom
407
+ # is probably useful so it's not deprecated.
408
+ # We return a Deferrable, as did the original implementation.
409
+ #
410
+ # Because we're improving the way we deal with errors and exceptions
411
+ # (specifically, HTTP response codes other than 2xx will trigger the
412
+ # errback rather than the callback), this may break some existing code.
413
+ #
414
+ def self.request args
415
+ c = connect args
416
+ end
417
+
418
+ #--
419
+ # Requests can be pipelined. When we get a request, add it to the
420
+ # front of a queue as an array. The last element of the @requests
421
+ # array is always the oldest request received. Each element of the
422
+ # @requests array is a two-element array consisting of a hash with
423
+ # the original caller's arguments, and an initially-empty Ostruct
424
+ # containing the data we retrieve from the server's response.
425
+ # Maintain the instance variable @current_response, which is the response
426
+ # of the oldest pending request. That's just to make other code a little
427
+ # easier. If the variable doesn't exist when we come here, we're
428
+ # obviously the first request being made on the connection.
429
+ #
430
+ # The reason for keeping this method private (and requiring use of the
431
+ # convenience methods #get, #post, #head, etc) is to avoid the small
432
+ # performance penalty of canonicalizing the verb.
433
+ #
434
+ def request args
435
+ d = EventMachine::DefaultDeferrable.new
436
+
437
+ if @closed
438
+ d.fail
439
+ return d
440
+ end
441
+
442
+ o = OpenStruct.new
443
+ o.deferrable = d
444
+ (@requests ||= []).unshift [args, o]
445
+ @current_response ||= @requests.last.last
446
+ @connected.callback {
447
+ az = args[:authorization] and az = "Authorization: #{az}\r\n"
448
+
449
+ r = [
450
+ "#{args[:verb]} #{args[:uri]} HTTP/#{args[:version] || "1.1"}\r\n",
451
+ "Host: #{args[:host_header] || @host_header}\r\n",
452
+ az || "",
453
+ "\r\n"
454
+ ]
455
+ p r
456
+ send_data r.join
457
+ }
458
+ o.deferrable
459
+ end
460
+ private :request
461
+
462
+ def get args
463
+ if args.is_a?(String)
464
+ args = {:uri=>args}
465
+ end
466
+ args[:verb] = "GET"
467
+ request args
468
+ end
469
+
470
+ def initialize host, port
471
+ super
472
+ @host_header = "#{host}:#{port}"
473
+ end
474
+ def post_init
475
+ super
476
+ @connected = EM::DefaultDeferrable.new
477
+ end
478
+
479
+
480
+ def connection_completed
481
+ super
482
+ @connected.succeed
483
+ end
484
+
485
+ #--
486
+ # Make sure to throw away any leftover incoming data if we've
487
+ # been closed due to recognizing an error.
488
+ #
489
+ # Generate an internal error if we get an unreasonable number of
490
+ # header lines. It could be malicious.
491
+ #
492
+ def receive_line ln
493
+ p ln
494
+ return if @closed
495
+
496
+ if ln.length > 0
497
+ (@current_response.headers ||= []).push ln
498
+ abort_connection if @current_response.headers.length > 100
499
+ else
500
+ process_received_headers
501
+ end
502
+ end
503
+
504
+ #--
505
+ # We come here when we've seen all the headers for a particular request.
506
+ # What we do next depends on the response line (which should be the
507
+ # first line in the header set), and whether there is content to read.
508
+ # We may transition into a text-reading state to read content, or
509
+ # we may abort the connection, or we may go right back into parsing
510
+ # responses for the next response in the chain.
511
+ #
512
+ # We make an ASSUMPTION that the first line is an HTTP response.
513
+ # Anything else produces an error that aborts the connection.
514
+ # This may not be enough, because it may be that responses to pipelined
515
+ # requests will come with a blank-line delimiter.
516
+ #
517
+ # Any non-2xx response will be treated as a fatal error, and abort the
518
+ # connection. We will set up the status and other response parameters.
519
+ # TODO: we will want to properly support 1xx responses, which some versions
520
+ # of IIS copiously generate.
521
+ # TODO: We need to give the option of not aborting the connection with certain
522
+ # non-200 responses, in order to work with NTLM and other authentication
523
+ # schemes that work at the level of individual connections.
524
+ #
525
+ # Some error responses will get sugarings. For example, we'll return the
526
+ # Location header in the response in case of a 301/302 response.
527
+ #
528
+ # Possible dispositions here:
529
+ # 1) No content to read (either content-length is zero or it's a HEAD request);
530
+ # 2) Switch to text mode to read a specific number of bytes;
531
+ # 3) Read a chunked or multipart response;
532
+ # 4) Read till the server closes the connection.
533
+ #
534
+ # Our reponse to the client can be either to wait till all the content
535
+ # has been read and then to signal caller's deferrable, or else to signal
536
+ # it when we finish the processing the headers and then expect the caller
537
+ # to have given us a block to call as the content comes in. And of course
538
+ # the latter gets stickier with chunks and multiparts.
539
+ #
540
+ HttpResponseRE = /\AHTTP\/(1.[01]) ([\d]{3})/i
541
+ ClenRE = /\AContent-length:\s*(\d+)/i
542
+ def process_received_headers
543
+ abort_connection unless @current_response.headers.first =~ HttpResponseRE
544
+ @current_response.version = $1.dup
545
+ st = $2.dup
546
+ @current_response.status = st.to_i
547
+ abort_connection unless st[0,1] == "2"
548
+
549
+ clen = nil
550
+ @current_response.headers.each do |e|
551
+ if clen == nil and e =~ ClenRE
552
+ clen = $1.dup.to_i
553
+ end
554
+ end
555
+
556
+ if clen
557
+ set_text_mode clen
558
+ end
559
+ end
560
+ private :process_received_headers
561
+
562
+
563
+ def receive_binary_data text
564
+ @current_response.content = text
565
+ @current_response.deferrable.succeed @current_response
566
+ @requests.pop
567
+ @current_response = (@requests.last || []).last
568
+ set_line_mode
569
+ end
570
+
571
+
572
+
573
+ # We've received either a server error or an internal error.
574
+ # Close the connection and abort any pending requests.
575
+ #--
576
+ # When should we call close_connection? It will cause #unbind
577
+ # to be fired. Should the user expect to see #unbind before
578
+ # we call #receive_http_error, or the other way around?
579
+ #
580
+ # Set instance variable @closed. That's used to inhibit further
581
+ # processing of any inbound data after an error has been recognized.
582
+ #
583
+ # We shouldn't have to worry about any leftover outbound data,
584
+ # because we call close_connection (not close_connection_after_writing).
585
+ # That ensures that any pipelined requests received after an error
586
+ # DO NOT get streamed out to the server on this connection.
587
+ # Very important. TODO, write a unit-test to establish that behavior.
588
+ #
589
+ def abort_connection
590
+ close_connection
591
+ @closed = true
592
+ @current_response.deferrable.fail( @current_response )
593
+ end
594
+
595
+
596
+ #------------------------
597
+ # Below here are user-overridable methods.
598
+
599
+ end
600
+ =end
601
+ end
602
+ end