eventmachine 0.12.6-x86-mswin32-60 → 0.12.8-x86-mswin32-60

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