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.