eventmachine-eventmachine 0.12.3

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 (121) hide show
  1. data/Rakefile +169 -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 +530 -0
  20. data/ext/cplusplus.cpp +172 -0
  21. data/ext/ed.cpp +1473 -0
  22. data/ext/ed.h +361 -0
  23. data/ext/em.cpp +1895 -0
  24. data/ext/em.h +170 -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 +90 -0
  30. data/ext/eventmachine_cpp.h +94 -0
  31. data/ext/extconf.rb +150 -0
  32. data/ext/files.cpp +94 -0
  33. data/ext/files.h +65 -0
  34. data/ext/kb.cpp +368 -0
  35. data/ext/page.cpp +107 -0
  36. data/ext/page.h +51 -0
  37. data/ext/pipe.cpp +327 -0
  38. data/ext/project.h +119 -0
  39. data/ext/rubymain.cpp +683 -0
  40. data/ext/sigs.cpp +89 -0
  41. data/ext/sigs.h +32 -0
  42. data/ext/ssl.cpp +408 -0
  43. data/ext/ssl.h +86 -0
  44. data/java/src/com/rubyeventmachine/Application.java +196 -0
  45. data/java/src/com/rubyeventmachine/Connection.java +74 -0
  46. data/java/src/com/rubyeventmachine/ConnectionFactory.java +37 -0
  47. data/java/src/com/rubyeventmachine/DefaultConnectionFactory.java +46 -0
  48. data/java/src/com/rubyeventmachine/EmReactor.java +408 -0
  49. data/java/src/com/rubyeventmachine/EmReactorException.java +40 -0
  50. data/java/src/com/rubyeventmachine/EventableChannel.java +57 -0
  51. data/java/src/com/rubyeventmachine/EventableDatagramChannel.java +171 -0
  52. data/java/src/com/rubyeventmachine/EventableSocketChannel.java +244 -0
  53. data/java/src/com/rubyeventmachine/PeriodicTimer.java +38 -0
  54. data/java/src/com/rubyeventmachine/Timer.java +54 -0
  55. data/java/src/com/rubyeventmachine/tests/ApplicationTest.java +108 -0
  56. data/java/src/com/rubyeventmachine/tests/ConnectTest.java +124 -0
  57. data/java/src/com/rubyeventmachine/tests/EMTest.java +80 -0
  58. data/java/src/com/rubyeventmachine/tests/TestDatagrams.java +53 -0
  59. data/java/src/com/rubyeventmachine/tests/TestServers.java +74 -0
  60. data/java/src/com/rubyeventmachine/tests/TestTimers.java +89 -0
  61. data/lib/em/deferrable.rb +208 -0
  62. data/lib/em/eventable.rb +39 -0
  63. data/lib/em/future.rb +62 -0
  64. data/lib/em/messages.rb +66 -0
  65. data/lib/em/processes.rb +68 -0
  66. data/lib/em/spawnable.rb +88 -0
  67. data/lib/em/streamer.rb +112 -0
  68. data/lib/eventmachine.rb +1763 -0
  69. data/lib/eventmachine_version.rb +31 -0
  70. data/lib/evma.rb +32 -0
  71. data/lib/evma/callback.rb +32 -0
  72. data/lib/evma/container.rb +75 -0
  73. data/lib/evma/factory.rb +77 -0
  74. data/lib/evma/protocol.rb +87 -0
  75. data/lib/evma/reactor.rb +48 -0
  76. data/lib/jeventmachine.rb +137 -0
  77. data/lib/pr_eventmachine.rb +1011 -0
  78. data/lib/protocols/buftok.rb +127 -0
  79. data/lib/protocols/header_and_content.rb +129 -0
  80. data/lib/protocols/httpcli2.rb +794 -0
  81. data/lib/protocols/httpclient.rb +270 -0
  82. data/lib/protocols/line_and_text.rb +122 -0
  83. data/lib/protocols/linetext2.rb +163 -0
  84. data/lib/protocols/postgres.rb +261 -0
  85. data/lib/protocols/saslauth.rb +179 -0
  86. data/lib/protocols/smtpclient.rb +308 -0
  87. data/lib/protocols/smtpserver.rb +556 -0
  88. data/lib/protocols/stomp.rb +130 -0
  89. data/lib/protocols/tcptest.rb +57 -0
  90. data/tasks/cpp.rake +77 -0
  91. data/tasks/project.rake +78 -0
  92. data/tasks/tests.rake +192 -0
  93. data/tests/test_attach.rb +66 -0
  94. data/tests/test_basic.rb +231 -0
  95. data/tests/test_defer.rb +47 -0
  96. data/tests/test_epoll.rb +161 -0
  97. data/tests/test_errors.rb +82 -0
  98. data/tests/test_eventables.rb +78 -0
  99. data/tests/test_exc.rb +58 -0
  100. data/tests/test_futures.rb +214 -0
  101. data/tests/test_hc.rb +218 -0
  102. data/tests/test_httpclient.rb +215 -0
  103. data/tests/test_httpclient2.rb +133 -0
  104. data/tests/test_kb.rb +61 -0
  105. data/tests/test_ltp.rb +192 -0
  106. data/tests/test_ltp2.rb +320 -0
  107. data/tests/test_next_tick.rb +102 -0
  108. data/tests/test_processes.rb +56 -0
  109. data/tests/test_pure.rb +129 -0
  110. data/tests/test_running.rb +47 -0
  111. data/tests/test_sasl.rb +74 -0
  112. data/tests/test_send_file.rb +245 -0
  113. data/tests/test_servers.rb +80 -0
  114. data/tests/test_smtpclient.rb +81 -0
  115. data/tests/test_smtpserver.rb +93 -0
  116. data/tests/test_spawn.rb +329 -0
  117. data/tests/test_ssl_args.rb +68 -0
  118. data/tests/test_timers.rb +146 -0
  119. data/tests/test_ud.rb +43 -0
  120. data/tests/testem.rb +31 -0
  121. metadata +197 -0
@@ -0,0 +1,270 @@
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
+ class HttpClient < Connection
32
+ include EventMachine::Deferrable
33
+
34
+
35
+ MaxPostContentLength = 20 * 1024 * 1024
36
+
37
+ # USAGE SAMPLE:
38
+ #
39
+ # EventMachine.run {
40
+ # http = EventMachine::Protocols::HttpClient.request(
41
+ # :host => server,
42
+ # :port => 80,
43
+ # :request => "/index.html",
44
+ # :query_string => "parm1=value1&parm2=value2"
45
+ # )
46
+ # http.callback {|response|
47
+ # puts response[:status]
48
+ # puts response[:headers]
49
+ # puts response[:content]
50
+ # }
51
+ # }
52
+ #
53
+
54
+ # TODO:
55
+ # Add streaming so we can support enormous POSTs. Current max is 20meg.
56
+ # Timeout for connections that run too long or hang somewhere in the middle.
57
+ # Persistent connections (HTTP/1.1), may need a associated delegate object.
58
+ # DNS: Some way to cache DNS lookups for hostnames we connect to. Ruby's
59
+ # DNS lookups are unbelievably slow.
60
+ # HEAD requests.
61
+ # Chunked transfer encoding.
62
+ # Convenience methods for requests. get, post, url, etc.
63
+ # SSL.
64
+ # Handle status codes like 304, 100, etc.
65
+ # Refactor this code so that protocol errors all get handled one way (an exception?),
66
+ # instead of sprinkling set_deferred_status :failed calls everywhere.
67
+
68
+ # === Arg list
69
+ # :host => 'ip/dns', :port => fixnum, :verb => 'GET', :request => 'path',
70
+ # :basic_auth => {:username => '', :password => ''}, :content => 'content',
71
+ # :contenttype => 'text/plain', :query_string => '', :host_header => '',
72
+ # :cookie => ''
73
+
74
+ def self.request( args = {} )
75
+ args[:port] ||= 80
76
+ EventMachine.connect( args[:host], args[:port], self ) {|c|
77
+ # According to the docs, we will get here AFTER post_init is called.
78
+ c.instance_eval {@args = args}
79
+ }
80
+ end
81
+
82
+ def post_init
83
+ @start_time = Time.now
84
+ @data = ""
85
+ @read_state = :base
86
+ end
87
+
88
+ # We send the request when we get a connection.
89
+ # AND, we set an instance variable to indicate we passed through here.
90
+ # That allows #unbind to know whether there was a successful connection.
91
+ # NB: This naive technique won't work when we have to support multiple
92
+ # requests on a single connection.
93
+ def connection_completed
94
+ @connected = true
95
+ send_request @args
96
+ end
97
+
98
+ def send_request args
99
+ args[:verb] ||= args[:method] # Support :method as an alternative to :verb.
100
+ args[:verb] ||= :get # IS THIS A GOOD IDEA, to default to GET if nothing was specified?
101
+
102
+ verb = args[:verb].to_s.upcase
103
+ unless ["GET", "POST", "PUT", "DELETE", "HEAD"].include?(verb)
104
+ set_deferred_status :failed, {:status => 0} # TODO, not signalling the error type
105
+ return # NOTE THE EARLY RETURN, we're not sending any data.
106
+ end
107
+
108
+ request = args[:request] || "/"
109
+ unless request[0,1] == "/"
110
+ request = "/" + request
111
+ end
112
+
113
+ qs = args[:query_string] || ""
114
+ if qs.length > 0 and qs[0,1] != '?'
115
+ qs = "?" + qs
116
+ end
117
+
118
+ version = args[:version] || "1.1"
119
+
120
+ # Allow an override for the host header if it's not the connect-string.
121
+ host = args[:host_header] || args[:host] || "_"
122
+ # For now, ALWAYS tuck in the port string, although we may want to omit it if it's the default.
123
+ port = args[:port]
124
+
125
+ # POST items.
126
+ postcontenttype = args[:contenttype] || "application/octet-stream"
127
+ postcontent = args[:content] || ""
128
+ raise "oversized content in HTTP POST" if postcontent.length > MaxPostContentLength
129
+
130
+ # ESSENTIAL for the request's line-endings to be CRLF, not LF. Some servers misbehave otherwise.
131
+ # TODO: We ASSUME the caller wants to send a 1.1 request. May not be a good assumption.
132
+ req = [
133
+ "#{verb} #{request}#{qs} HTTP/#{version}",
134
+ "Host: #{host}:#{port}",
135
+ "User-agent: Ruby EventMachine",
136
+ ]
137
+
138
+ if verb == "POST" || verb == "PUT"
139
+ req << "Content-type: #{postcontenttype}"
140
+ req << "Content-length: #{postcontent.length}"
141
+ end
142
+
143
+ # TODO, this cookie handler assumes it's getting a single, semicolon-delimited string.
144
+ # Eventually we will want to deal intelligently with arrays and hashes.
145
+ if args[:cookie]
146
+ req << "Cookie: #{args[:cookie]}"
147
+ end
148
+
149
+ # Basic-auth stanza contributed by Mike Murphy.
150
+ if args[:basic_auth]
151
+ basic_auth_string = ["#{args[:basic_auth][:username]}:#{args[:basic_auth][:password]}"].pack('m').strip
152
+ req << "Authorization: Basic #{basic_auth_string}"
153
+ end
154
+
155
+ req << ""
156
+ reqstring = req.map {|l| "#{l}\r\n"}.join
157
+ send_data reqstring
158
+
159
+ if verb == "POST" || verb == "PUT"
160
+ send_data postcontent
161
+ end
162
+ end
163
+
164
+
165
+ def receive_data data
166
+ while data and data.length > 0
167
+ case @read_state
168
+ when :base
169
+ # Perform any per-request initialization here and don't consume any data.
170
+ @data = ""
171
+ @headers = []
172
+ @content_length = nil # not zero
173
+ @content = ""
174
+ @status = nil
175
+ @read_state = :header
176
+ @connection_close = nil
177
+ when :header
178
+ ary = data.split( /\r?\n/m, 2 )
179
+ if ary.length == 2
180
+ data = ary.last
181
+ if ary.first == ""
182
+ if (@content_length and @content_length > 0) || @connection_close
183
+ @read_state = :content
184
+ else
185
+ dispatch_response
186
+ @read_state = :base
187
+ end
188
+ else
189
+ @headers << ary.first
190
+ if @headers.length == 1
191
+ parse_response_line
192
+ elsif ary.first =~ /\Acontent-length:\s*/i
193
+ # Only take the FIRST content-length header that appears,
194
+ # which we can distinguish because @content_length is nil.
195
+ # TODO, it's actually a fatal error if there is more than one
196
+ # content-length header, because the caller is presumptively
197
+ # a bad guy. (There is an exploit that depends on multiple
198
+ # content-length headers.)
199
+ @content_length ||= $'.to_i
200
+ elsif ary.first =~ /\Aconnection:\s*close/i
201
+ @connection_close = true
202
+ end
203
+ end
204
+ else
205
+ @data << data
206
+ data = ""
207
+ end
208
+ when :content
209
+ # If there was no content-length header, we have to wait until the connection
210
+ # closes. Everything we get until that point is content.
211
+ # TODO: Must impose a content-size limit, and also must implement chunking.
212
+ # Also, must support either temporary files for large content, or calling
213
+ # a content-consumer block supplied by the user.
214
+ if @content_length
215
+ bytes_needed = @content_length - @content.length
216
+ @content += data[0, bytes_needed]
217
+ data = data[bytes_needed..-1] || ""
218
+ if @content_length == @content.length
219
+ dispatch_response
220
+ @read_state = :base
221
+ end
222
+ else
223
+ @content << data
224
+ data = ""
225
+ end
226
+ end
227
+ end
228
+ end
229
+
230
+
231
+ # We get called here when we have received an HTTP response line.
232
+ # It's an opportunity to throw an exception or trigger other exceptional
233
+ # handling.
234
+ def parse_response_line
235
+ if @headers.first =~ /\AHTTP\/1\.[01] ([\d]{3})/
236
+ @status = $1.to_i
237
+ else
238
+ set_deferred_status :failed, {
239
+ :status => 0 # crappy way of signifying an unrecognized response. TODO, find a better way to do this.
240
+ }
241
+ close_connection
242
+ end
243
+ end
244
+ private :parse_response_line
245
+
246
+ def dispatch_response
247
+ @read_state = :base
248
+ set_deferred_status :succeeded, {
249
+ :content => @content,
250
+ :headers => @headers,
251
+ :status => @status
252
+ }
253
+ # TODO, we close the connection for now, but this is wrong for persistent clients.
254
+ close_connection
255
+ end
256
+
257
+ def unbind
258
+ if !@connected
259
+ set_deferred_status :failed, {:status => 0} # YECCCCH. Find a better way to signal no-connect/network error.
260
+ elsif (@read_state == :content and @content_length == nil)
261
+ dispatch_response
262
+ end
263
+ end
264
+ end
265
+
266
+
267
+ end
268
+ end
269
+
270
+
@@ -0,0 +1,122 @@
1
+ # $Id$
2
+ #
3
+ # Author:: Francis Cianfrocca (gmail: blackhedd)
4
+ # Homepage:: http://rubyeventmachine.com
5
+ # Date:: 15 November 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
+ require File.dirname(__FILE__) + '/buftok'
27
+
28
+ module EventMachine
29
+ module Protocols
30
+
31
+ class LineAndTextProtocol < Connection
32
+ MaxLineLength = 16*1024
33
+ MaxBinaryLength = 32*1024*1024
34
+
35
+ def initialize *args
36
+ super
37
+ lbp_init_line_state
38
+ end
39
+ def receive_data data
40
+ if @lbp_mode == :lines
41
+ begin
42
+ @lpb_buffer.extract(data).each do |line|
43
+ receive_line(line.chomp) if respond_to?(:receive_line)
44
+ end
45
+ rescue Exception
46
+ receive_error('overlength line') if respond_to?(:receive_error)
47
+ close_connection
48
+ return
49
+ end
50
+ else
51
+ if @lbp_binary_limit > 0
52
+ wanted = @lbp_binary_limit - @lbp_binary_bytes_received
53
+ chunk = nil
54
+ if data.length > wanted
55
+ chunk = data.slice!(0...wanted)
56
+ else
57
+ chunk = data
58
+ data = ""
59
+ end
60
+ @lbp_binary_buffer[@lbp_binary_bytes_received...(@lbp_binary_bytes_received+chunk.length)] = chunk
61
+ @lbp_binary_bytes_received += chunk.length
62
+ if @lbp_binary_bytes_received == @lbp_binary_limit
63
+ receive_binary_data(@lbp_binary_buffer) if respond_to?(:receive_binary_data)
64
+ lbp_init_line_state
65
+ end
66
+ receive_data(data) if data.length > 0
67
+ else
68
+ receive_binary_data(data) if respond_to?(:receive_binary_data)
69
+ data = ""
70
+ end
71
+ end
72
+ end
73
+
74
+ def unbind
75
+ if @lbp_mode == :binary and @lbp_binary_limit > 0
76
+ if respond_to?(:receive_binary_data)
77
+ receive_binary_data( @lbp_binary_buffer[0...@lbp_binary_bytes_received] )
78
+ end
79
+ end
80
+ end
81
+
82
+ # Set up to read the supplied number of binary bytes.
83
+ # This recycles all the data currently waiting in the line buffer, if any.
84
+ # If the limit is nil, then ALL subsequent data will be treated as binary
85
+ # data and passed to the upstream protocol handler as we receive it.
86
+ # If a limit is given, we'll hold the incoming binary data and not
87
+ # pass it upstream until we've seen it all, or until there is an unbind
88
+ # (in which case we'll pass up a partial).
89
+ # Specifying nil for the limit (the default) means there is no limit.
90
+ # Specifiyng zero for the limit will cause an immediate transition back to line mode.
91
+ #
92
+ def set_binary_mode size = nil
93
+ if @lbp_mode == :lines
94
+ if size == 0
95
+ receive_binary_data("") if respond_to?(:receive_binary_data)
96
+ # Do no more work here. Stay in line mode and keep consuming data.
97
+ else
98
+ @lbp_binary_limit = size.to_i # (nil will be stored as zero)
99
+ if @lbp_binary_limit > 0
100
+ raise "Overlength" if @lbp_binary_limit > MaxBinaryLength # arbitrary sanity check
101
+ @lbp_binary_buffer = "\0" * @lbp_binary_limit
102
+ @lbp_binary_bytes_received = 0
103
+ end
104
+
105
+ @lbp_mode = :binary
106
+ receive_data @lpb_buffer.flush
107
+ end
108
+ else
109
+ raise "invalid operation"
110
+ end
111
+ end
112
+
113
+ #--
114
+ # For internal use, establish protocol baseline for handling lines.
115
+ def lbp_init_line_state
116
+ @lpb_buffer = BufferedTokenizer.new("\n", MaxLineLength)
117
+ @lbp_mode = :lines
118
+ end
119
+ private :lbp_init_line_state
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,163 @@
1
+ # $Id$
2
+ #
3
+ # Author:: Francis Cianfrocca (gmail: blackhedd)
4
+ # Homepage:: http://rubyeventmachine.com
5
+ # Date:: 15 November 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
+ # In the grand, time-honored tradition of re-inventing the wheel, we offer
29
+ # here YET ANOTHER protocol that handles line-oriented data with interspersed
30
+ # binary text. This one trades away some of the performance optimizations of
31
+ # EventMachine::Protocols::LineAndTextProtocol in order to get better correctness
32
+ # with regard to binary text blocks that can switch back to line mode. It also
33
+ # permits the line-delimiter to change in midstream.
34
+ # This was originally written to support Stomp.
35
+
36
+ # TODO! We're not enforcing the limits on header lengths and text-lengths.
37
+ # When we get around to that, call #receive_error if the user defined it, otherwise
38
+ # throw exceptions.
39
+
40
+ module EventMachine
41
+ module Protocols
42
+ module LineText2
43
+ MaxLineLength = 16*1024
44
+ MaxBinaryLength = 32*1024*1024
45
+
46
+ #--
47
+ # Will be called recursively until there's no data to read.
48
+ # That way the user-defined handlers we call can modify the
49
+ # handling characteristics on a per-token basis.
50
+ #
51
+ def receive_data data
52
+ return unless (data and data.length > 0)
53
+
54
+ # Do this stuff in lieu of a constructor.
55
+ @lt2_mode ||= :lines
56
+ @lt2_delimiter ||= "\n"
57
+ @lt2_linebuffer ||= []
58
+
59
+ if @lt2_mode == :lines
60
+ if ix = data.index( @lt2_delimiter )
61
+ @lt2_linebuffer << data[0...ix]
62
+ ln = @lt2_linebuffer.join
63
+ @lt2_linebuffer.clear
64
+ if @lt2_delimiter == "\n"
65
+ ln.chomp!
66
+ end
67
+ receive_line ln
68
+ receive_data data[(ix+@lt2_delimiter.length)..-1]
69
+ else
70
+ @lt2_linebuffer << data
71
+ end
72
+ elsif @lt2_mode == :text
73
+ if @lt2_textsize
74
+ needed = @lt2_textsize - @lt2_textpos
75
+ will_take = if data.length > needed
76
+ needed
77
+ else
78
+ data.length
79
+ end
80
+
81
+ @lt2_textbuffer << data[0...will_take]
82
+ tail = data[will_take..-1]
83
+
84
+ @lt2_textpos += will_take
85
+ if @lt2_textpos >= @lt2_textsize
86
+ # Reset line mode (the default behavior) BEFORE calling the
87
+ # receive_binary_data. This makes it possible for user code
88
+ # to call set_text_mode, enabling chains of text blocks
89
+ # (which can possibly be of different sizes).
90
+ set_line_mode
91
+ receive_binary_data @lt2_textbuffer.join
92
+ receive_end_of_binary_data
93
+ end
94
+
95
+ receive_data tail
96
+ else
97
+ receive_binary_data data
98
+ end
99
+ end
100
+ end
101
+
102
+
103
+ def set_delimiter delim
104
+ @lt2_delimiter = delim.to_s
105
+ end
106
+
107
+ # Called internally but also exposed to user code, for the case in which
108
+ # processing of binary data creates a need to transition back to line mode.
109
+ # We support an optional parameter to "throw back" some data, which might
110
+ # be an umprocessed chunk of the transmitted binary data, or something else
111
+ # entirely.
112
+ def set_line_mode data=""
113
+ @lt2_mode = :lines
114
+ (@lt2_linebuffer ||= []).clear
115
+ receive_data data.to_s
116
+ end
117
+
118
+ def set_text_mode size=nil
119
+ if size == 0
120
+ set_line_mode
121
+ else
122
+ @lt2_mode = :text
123
+ (@lt2_textbuffer ||= []).clear
124
+ @lt2_textsize = size # which can be nil, signifying no limit
125
+ @lt2_textpos = 0
126
+ end
127
+ end
128
+
129
+ # Alias for #set_text_mode, added for back-compatibility with LineAndTextProtocol.
130
+ def set_binary_mode size=nil
131
+ set_text_mode size
132
+ end
133
+
134
+ # In case of a dropped connection, we'll send a partial buffer to user code
135
+ # when in sized text mode. User overrides of #receive_binary_data need to
136
+ # be aware that they may get a short buffer.
137
+ def unbind
138
+ if @lt2_mode == :text and @lt2_textpos > 0
139
+ receive_binary_data @lt2_textbuffer.join
140
+ end
141
+ end
142
+
143
+ # Stub. Should be subclassed by user code.
144
+ def receive_line ln
145
+ # no-op
146
+ end
147
+
148
+ # Stub. Should be subclassed by user code.
149
+ def receive_binary_data data
150
+ # no-op
151
+ end
152
+
153
+ # Stub. Should be subclassed by user code.
154
+ # This is called when transitioning internally from text mode
155
+ # back to line mode. Useful when client code doesn't want
156
+ # to keep track of how much data it's received.
157
+ def receive_end_of_binary_data
158
+ # no-op
159
+ end
160
+ end
161
+ end
162
+ end
163
+