| #!/usr/bin/env python |
| # throttled_ftpd.py |
| |
| """ftpd supporting bandwidth throttling capabilities for data transfer. |
| """ |
| |
| import os |
| import time |
| import asyncore |
| |
| from pyftpdlib import ftpserver |
| |
| |
| class ThrottledDTPHandler(ftpserver.DTPHandler): |
| """A DTPHandler which wraps sending and receiving in a data counter |
| and temporarily sleeps the channel so that you burst to no more than |
| x Kb/sec average. |
| """ |
| |
| # maximum number of bytes to transmit in a second (0 == no limit) |
| read_limit = 0 |
| write_limit = 0 |
| |
| def __init__(self, sock_obj, cmd_channel): |
| ftpserver.DTPHandler.__init__(self, sock_obj, cmd_channel) |
| self._timenext = 0 |
| self._datacount = 0 |
| self._sleeping = False |
| self._throttler = None |
| |
| def readable(self): |
| return not self._sleeping and ftpserver.DTPHandler.readable(self) |
| |
| def writable(self): |
| return not self._sleeping and ftpserver.DTPHandler.writable(self) |
| |
| def recv(self, buffer_size): |
| chunk = asyncore.dispatcher.recv(self, buffer_size) |
| if self.read_limit: |
| self.throttle_bandwidth(len(chunk), self.read_limit) |
| return chunk |
| |
| def send(self, data): |
| num_sent = asyncore.dispatcher.send(self, data) |
| if self.write_limit: |
| self.throttle_bandwidth(num_sent, self.write_limit) |
| return num_sent |
| |
| def throttle_bandwidth(self, len_chunk, max_speed): |
| """A method which counts data transmitted so that you burst to |
| no more than x Kb/sec average. |
| """ |
| self._datacount += len_chunk |
| if self._datacount >= max_speed: |
| self._datacount = 0 |
| now = time.time() |
| sleepfor = self._timenext - now |
| if sleepfor > 0: |
| # we've passed bandwidth limits |
| def unsleep(): |
| self._sleeping = False |
| self._sleeping = True |
| self._throttler = ftpserver.CallLater(sleepfor * 2, unsleep) |
| self._timenext = now + 1 |
| |
| def close(self): |
| if self._throttler is not None and not self._throttler.cancelled: |
| self._throttler.cancel() |
| ftpserver.DTPHandler.close(self) |
| |
| |
| if __name__ == '__main__': |
| authorizer = ftpserver.DummyAuthorizer() |
| authorizer.add_user('user', '12345', os.getcwd(), perm='elradfmw') |
| authorizer.add_anonymous(os.getcwd()) |
| |
| # use the modified DTPHandler class and set a speed limit for both |
| # sending and receiving |
| dtp_handler = ThrottledDTPHandler |
| dtp_handler.read_limit = 30072 # 30 Kb/sec (30 * 1024) |
| dtp_handler.write_limit = 30072 # 30 Kb/sec (30 * 1024) |
| |
| ftp_handler = ftpserver.FTPHandler |
| ftp_handler.authorizer = authorizer |
| # have the ftp handler use the different dtp handler |
| ftp_handler.dtp_handler = dtp_handler |
| |
| ftpd = ftpserver.FTPServer(('', 21), ftp_handler) |
| ftpd.serve_forever() |