careo-eventmachine 0.12.5.1

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