janos.szurovecz.hu

A régi blogom, ahova lehet írok majd még...

Sávszélesség állítás streamwrapperrel

2011-04-17 3 perc olvasási idő

Munkám során egyszer meg kellett oldanom azt, hogy a PHP-ből indított FTP feltöltés sávszélessége állítható legyen. Természetesen a legegyszerűbb mód az lett volna, ha tűzfalban, vagy routeren konfigurálják be ezt, de erre nem volt lehetőség. Végül olyan megoldást sikerült találnom, amivel nem csak az FTP feltöltés limitálható, hanem bármilyen művelet, ahol file resource-okkal dolgozunk.

Az ötlet

Az ötlet az volt, hogy magát a fájl olvasást lassítom be, és a használt file resource-t az ftp_fput függvénynek átadva elérem a kívánt hatást. Némi kutatás után arra jutottam, hogy streamwrapper kell nekem. A streamwrapper lényege az, hogy saját protokolt tudunk regisztrálni és kezelni. Bővebben: http://hu2.php.net/manual/en/class.streamwrapper.php Ha például a throttle:// protokollt szeretnénk lekezelni, akkor az elkészített streamwrapper osztályunkat be kell regisztrálni és a stream_open, stream_close, stb. metódusokat fogja hívni rendre az fopen, fclose, stb.

A limitálást végző osztályban az alábbi főbb műveleteket kell elvégezni:

  • Kívánt sávszélesség beállítása
  • Fájl megnyitása
  • Aktuális sávszélesség kiszámítása
  • Fájl olvasás, ha az aktuális sávszélesség a megengedett alatt van, ellenkező esetben egy kis várakozás

A megoldás

class StreamWrapper_FileThrottle {  
  const STREAM_NAME = 'throttle';  
  const BANDWIDTH = 'bandWidth';  
  /**
   * @var resource
   */  
  private $fileResource = null;  
  /**
   * @var float
   */  
  private $startTime = 0;  
  /**
   * @var int
   */  
  private $fileSize = 0;  
  /**
   * @var resource
   */  
  public $context;  
  /**
   * kB/s
   * 0 means unlimited bandwidth
   *  
   * @var int
   */  
  private $maxBandWidth = 0;  
  public function stream_open($path, $mode, $options, &$opened_path) {  
    $matches = array();  
    if (!preg_match('#[^:]+:\/\/(.*)#', $path, $matches)) {  
      throw new InvalidArgumentException("Invalid path '$path'");  
    }  
    $path = $matches[1];  
    $this->fileResource = @fopen($path, $mode);  
    if ($this->fileResource === false) {  
      throw new InvalidArgumentException("Invalid path '$path'");  
    }  
    $this->fileSize     = filesize($path);  
    $this->startTime    = microtime(true);  

    $contextOptions = stream_context_get_options($this->context);  
    if ( array_key_exists(self::STREAM_NAME, $contextOptions)  
      && array_key_exists(self::BANDWIDTH, $contextOptions[self::STREAM_NAME])) {  

      $this->maxBandWidth = $contextOptions[self::STREAM_NAME][self::BANDWIDTH];  
    }  
    return true;  
  }  
  public function stream_close() {  
    return fclose($this->fileResource);  
  }  
  private function getCurrentSpeed() {  
    $uploadedKBytes = ftell($this->fileResource) / 1024;  
    $seconds        = microtime(true) - $this->startTime;  
    return $uploadedKBytes / $seconds;  
  }  
  public function stream_read($count) {  
    while ($this->maxBandWidth !== 0 && $this->maxBandWidth < $this->getCurrentSpeed()) {  
      usleep(100);  
    }  
    return fread($this->fileResource, $count);  
  }  
  public function stream_write($data) {  
    throw new Exception("Writting is unsupported on 'throttle' stream");  
  }  
  public function stream_tell() {  
    return ftell($this->fileResource);  
  }  
  public function stream_eof() {  
    return feof($this->fileResource);  
  }  
  public function stream_seek($offset, $whence) {  
    throw new Exception("You can not use 'seek' method on throttle stream");  
  }  
}  

A sávszélesség határt stream contexten keresztül lehet átadni, melyhez hozzáfér a streamwrapper. A fájl megnyitása és az inicializálási rész a stream_open metódusban találhatók. Az aktuális sávszélességet (ami egész pontosan a fájl megnyitásától számított átlagos sávszélesség) a getCurrentSpeed metódus számolja. Mivel az ftell()-t használja, ezért a seekelés nem engedélyezett (hibás sávszélességet adna vissza).

Használat

Mint már említettem, a streamwrappert be kell regisztrálni, valamint context paraméterben át kell adnunk neki a megengedett sávszélesség értéket.

stream_wrapper_register("throttle", "StreamWrapper_FileThrottle");  
$opts = array(  
  'throttle' => array(  
    StreamWrapper_FileThrottle::BANDWIDTH => $maxBandwidth  
  )  
);  
$fp = fopen('throttle://' . $localFile, 'r', null, stream_context_create($opts));  
ftp_fput($ftpResource, $remoteFile, $fp, $mode); 
comments powered by Disqus