为什么我将"200 Type设置为I.(Net :: FTPReplyError)"

Rad*_*dek 4 ruby

注意:我在同一个.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"
}
Run Code Online (Sandbox Code Playgroud)

然后在同一个.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}"
}
Run Code Online (Sandbox Code Playgroud)

那段代码抛出了这个错误

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'
Run Code Online (Sandbox Code Playgroud)

ftp会话由.创建

ftp = Net::FTP.new('ftp.***.***.net')
ftp.passive = false
ftp.debug_mode = true
ftp.login(ftp_username, ftp_password)
Run Code Online (Sandbox Code Playgroud)

有人可以解释为什么第二个版本有效吗?

UPDATE

添加了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>'
Run Code Online (Sandbox Code Playgroud)

UPDATE2

如果ftp.passive = false使用log

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>'
Run Code Online (Sandbox Code Playgroud)

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>'
Run Code Online (Sandbox Code Playgroud)

Wan*_*ker 9

"200 Type"设置为"I.(Net :: FTPReplyError)"的原因

您的连接使用PASSIVE模式.由于您没有显示创建FTP对象的代码部分,我将假设您将模式设置为passive显式.

ftp = Net::FTP.new('example.com')
ftp.passive = true
Run Code Online (Sandbox Code Playgroud)

基于异常的堆栈跟踪,可以看到当方法makepasv发出PASV命令时发生问题,但是获得响应而不是227 Entering Passive Mode (194,212,10,23,195,93).获得响应200 Type set to I.

执行makepasv和parse227(请参阅后面的参考文献1和参考文献2)表明代码专门查找返回代码,227如果不是这种情况,则会抛出FTPError.

这是给定方案中发生的情况.


为什么`parse227`收到错误的回复?

这可以归因于下面显示的语法.
这可能是不太了解的行为(正如我自己在回答这篇文章的过程中发现的那样)

ftp.list('*') { |item| 
  ftp.getbinaryfile(file, where_to_save+File.basename(file)+counter.to_s, 1024)
}
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,LIST *命令由ftp.list('*').颁发.此命令的典型响应如下:

put: LIST *
get: 125 Data connection already open; Transfer starting.
get: 226 Transfer complete.
Run Code Online (Sandbox Code Playgroud)

可以看出,LIST *产生两行结果.这个事实对于理解这个问题至关重要.

传递的块ftp.list('*')使用getbinaryfile方法下载二进制文件.

getbinaryfile 通常会发出以下命令:

  1. TYPE I 将连接置于图像(二进制)模式
  2. PASV进入被动模式
  3. RETR /path/of/file/to/download

当块执行第一个结果ftp.list('*')并开始发出与之相关的命令时getbinaryfile,在该时间点,只LIST *读取了第一行响应- 第二行尚未被读取.这是第二行,显示为对块中发出的下一个命令的响应.

因此,当TYPE I发出第一个命令时,代码读取第二行LIST *作为响应(在调试日志中显而易见)

put: TYPE I
get: 226 Transfer complete.
Run Code Online (Sandbox Code Playgroud)

PASV发出第二个命令时,代码读取响应TYPE I(从调试日志中可以看出)

put: PASV
get: 200 Type set to I.
Run Code Online (Sandbox Code Playgroud)

实现makepasv是这样的,它希望响应具有响应代码227(分别参考参考文献1和参考文献2中的第394和973行).一个异常Net::FTPReplyError被抛出在这种情况下被抛出parse227传递的响应TYPE I命令.

总之,当使用passive模式时,似乎在给出给ftp.list('*')的块中执行其他FTP操作是不可行的.


为什么`ftp.list('*').each`工作?

在这种情况下,在ftp.list('*')没有块的情况下调用它,因此它返回字符串数组作为输出.使用each该阵列上不会产生类似的情况-因此,没有发现问题.


解

似乎作者FTP#list预期以下两种变体以相同的方式工作:

ftp.list('*') { |f|  }  # block given to list
ftp.list('*').each { |f|  } # block given to enum returned by list
Run Code Online (Sandbox Code Playgroud)

按照官方文档的listAPI:

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
Run Code Online (Sandbox Code Playgroud)

我们可以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
Run Code Online (Sandbox Code Playgroud)

我已经报告了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
Run Code Online (Sandbox Code Playgroud)

参考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
Run Code Online (Sandbox Code Playgroud)

源代码片段取自ftp.rb.

更新:2015年9月13日 所提出的变化已接受由Ruby的核心团队针对此问题.