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

时间:2015-08-12 03:12:23

标签: 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"
}

然后在同一个.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>'

1 个答案:

答案 0 :(得分:9)

`200 Type设置为I.(Net :: FTPReplyError)`

的原因

您的连接使用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.

makepasvparse227的实施(稍后在帖子中提及参考1 &amp; 参考2 )表明代码专门寻找回报代码227如果不是这样,它将抛出一个FTPError。

这是给定方案中发生的事情。

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

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

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通常会发出以下命令:

  1. TYPE I将连接置于图像(二进制)模式
  2. PASV进入被动模式
  3. RETR /path/of/file/to/download
  4. 当块执行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(&#39; *&#39;)。每个都工作?

    在这种情况下,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