Ruby FTP - A free Ruby script to throttle the FTP file upload speed

An interesting thing about developing software to work with an FTP server is that for some tests you need files to be uploaded to the FTP server very slowly. Usually you want software to run as fast as possible, but in my case I needed to be able to throttle the FTP upload speed to test portions of my code. (Specifically, I'm writing code to listen to Proftpd FTP server events, and I needed this to make sure all the STOR, DELE, RNFR, and RNTO events work as advertised (making sure the event notifications aren't sent until the event is complete.)

As timing would have it, one of my co-workers had a similar but more urgent need yesterday for something he's doing, so I finally got around to writing a Ruby script for this purpose.

FTP throttling with Filezilla

I found that Filezilla has an FTP speed-throttling feature, which is cool. It can throttle both upload and download speeds, which will be really helpful when I just want to run a few tests manually. But for my purposes I need to be able to run my tests over and over again from the command line, and I didn't see where Filezilla offered a command line scripting feature.

A quick intro to my Ruby FTP throttle script

So, Ruby being Ruby, in a little less than an hour I kicked out the following Ruby FTP throttle script that lets me do just what I need: throttle the speed of files that are being uploaded to an FTP server, keeping the connection open as long as I need to test my programs.

Before looking at the code, please note that I kicked this out quickly, so there are no frills here. Most importantly, you need to set the values of the variables at the top of the script, and you also need to change the kbpersec parameter in the storbinary method to set the upload speed to whatever you want it to be.

Without any further ado, here is the Ruby source code for my "slow ftp" program:

#
# program: slowftp.rb - intentionally upload an ftp file slowly
# author:  al alexander, devdaily.com
# version: 0.2
#
require 'net/ftp'

# define your variables
server = 'myserver'
username = 'myuser'
password = 'mypass'
upload_dir = '/ftp/foo/bar'
upload_file = 'testfile.jpg'

ftp = Net::FTP.new(server)

class <<ftp

  def storbinary(cmd, file, blocksize, rest_offset = nil, &block) # :yield: data

    #------------------------#
    # change this as desired #
    #------------------------#
    kb_per_sec = 20

    #------------------------#
    # al: don't change these #
    #------------------------#
    bytes_per_sec = kb_per_sec * 1024.0
    blocksize = 1024
    sleep_time = blocksize / bytes_per_sec

    if rest_offset
      file.seek(rest_offset, IO::SEEK_SET)
    end
    synchronize do
      voidcmd("TYPE I")
      conn = transfercmd(cmd, rest_offset)
      loop do
        buf = file.read(blocksize)
        break if buf == nil
        conn.write(buf)
        yield(buf) if block

        #-----------#
        # al: sleep #
        #-----------#
        sleep sleep_time

      end
      conn.close
      voidresp
    end
  end
end

#-----
# main
#-----

ftp.login(username, password)
files = ftp.chdir(upload_dir)
p 'starting upload'
start = Time.new
ftp.putbinaryfile(upload_file)
done = Time.new
puts 'upload complete'
puts "started at #{start}"
puts "finished at #{done}"
ftp.close

Discussion of the Ruby FTP throttle script

I figured out how to write this Ruby FTP program by reading the documentation for the Net::FTP class, where I saw that if I wanted to make file uploads go slower I would need to modify the storbinary method shown above. The sections of that method that I modified look like this:

#------------------------#
# change this as desired #
#------------------------#
kb_per_sec = 20

#------------------------#
# al: don't change these #
#------------------------#
bytes_per_sec = kb_per_sec * 1024.0
blocksize = 1024
sleep_time = blocksize / bytes_per_sec

and this:

#-----------#
# al: sleep #
#-----------#
sleep sleep_time

Cracking open a Ruby object

I get to the storbinary method by cracking open my ftp object after I create it, using this syntax:

class <<ftp

If I had more time I would have done this differently, creating my own class to extend Net::FTP, but I was under the gun, and for some reason this was the first thing I thought to do. If you want to use this source code for your own needs, I'd recommend taking that approach. But under the covers I think you'll still have to modify this method to make the upload go slower.

As usual, feel free to use this Ruby source code for your own needs. That's why we're here.