1 | #
|
---|
2 | # = net/protocol.rb
|
---|
3 | #
|
---|
4 | #--
|
---|
5 | # Copyright (c) 1999-2005 Yukihiro Matsumoto
|
---|
6 | # Copyright (c) 1999-2005 Minero Aoki
|
---|
7 | #
|
---|
8 | # written and maintained by Minero Aoki <[email protected]>
|
---|
9 | #
|
---|
10 | # This program is free software. You can re-distribute and/or
|
---|
11 | # modify this program under the same terms as Ruby itself,
|
---|
12 | # Ruby Distribute License or GNU General Public License.
|
---|
13 | #
|
---|
14 | # $Id: protocol.rb 11708 2007-02-12 23:01:19Z shyouhei $
|
---|
15 | #++
|
---|
16 | #
|
---|
17 | # WARNING: This file is going to remove.
|
---|
18 | # Do not rely on the implementation written in this file.
|
---|
19 | #
|
---|
20 |
|
---|
21 | require 'socket'
|
---|
22 | require 'timeout'
|
---|
23 |
|
---|
24 | module Net # :nodoc:
|
---|
25 |
|
---|
26 | class Protocol #:nodoc: internal use only
|
---|
27 | private
|
---|
28 | def Protocol.protocol_param(name, val)
|
---|
29 | module_eval(<<-End, __FILE__, __LINE__ + 1)
|
---|
30 | def #{name}
|
---|
31 | #{val}
|
---|
32 | end
|
---|
33 | End
|
---|
34 | end
|
---|
35 | end
|
---|
36 |
|
---|
37 |
|
---|
38 | class ProtocolError < StandardError; end
|
---|
39 | class ProtoSyntaxError < ProtocolError; end
|
---|
40 | class ProtoFatalError < ProtocolError; end
|
---|
41 | class ProtoUnknownError < ProtocolError; end
|
---|
42 | class ProtoServerError < ProtocolError; end
|
---|
43 | class ProtoAuthError < ProtocolError; end
|
---|
44 | class ProtoCommandError < ProtocolError; end
|
---|
45 | class ProtoRetriableError < ProtocolError; end
|
---|
46 | ProtocRetryError = ProtoRetriableError
|
---|
47 |
|
---|
48 |
|
---|
49 | class BufferedIO #:nodoc: internal use only
|
---|
50 | def initialize(io)
|
---|
51 | @io = io
|
---|
52 | @read_timeout = 60
|
---|
53 | @debug_output = nil
|
---|
54 | @rbuf = ''
|
---|
55 | end
|
---|
56 |
|
---|
57 | attr_reader :io
|
---|
58 | attr_accessor :read_timeout
|
---|
59 | attr_accessor :debug_output
|
---|
60 |
|
---|
61 | def inspect
|
---|
62 | "#<#{self.class} io=#{@io}>"
|
---|
63 | end
|
---|
64 |
|
---|
65 | def closed?
|
---|
66 | @io.closed?
|
---|
67 | end
|
---|
68 |
|
---|
69 | def close
|
---|
70 | @io.close
|
---|
71 | end
|
---|
72 |
|
---|
73 | #
|
---|
74 | # Read
|
---|
75 | #
|
---|
76 |
|
---|
77 | public
|
---|
78 |
|
---|
79 | def read(len, dest = '', ignore_eof = false)
|
---|
80 | LOG "reading #{len} bytes..."
|
---|
81 | read_bytes = 0
|
---|
82 | begin
|
---|
83 | while read_bytes + @rbuf.size < len
|
---|
84 | dest << (s = rbuf_consume(@rbuf.size))
|
---|
85 | read_bytes += s.size
|
---|
86 | rbuf_fill
|
---|
87 | end
|
---|
88 | dest << (s = rbuf_consume(len - read_bytes))
|
---|
89 | read_bytes += s.size
|
---|
90 | rescue EOFError
|
---|
91 | raise unless ignore_eof
|
---|
92 | end
|
---|
93 | LOG "read #{read_bytes} bytes"
|
---|
94 | dest
|
---|
95 | end
|
---|
96 |
|
---|
97 | def read_all(dest = '')
|
---|
98 | LOG 'reading all...'
|
---|
99 | read_bytes = 0
|
---|
100 | begin
|
---|
101 | while true
|
---|
102 | dest << (s = rbuf_consume(@rbuf.size))
|
---|
103 | read_bytes += s.size
|
---|
104 | rbuf_fill
|
---|
105 | end
|
---|
106 | rescue EOFError
|
---|
107 | ;
|
---|
108 | end
|
---|
109 | LOG "read #{read_bytes} bytes"
|
---|
110 | dest
|
---|
111 | end
|
---|
112 |
|
---|
113 | def readuntil(terminator, ignore_eof = false)
|
---|
114 | begin
|
---|
115 | until idx = @rbuf.index(terminator)
|
---|
116 | rbuf_fill
|
---|
117 | end
|
---|
118 | return rbuf_consume(idx + terminator.size)
|
---|
119 | rescue EOFError
|
---|
120 | raise unless ignore_eof
|
---|
121 | return rbuf_consume(@rbuf.size)
|
---|
122 | end
|
---|
123 | end
|
---|
124 |
|
---|
125 | def readline
|
---|
126 | readuntil("\n").chop
|
---|
127 | end
|
---|
128 |
|
---|
129 | private
|
---|
130 |
|
---|
131 | def rbuf_fill
|
---|
132 | timeout(@read_timeout) {
|
---|
133 | @rbuf << @io.sysread(1024)
|
---|
134 | }
|
---|
135 | end
|
---|
136 |
|
---|
137 | def rbuf_consume(len)
|
---|
138 | s = @rbuf.slice!(0, len)
|
---|
139 | @debug_output << %Q[-> #{s.dump}\n] if @debug_output
|
---|
140 | s
|
---|
141 | end
|
---|
142 |
|
---|
143 | #
|
---|
144 | # Write
|
---|
145 | #
|
---|
146 |
|
---|
147 | public
|
---|
148 |
|
---|
149 | def write(str)
|
---|
150 | writing {
|
---|
151 | write0 str
|
---|
152 | }
|
---|
153 | end
|
---|
154 |
|
---|
155 | def writeline(str)
|
---|
156 | writing {
|
---|
157 | write0 str + "\r\n"
|
---|
158 | }
|
---|
159 | end
|
---|
160 |
|
---|
161 | private
|
---|
162 |
|
---|
163 | def writing
|
---|
164 | @written_bytes = 0
|
---|
165 | @debug_output << '<- ' if @debug_output
|
---|
166 | yield
|
---|
167 | @debug_output << "\n" if @debug_output
|
---|
168 | bytes = @written_bytes
|
---|
169 | @written_bytes = nil
|
---|
170 | bytes
|
---|
171 | end
|
---|
172 |
|
---|
173 | def write0(str)
|
---|
174 | @debug_output << str.dump if @debug_output
|
---|
175 | len = @io.write(str)
|
---|
176 | @written_bytes += len
|
---|
177 | len
|
---|
178 | end
|
---|
179 |
|
---|
180 | #
|
---|
181 | # Logging
|
---|
182 | #
|
---|
183 |
|
---|
184 | private
|
---|
185 |
|
---|
186 | def LOG_off
|
---|
187 | @save_debug_out = @debug_output
|
---|
188 | @debug_output = nil
|
---|
189 | end
|
---|
190 |
|
---|
191 | def LOG_on
|
---|
192 | @debug_output = @save_debug_out
|
---|
193 | end
|
---|
194 |
|
---|
195 | def LOG(msg)
|
---|
196 | return unless @debug_output
|
---|
197 | @debug_output << msg + "\n"
|
---|
198 | end
|
---|
199 | end
|
---|
200 |
|
---|
201 |
|
---|
202 | class InternetMessageIO < BufferedIO #:nodoc: internal use only
|
---|
203 | def InternetMessageIO.old_open(addr, port,
|
---|
204 | open_timeout = nil, read_timeout = nil, debug_output = nil)
|
---|
205 | debug_output << "opening connection to #{addr}...\n" if debug_output
|
---|
206 | s = timeout(open_timeout) { TCPsocket.new(addr, port) }
|
---|
207 | io = new(s)
|
---|
208 | io.read_timeout = read_timeout
|
---|
209 | io.debug_output = debug_output
|
---|
210 | io
|
---|
211 | end
|
---|
212 |
|
---|
213 | def initialize(io)
|
---|
214 | super
|
---|
215 | @wbuf = nil
|
---|
216 | end
|
---|
217 |
|
---|
218 | #
|
---|
219 | # Read
|
---|
220 | #
|
---|
221 |
|
---|
222 | def each_message_chunk
|
---|
223 | LOG 'reading message...'
|
---|
224 | LOG_off()
|
---|
225 | read_bytes = 0
|
---|
226 | while (line = readuntil("\r\n")) != ".\r\n"
|
---|
227 | read_bytes += line.size
|
---|
228 | yield line.sub(/\A\./, '')
|
---|
229 | end
|
---|
230 | LOG_on()
|
---|
231 | LOG "read message (#{read_bytes} bytes)"
|
---|
232 | end
|
---|
233 |
|
---|
234 | # *library private* (cannot handle 'break')
|
---|
235 | def each_list_item
|
---|
236 | while (str = readuntil("\r\n")) != ".\r\n"
|
---|
237 | yield str.chop
|
---|
238 | end
|
---|
239 | end
|
---|
240 |
|
---|
241 | def write_message_0(src)
|
---|
242 | prev = @written_bytes
|
---|
243 | each_crlf_line(src) do |line|
|
---|
244 | write0 line.sub(/\A\./, '..')
|
---|
245 | end
|
---|
246 | @written_bytes - prev
|
---|
247 | end
|
---|
248 |
|
---|
249 | #
|
---|
250 | # Write
|
---|
251 | #
|
---|
252 |
|
---|
253 | def write_message(src)
|
---|
254 | LOG "writing message from #{src.class}"
|
---|
255 | LOG_off()
|
---|
256 | len = writing {
|
---|
257 | using_each_crlf_line {
|
---|
258 | write_message_0 src
|
---|
259 | }
|
---|
260 | }
|
---|
261 | LOG_on()
|
---|
262 | LOG "wrote #{len} bytes"
|
---|
263 | len
|
---|
264 | end
|
---|
265 |
|
---|
266 | def write_message_by_block(&block)
|
---|
267 | LOG 'writing message from block'
|
---|
268 | LOG_off()
|
---|
269 | len = writing {
|
---|
270 | using_each_crlf_line {
|
---|
271 | begin
|
---|
272 | block.call(WriteAdapter.new(self, :write_message_0))
|
---|
273 | rescue LocalJumpError
|
---|
274 | # allow `break' from writer block
|
---|
275 | end
|
---|
276 | }
|
---|
277 | }
|
---|
278 | LOG_on()
|
---|
279 | LOG "wrote #{len} bytes"
|
---|
280 | len
|
---|
281 | end
|
---|
282 |
|
---|
283 | private
|
---|
284 |
|
---|
285 | def using_each_crlf_line
|
---|
286 | @wbuf = ''
|
---|
287 | yield
|
---|
288 | if not @wbuf.empty? # unterminated last line
|
---|
289 | write0 @wbuf.chomp + "\r\n"
|
---|
290 | elsif @written_bytes == 0 # empty src
|
---|
291 | write0 "\r\n"
|
---|
292 | end
|
---|
293 | write0 ".\r\n"
|
---|
294 | @wbuf = nil
|
---|
295 | end
|
---|
296 |
|
---|
297 | def each_crlf_line(src)
|
---|
298 | buffer_filling(@wbuf, src) do
|
---|
299 | while line = @wbuf.slice!(/\A.*(?:\n|\r\n|\r(?!\z))/n)
|
---|
300 | yield line.chomp("\n") + "\r\n"
|
---|
301 | end
|
---|
302 | end
|
---|
303 | end
|
---|
304 |
|
---|
305 | def buffer_filling(buf, src)
|
---|
306 | case src
|
---|
307 | when String # for speeding up.
|
---|
308 | 0.step(src.size - 1, 1024) do |i|
|
---|
309 | buf << src[i, 1024]
|
---|
310 | yield
|
---|
311 | end
|
---|
312 | when File # for speeding up.
|
---|
313 | while s = src.read(1024)
|
---|
314 | buf << s
|
---|
315 | yield
|
---|
316 | end
|
---|
317 | else # generic reader
|
---|
318 | src.each do |s|
|
---|
319 | buf << s
|
---|
320 | yield if buf.size > 1024
|
---|
321 | end
|
---|
322 | yield unless buf.empty?
|
---|
323 | end
|
---|
324 | end
|
---|
325 | end
|
---|
326 |
|
---|
327 |
|
---|
328 | #
|
---|
329 | # The writer adapter class
|
---|
330 | #
|
---|
331 | class WriteAdapter
|
---|
332 | def initialize(socket, method)
|
---|
333 | @socket = socket
|
---|
334 | @method_id = method
|
---|
335 | end
|
---|
336 |
|
---|
337 | def inspect
|
---|
338 | "#<#{self.class} socket=#{@socket.inspect}>"
|
---|
339 | end
|
---|
340 |
|
---|
341 | def write(str)
|
---|
342 | @socket.__send__(@method_id, str)
|
---|
343 | end
|
---|
344 |
|
---|
345 | alias print write
|
---|
346 |
|
---|
347 | def <<(str)
|
---|
348 | write str
|
---|
349 | self
|
---|
350 | end
|
---|
351 |
|
---|
352 | def puts(str = '')
|
---|
353 | write str.chomp("\n") + "\n"
|
---|
354 | end
|
---|
355 |
|
---|
356 | def printf(*args)
|
---|
357 | write sprintf(*args)
|
---|
358 | end
|
---|
359 | end
|
---|
360 |
|
---|
361 |
|
---|
362 | class ReadAdapter #:nodoc: internal use only
|
---|
363 | def initialize(block)
|
---|
364 | @block = block
|
---|
365 | end
|
---|
366 |
|
---|
367 | def inspect
|
---|
368 | "#<#{self.class}>"
|
---|
369 | end
|
---|
370 |
|
---|
371 | def <<(str)
|
---|
372 | call_block(str, &@block) if @block
|
---|
373 | end
|
---|
374 |
|
---|
375 | private
|
---|
376 |
|
---|
377 | # This method is needed because @block must be called by yield,
|
---|
378 | # not Proc#call. You can see difference when using `break' in
|
---|
379 | # the block.
|
---|
380 | def call_block(str)
|
---|
381 | yield str
|
---|
382 | end
|
---|
383 | end
|
---|
384 |
|
---|
385 |
|
---|
386 | module NetPrivate #:nodoc: obsolete
|
---|
387 | Socket = ::Net::InternetMessageIO
|
---|
388 | end
|
---|
389 |
|
---|
390 | end # module Net
|
---|