Yii ConsoleApplication and Gearman Workers PSD to JPEG conversation

Hi. I am going to show small example of how I use gearman with my Yii applications.
If someone doesn’t know what is gearman here is quick explanation from the official website http://gearman.org

A Gearman powered application consists of three parts: a client, a worker, and a job server. The client is responsible for creating a job to be run and sending it to a job server. The job server will find a suitable worker that can run the job and forwards the job on. The worker performs the work requested by the client and sends a response to the client through the job server. Gearman provides client and worker APIs that your applications call to talk with the Gearman job server (also known as gearmand) so you don’t need to deal with networking or mapping of jobs. Internally, the gearman client and worker APIs communicate with the job server using TCP sockets.

Our example application converts psd to jpeg. We have to install gearman job server, gearman pecl extension and ImageMagick on the server.
Continue reading Yii ConsoleApplication and Gearman Workers PSD to JPEG conversation

Gearman воркер для скачивания файлов по URl

Gearman, сервер организации и распределения задач, более подробно можно узнать на оффициальном сайте http://gearman.org/

Данную статью я пишу для тех кто уже знает для чего он нужен и как его устанавливать.  В одном из моих проектов gearman воркеры скачивают файлы по ссылке. Также они во время работы отсылают статусы о прогрессе. Вы можете запустить нужно количество воркеров в зависимости от доступного интернет канала и конфигурации серверов.

Собственно сам класс воркера

download_size = 0;
		$this->downloaded = 0;
		$this->username = '';
		$this->pswd = '';
		$this->referrer = '';
		
	}
	private function closefp() {
		@fclose($this->tmp_file_pointer);
	}
	
	private	function remote_filesize()
	{	
		ob_start();
		$ch = curl_init($this->url);
		curl_setopt($ch, CURLOPT_HEADER, 1);
		curl_setopt($ch, CURLOPT_NOBODY, 1);
	    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
	    curl_setopt($ch, CURLOPT_REFERER, $this->referrer);
	    
		if(!empty($this->username) && !empty($this->pswd)) {
			curl_setopt($ch, CURLOPT_USERPWD, $this->username.':'.$this->pswd);
		}
	
		$ok = curl_exec($ch);
		
		curl_close($ch);
		$head = ob_get_contents();
		ob_end_clean();
	
		$regex = '/Content-Length:\s([0-9].+?)\s/';
		$count = preg_match($regex, $head, $matches);
	
		return isset($matches[1]) ? $matches[1] : 0;
	}
	
	
	function fetchfile($job) {
		$this->resetvalues();
		
		$this->job = $job;
		$workload= $this->job->workload();
 	
		echo "Received job: " . $this->job->handle() . "\n";
		
		$args = json_decode($workload);
		
		if(!isset($args->url) || !isset($args->tmp_file)) {
			print "Url and tmp_file are required";
			return;
		}
		
		$this->url = $args->url;
		$this->pswd = (isset($args->password))? $args->password : '';
		$this->username = (isset($args->username))? $args->username : '';
		$this->referrer = (isset($args->referrer)) ? $args->referrer : $this->url;
		
		$this->download_size = $this->remote_filesize();
		
		if($this->download_size==0) {
			$this->resetvalues();
			print "Can't get size header of the remote file";
			return;
		}
		
		$this->tmp_file_name = $args->tmp_file;
		$this->tmp_file_pointer = fopen($this->tmp_file_name, "wb");
		
		if($this->tmp_file_pointer)
			$this->fetchurl();
		else {
				$this->resetvalues();
				print("Couldn't open tmp file");
				return;
			 }
	}
	
	function percent($num_amount, $num_total) {
		$count1 = $num_amount / $num_total;
		$count2 = $count1 * 100;
		$count = ceil($count2);
		return $count;
	}

	function fetchurl() {
	
		$ch = curl_init($this->url);	
		curl_setopt($ch, CURLOPT_NOPROGRESS, true);
		curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($this, 'fetchcallback'));
		curl_setopt($ch, CURLOPT_REFERER, $this->referrer);
		
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
		
		if(!empty($this->username) && !empty($this->pswd)) {
			curl_setopt($ch, CURLOPT_USERPWD, $this->username.':'.$this->pswd);
		}
		
		curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'readHeader'));
		curl_exec($ch);
		curl_close($ch);
	}
	
	function readHeader($ch, $headers_text) {
		return strlen($headers_text);
	}
	
	function fetchcallback($ch, $str) {
			$length =  strlen($str);
			
			$this->downloaded +=$length;
			if(!fwrite($this->tmp_file_pointer,$str)) {
				$this->resetvalues();
				print "Can't write to tmp file\n";
				return;
			} 
			
			if($this->downloaded >= $this->download_size) {
				 $this->resetvalues();
				 $this->job->sendStatus(100,100);
				 $this->job->sendComplete();
				  
				 $this->closefp();			
				 print "100% Done\n";	
				 return;
			}		
			
			if($this->last_part_time > time()- 2) // 2 seconds
			return $length;
			
			$this->last_part_time = time();
			$this->job->sendStatus($this->percent($this->downloaded, $this->download_size) ,100);

			print $this->percent($this->downloaded, $this->download_size)."%...";
			return $length;	
	}
	
}

Как видите для скачивания используется curl.
Далее прилагаю код демона, который поднимает вышеуказанный класс. У меня это реализовано как консольная комманда Yii фреймворка, но вы можете реализовать демон как угодно.

Версия с использованием Yii

addServer(); 
		$worker->addFunction("fetchfile", array($worker, "fetchfile")); 
		
		
		while (1)
		{
		  print "Waiting for job...\n";
		 
		  $ret= $worker->work();
		  if ($worker->returnCode() != GEARMAN_SUCCESS)
		    break;
		}

	}

}

Standalone версия


addServer(); 
		$worker->addFunction("fetchfile", array($worker, "fetchfile")); 
		
		
		while (1)
		{
		  print "Waiting for job...\n";
		 
		  $ret= $worker->work();
		  if ($worker->returnCode() != GEARMAN_SUCCESS)
		    break;
		}

Запуск standalone версии


php worker.php

Запус версии с использованием Yii

php dispatch.php Fetch

Ну и наконец чтобы статья была исчерпывающей я приведу скрипт (точку входа) dispatch.php для Yii. В принципе он очень похож на обычный index.php для Yii application

require_once(dirname(__FILE__).'/framework/yii.php');
$config=dirname(__FILE__).'/protected/config/console.php';

// remove the following line when in production mode
defined('YII_DEBUG') or define('YII_DEBUG',true);

Yii::createConsoleApplication($config)->run();

Нужно только создать отдельный конфиг для консоли. Как минимум скопируйте содержимое main config
Все, будем считать что наши демоны 🙂 запущены и ждут камманды от gearman jоb сервера чтобы начать работу.

Даем комманду job серверу

addServer();

if(isset($_GET['handle'])){
	$handle = $_GET['handle'];
	$status = $client->jobStatus($handle);
	print_r($status);
	die;
}

$params = array(
		'referrer'=>'',
		'tmp_file'=>dirname(__FILE__).'/test.file',
		'url'=>'http://dl1.overload.in.ua/files/2050/SOD3z9jF3LiXdxO/file.zip',
		);

$params = json_encode($params);
$handle = $client->doBackground("fetchfile", $params);
header('Location: index.php?handle='.urlencode($handle));

В примере идет подключение к job серверу, дается фоновая команда с параметрами. Далее работа скрипта заканчивается и идет редирект с параметром именем задачи job-handle
Далее по этому параметру скрипт может следить за прогрессом выполнения именно этого таска.

Конец.