注意:我在同一个.rb文件中有两个代码块(见下文)。第一次ftp.getbinaryfile()工作,然后它抛出错误。
注意: file
变量是用于调试目的的文件的静态路径。
我在ruby 2.0.0p481 (2014-05-08) [x64-mingw32]
file = "/Filetrack/E-mail_Gateway/_Installer/GA/E-mail Gateway_10.0_Changes_PUBLIC.pdf"
list = ftp.list('*')
list.each{|item|
counter=counter+1
counter++
ftp.getbinaryfile(file, where_to_save+File.basename(file)+counter.to_s, 1024)
puts "downloaded - .each used"
}
然后在同一个.rb文件中我得到了这段代码
ftp.list('*') { |item|
puts "downloading using .list('*') {"
counter++
ftp.getbinaryfile(file, where_to_save+File.basename(file)+counter.to_s, 1024)
puts "downloaded #{file}"
}
并且该代码会抛出此错误
Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:974:in `parse227': 200 Type set to I. (Net::FTPReplyError)
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:394:in `makepasv'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:406:in `transfercmd'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:487:in `block (2 levels) in retrbinary'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:199:in `with_binary'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:485:in `block in retrbinary'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/monitor.rb:211:in `mon_synchronize'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:484:in `retrbinary'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:617:in `getbinaryfile'
ftp会话由
创建ftp = Net::FTP.new('ftp.***.***.net')
ftp.passive = false
ftp.debug_mode = true
ftp.login(ftp_username, ftp_password)
有人可以解释为什么第二个版本有效吗?
添加了ftp调试日志:
put: USER r.***
get: 331 Password required for r.***.
put: PASS ************
get: 230-Welcome to FTP
get: 230 User r.****logged in.
put: TYPE I
get: 200 Type set to I.
put: CWD /Filetrack/E-mail_Gateway/_Installer/GA/010_000_003_000/
get: 250 CWD command successful.
put: TYPE A
get: 200 Type set to A.
put: PASV
get: 227 Entering Passive Mode (194,212,10,23,195,92).
put: LIST *
get: 125 Data connection already open; Transfer starting.
get: 226 Transfer complete.
put: TYPE I
get: 200 Type set to I.
put: PASV
get: 227 Entering Passive Mode (194,212,10,23,195,93).
put: RETR /Filetrack/E-mail_Gateway/_Installer/GA/010_000_003_000/E-mail Gateway_10.0_Changes_PUBLIC.pdf
get: 125 Data connection already open; Transfer starting.
get: 226 Transfer complete.
downloaded - .each used
put: TYPE A
get: 200 Type set to A.
put: PASV
get: 227 Entering Passive Mode (***,***,10,23,195,97).
put: LIST *
get: 125 Data connection already open; Transfer starting.
downloading using .list('*') {
put: TYPE I
get: 226 Transfer complete.
put: PASV
get: 200 Type set to I.
put: TYPE A
get: 227 Entering Passive Mode (***,***,10,23,195,98).
put: TYPE I
get: 200 Type set to A.
d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:974:in `parse227': 200 Type set to I. (Net::FTPReplyError)
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:394:in `makepasv'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:406:in `transfercmd'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:487:in `block (2 levels) in retrbinary'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:199:in `with_binary'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:485:in `block in retrbinary'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/monitor.rb:211:in `mon_synchronize'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:484:in `retrbinary'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:617:in `getbinaryfile'
from download2 - debugging.rb:41:in `block in <main>'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:518:in `block (3 levels) in retrlines'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:515:in `loop'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:515:in `block (2 levels) in retrlines'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:199:in `with_binary'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:512:in `block in retrlines'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/monitor.rb:211:in `mon_synchronize'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:511:in `retrlines'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:760:in `list'
from download2 - debugging.rb:38:in `<main>'
UPDATE2
如果使用ftp.passive = false
则记录
downloading using .list('*') {
put: TYPE I
get: 226 Transfer complete.
put: PORT ***,***,20,102,235,136
get: 200 Type set to I.
put: RETR /Filetrack/E-mail_Gateway/_Installer/GA/010_000_003_000/Email Gateway_10.0_Changes_PUBLIC.pdf
get: 200 PORT command successful.
put: TYPE A
put: TYPE I
d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/protocol.rb:211:in `write': An existing connection was forcibly closed by the remote host. (Errno::ECONNRESET)
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/protocol.rb:211:in `write0'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/protocol.rb:185:in `block in write'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/protocol.rb:202:in `writing'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/protocol.rb:184:in `write'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:283:in `putline'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:360:in `block in voidcmd'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/monitor.rb:211:in `mon_synchronize'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:359:in `voidcmd'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:183:in `send_type_command'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:172:in `binary='
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:201:in `ensure in with_binary'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:201:in `with_binary'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:512:in `block in retrlines'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/monitor.rb:211:in `mon_synchronize'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:511:in `retrlines'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:760:in `list'
from download2 - debugging.rb:39:in `<main>'
UPDATE3
我尝试在ftp活动模式下运行相同的代码几次,实际上所有文件都已下载,但脚本完成时出现错误。
downloading using .list('*') {
put: TYPE I
get: 200 Type set to I.
put: PORT **,**,20,102,197,73
get: 200 PORT command successful.
put: RETR /Filetrack/E-mail_Gateway/_Installer/GA/010_000_003_000/E-mail Gateway_10.0_Changes_PUBLIC.pdf
get: 150 Opening BINARY mode data connection for /Filetrack/E-mail_Gateway/_Installer/GA/010_000_003_000/E-mail Gateway_10.0_Changes_PUBLIC.pdf(
60911 bytes).
get: 226 Transfer complete.
put: TYPE A
get: 200 Type set to A.
downloaded /Filetrack/E-mail_Gateway/_Installer/GA/010_000_003_000/E-mail Gateway_10.0_Changes_PUBLIC.pdf
put: TYPE I
get: 200 Type set to I.
d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/protocol.rb:158:in `rescue in rbuf_fill': Net::ReadTimeout (Net::ReadTimeout)
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/protocol.rb:152:in `rbuf_fill'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/protocol.rb:134:in `readuntil'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:1108:in `readline'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:289:in `getline'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:300:in `getmultiline'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:318:in `getresp'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:338:in `voidresp'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:526:in `block (2 levels) in retrlines'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:199:in `with_binary'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:512:in `block in retrlines'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/monitor.rb:211:in `mon_synchronize'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:511:in `retrlines'
from d:/prog/Ruby200-x64/lib/ruby/2.0.0/net/ftp.rb:760:in `list'
from download2 - debugging.rb:39:in `<main>'
答案 0 :(得分:9)
您的连接使用PASSIVE模式。由于您没有显示创建FTP
对象的代码部分,因此我假设您明确将模式设置为passive
。
ftp = Net::FTP.new('example.com')
ftp.passive = true
根据异常的堆栈跟踪,可以看到方法makepasv
发出PASV
命令时发生问题,但是得到的响应不是227 Entering Passive Mode (194,212,10,23,195,93).
,而是获得响应200 Type set to I.
makepasv
和parse227
的实施(稍后在帖子中提及参考1 &amp; 参考2 )表明代码专门寻找回报代码227
如果不是这样,它将抛出一个FTPError。
这是给定方案中发生的事情。
这可归因于下面显示的语法 这很可能不是那么理解的行为(正如我自己在回答这篇文章的过程中发现的那样)
ftp.list('*') { |item|
ftp.getbinaryfile(file, where_to_save+File.basename(file)+counter.to_s, 1024)
}
在上面的代码中,LIST *
发出ftp.list('*')
命令。此命令的典型响应如下:
put: LIST *
get: 125 Data connection already open; Transfer starting.
get: 226 Transfer complete.
可以看出,LIST *
会产生两行结果。这个事实对于理解这个问题至关重要。
传递给ftp.list('*')
的块使用getbinaryfile
方法下载二进制文件。
getbinaryfile
通常会发出以下命令:
TYPE I
将连接置于图像(二进制)模式PASV
进入被动模式RETR /path/of/file/to/download
当块执行ftp.list('*')
的第一个结果,并开始发出与getbinaryfile
相关的命令时,在该时间点,只读取LIST *
的第一行响应 - 第二行尚未阅读。这是第二行,显示为对块中发出的下一个命令的响应。
因此,当发出第一个命令TYPE I
时,代码将LIST *
的第二行作为响应读取(在调试日志中显而易见)
put: TYPE I
get: 226 Transfer complete.
当发出第二个命令PASV
时,代码会读取TYPE I
的响应(从调试日志中可以看出)
put: PASV
get: 200 Type set to I.
makepasv
的实现是这样的,它希望响应的响应代码为227
(参见参考文献1和参考文献2中的第394和973行)。在这种情况下抛出异常Net::FTPReplyError
,因为parse227
传递了TYPE I
命令的响应。
总之,在使用passive
模式时,似乎不可能在给予ftp.list(&#39; *&#39;)的块中执行其他FTP操作强>
在这种情况下,ftp.list('*')
在没有块的情况下被调用,因此它返回字符串数组作为输出。在该数组上使用each
不会产生类似的情况 - 因此,没有观察到任何问题。
FTP#list
的作者似乎希望以下两种变体以同等方式运作:
ftp.list('*') { |f| } # block given to list
ftp.list('*').each { |f| } # block given to enum returned by list
根据list
API的official documentation:
list(* args){| line | ......}
返回。中的文件信息数组 目录(输出类似于
ls -l
)。如果给出一个块,它 遍历列表。
如果我们查看list
的实现,那么,我们会看到,当给出一个块时,从ftp.list('*')
读取的每一行都将逐一产生到块中。使用被动模式时,如果该块尝试执行任何其他FTP命令,则会导致上述情况。
754 def list(*args, &block) # :yield: line
755 cmd = "LIST"
756 args.each do |arg|
757 cmd = cmd + " " + arg.to_s
758 end
759 if block
760 retrlines(cmd, &block)
761 else
762 lines = []
763 retrlines(cmd) do |line|
764 lines << line
765 end
766 return lines
767 end
768 end
我们可以通过首先将ftp.list('*').each
响应中的所有行收集到数组中来将实现更改为等同于LIST *
变量来解决此问题,并且如果块是块则将该数组传递给块给出。我们仍将坚持API文档。
def list(*args, &block) # :yield: line
cmd = "LIST"
args.each do |arg|
cmd = cmd + " " + arg.to_s
end
# First lets fetch all the lines
lines = []
retrlines(cmd) do |line|
lines << line
end
if block
lines.each { |l| yield l }
else
return lines
end
end
我已报告bug in Ruby Bug Tracker建议在FTP#list方法的实施方面发生上述变化。
参考1 - makepasv
391 # sends the appropriate command to enable a passive connection
392 def makepasv # :nodoc:
393 if @sock.peeraddr[0] == "AF_INET"
394 host, port = parse227(sendcmd("PASV"))
395 else
396 host, port = parse229(sendcmd("EPSV"))
397 # host, port = parse228(sendcmd("LPSV"))
398 end
399 return host, port
400 end
参考2 - parse227
968 # handler for response code 227
969 # (Entering Passive Mode (h1,h2,h3,h4,p1,p2))
970 #
971 # Returns host and port.
972 def parse227(resp) # :nodoc:
973 if resp[0, 3] != "227"
974 raise FTPReplyError, resp
975 end
976 if m = /\((?<host>\d+(,\d+){3}),(?<port>\d+,\d+)\)/.match(resp)
977 return parse_pasv_ipv4_host(m["host"]), parse_pasv_port(m["port"])
978 else
979 raise FTPProtoError, resp
980 end
981 end
源代码摘要取自ftp.rb
.
更新时间:2015年9月13日 对于这个问题,Ruby核心团队已经The proposed change accepted。