eventmachine 0.12.0-i386-mswin32

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