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