Back to business

Posted by Dan Sosedoff on January 14, 2010

Im back to my blog, which i havent updated since last october. Need to throw some more articles.

How to identify your clients` browser type

Posted by Dan Sosedoff on October 02, 2009

Some time ago i wrote a simple regex patterns to determine whether my client crawler bot, mobile client or just regular one. Easy to expand and to use.

function is_mobile($agent) {
    $pattern = '/(blackberry|motorokr|motorola|sony|windows ce|240x320|176x220|palm|mobile|iphone|ipod|symbian|nokia|samsung|midp)/i';
    return (bool)preg_match($pattern, $agent);
}
 
function is_crawler($agent) {
    pattern = '/(google|yahoo|baidu|bot|webalta|ia_archiver)/';
    return (bool)preg_match($pattern, $agent);
}

Rails-like PHP url router

Posted by Dan Sosedoff on September 20, 2009

My previous version of php url router was not good enough, so i`ve done some core modification to the class. Its more flexible now. The whole idea is very similar to Rails` and Merb`s url router (merb is a rails-fork). In fact, previous version doesn`t even support GET variables in the request uri. New version just merges variables from request string into the same parameters array where other variables are stored. Also there is a command “default_routes” that maps default routes like this “/:controller/:action/:id”. So, basically you have two options – use defaults & custom routes or use only custom, which means that all other requests will be ignored. Function default_routes must be declared last.

Here is the sources:

define('ROUTER_DEFAULT_CONTROLLER', 'home');
define('ROUTER_DEFAULT_ACTION', 'index');
 
class Router {
  public $request_uri;
  public $routes;
  public $controller, $controller_name;
  public $action, $id;
  public $params;
  public $route_found = false;
 
  public function __construct() {
    $request = $_SERVER['REQUEST_URI'];
    $pos = strpos($request, '?');
    if ($pos) $request = substr($request, 0, $pos);
 
    $this->request_uri = $request;
    $this->routes = array();
  }
 
  public function map($rule, $target=array(), $conditions=array()) {
    $this->routes[$rule] = new Route($rule, $this->request_uri, $target, $conditions);
  }
 
  public function default_routes() {
    $this->map('/:controller');
    $this->map('/:controller/:action');
    $this->map('/:controller/:action/:id');
  }
 
  private function set_route($route) {
    $this->route_found = true;
    $params = $route->params;
    $this->controller = $params['controller']; unset($params['controller']);
    $this->action = $params['action']; unset($params['action']);
    $this->id = $params['id']; 
    $this->params = array_merge($params, $_GET);
 
    if (empty($this->controller)) $this->controller = ROUTER_DEFAULT_CONTROLLER;
    if (empty($this->action)) $this->action = ROUTER_DEFAULT_ACTION;
    if (empty($this->id)) $this->id = null;
 
    $w = explode('_', $this->controller);
    foreach($w as $k => $v) $w[$k] = ucfirst($v);
    $this->controller_name = implode('', $w);
  }
 
  public function execute() {
    foreach($this->routes as $route) {
      if ($route->is_matched) {
        $this->set_route($route);
        break;
      }
    }
  }
}
 
class Route {
  public $is_matched = false;
  public $params;
  public $url;
  private $conditions;
 
  function __construct($url, $request_uri, $target, $conditions) {
    $this->url = $url;
    $this->params = array();
    $this->conditions = $conditions;
    $p_names = array(); $p_values = array();
 
    preg_match_all('@:([\w]+)@', $url, $p_names, PREG_PATTERN_ORDER);
    $p_names = $p_names[0];
 
    $url_regex = preg_replace_callback('@:[\w]+@', array($this, 'regex_url'), $url);
    $url_regex .= '/?';
 
    if (preg_match('@^' . $url_regex . '$@', $request_uri, $p_values)) {
      array_shift($p_values);
      foreach($p_names as $index => $value) $this->params[substr($value,1)] = urldecode($p_values[$index]);
      foreach($target as $key => $value) $this->params[$key] = $value;
      $this->is_matched = true;
    }
 
    unset($p_names); unset($p_values);
  }
 
  function regex_url($matches) {
    $key = str_replace(':', '', $matches[0]);
    if (array_key_exists($key, $this->conditions)) {
      return '('.$this->conditions[$key].')';
    } 
    else {
      return '([a-zA-Z0-9_\+\-%]+)';
    }
  }
}

Setup example:

$r = new Router(); // create router instance 
 
$r->map('/', array('controller' => 'home')); // main page will call controller "Home" with method "index()"
$r->map('/login', array('controller' => 'auth', 'action' => 'login'));
$r->map('/logout', array('controller' => 'auth', 'action' => 'logout'));
$r->map('/signup', array('controller' => 'auth', 'action' => 'signup'));
$r->map('/profile/:action', array('controller' => 'profile')); // will call controller "Profile" with dynamic method ":action()"
$r->map('/users/:id', array('controller' => 'users'), array('id' => '[\d]{1,8}')); // define filters for the url parameters
 
$r->default_routes();
$r->execute();

Usage example:

$router = new Router();
// ... some configs ...
$controller = $router->controller; // will return name as it appears in url, ex: 'user_images'
$controller = $router->controller_name; // will return processed name of controller
// for example, if class name in url is 'user_images', then 'controller_name' var will be UserImages
$router->action;
$router->id; // if parameter :id presents
$router->params; // array(...)
$router->route_matched; // true - if route found, false - if not

Small and useful class.

Date separated MySQL backups

Posted by Dan Sosedoff on September 18, 2009

Here is the bash shell script that makes archived dumps of your database server. All databases are separated from each other and stored into date based folders.

#!/bin/bash

MyUSER="root"
MyPASS=""
MyHOST="localhost"
NOW="$(date +"%d-%m-%Y")"
STOREDIR="/home/storage/backup/database/by_dates/$NOW"
DBLIST="$(mysql -u $MyUSER -h $MyHOST -Bse 'show databases')"

[ ! -d $STOREDIR ] && mkdir -p $STOREDIR || :

for db in $DBLIST
do
	FILE="$STOREDIR/$db.gz"
	mysqldump -u $MyUSER -h $MyHOST $db | gzip -9 > $FILE
done

Blog is back!

Posted by Dan Sosedoff on September 17, 2009

Recovered blog. Will start write some other stuff soon.

How to deploy Sinatra / Merb applications with nginx

Posted by Dan Sosedoff on July 04, 2009

Developing with Merb or Sinatra ? Like web server nginx ? Ok, lets go.

Basically, Merb/Sinatra uses thin (based on eventmachine) server module, so it is working as stand-alone application. So, there is a way to connect nginx with these applications. Very simple – all you need to proxy requests to backend server. And all static files will be served by nginx.

upstream your_app {
  server 127.0.0.1:YOUR_PORT_NUMBER;
}

server {
  server_name YOUR_SERVERNAME;
  listen 80;
  charset utf-8;
  client_max_body_size 64M; # maximum of proxied filesize (for uploads)

  location / {
    root /path/to/app/root;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect false;
    if (-f $request_filename) {
      break;
    }

    if (!-f $request_filename) {
      proxy_pass http://your_app;
      break;
    }
  }

  # static content (images, flash, styles, etc.)
  location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|js|swf)$ {
    root /path/to/your/public/dir;
    access_log off;
  }
}

Then, all you need – start your application in daemon mode. Dont forget to bind application server to listen only on local address (127.0.0.1).

Simple php url routing controller 1

Posted by Dan Sosedoff on July 04, 2009

!!! CONSIDER THIS AS AN UPDATE:Rails-like PHP Url Router

Here is simple url parser, that produces output parameters lile: controller, action, parameters. Also, it support simple virtual key rules, “rails” style.

For example, url like “http://foo.bar/book/view/12″ will produce parameters: controller = book, action = view, id = 12.

Here is php code:

<?
 
/* ------------------------------------------------------------- */
/* URL Router class */
/* ------------------------------------------------------------- */
 
class Router {
  static protected $instance;
  static protected $controller;
  static protected $action;
  static protected $params;
  static protected $rules;
 
  public static function getInstance() {
    if (isset(self::$instance) and (self::$instance instanceof self)) {
      return self::$instance;
    } else {
      self::$instance = new self();
      return self::$instance;
    }
  }
 
  private static function arrayClean($array) {
    foreach($array as $key => $value) {
      if (strlen($value) == 0) unset($array[$key]);
    }  
  }
 
  private static function ruleMatch($rule, $data) {    
    $ruleItems = explode('/',$rule); self::arrayClean(&$ruleItems);
    $dataItems = explode('/',$data); self::arrayClean(&$dataItems);
 
    if (count($ruleItems) == count($dataItems)) {
      $result = array();
 
      foreach($ruleItems as $ruleKey => $ruleValue) {
        if (preg_match('/^:[\w]{1,}$/',$ruleValue)) {
          $ruleValue = substr($ruleValue,1);
          $result[$ruleValue] = $dataItems[$ruleKey];
        }
        else {
          if (strcmp($ruleValue,$dataItems[$ruleKey]) != 0) {
            return false;
          }
        }
      }
 
      if (count($result) > 0) return $result;
      unset($result);
    }
    return false;
  }
 
  private static function defaultRoutes($url) {
    // process default routes
    $items = explode('/',$url);
 
    // remove empty blocks
    foreach($items as $key => $value) {
      if (strlen($value) == 0) unset($items[$key]);
    }
 
    // extract data
    if (count($items)) {
      self::$controller = array_shift($items);
      self::$action = array_shift($items);
      self::$params = $items;
    }
  }
 
  protected function __construct() {
    self::$rules = array();
  }
 
  public static function init() {
    $url = $_SERVER['REQUEST_URI'];
    $isCustom = false;
 
    if (count(self::$rules)) {
      foreach(self::$rules as $ruleKey => $ruleData) {
        $params = self::ruleMatch($ruleKey,$url);
        if ($params) {          
          self::$controller = $ruleData['controller'];
          self::$action = $ruleData['action'];
          self::$params = $params;
          $isCustom = true;
          break;
        }
      }
    }
 
    if (!$isCustom) self::defaultRoutes($url);
 
    if (!strlen(self::$controller)) self::$controller = 'home';
    if (!strlen(self::$action)) self::$action = 'index';
  }
 
  public static function addRule($rule, $target) {
    self::$rules[$rule] = $target;
  }
 
 
  public static function getController() { return self::$controller; }
  public static function getAction() { return self::$action; }
  public static function getParams() { return self::$params; }
  public static function getParam($id) { return self::$params[$id]; }
}
?>

Basic usage:

$router = Router::getInstance(); // init router
$router->addRule('/books/:id/:keyname',array('controller' => 'books', 'action' => 'view')); // add simple rule
// add some more rules
$router->init(); // execute router

So, as you can see, the router class can be accessible from anywhere in your code. Just because the class has beed designed as one-instance object, you cannot call for new instance. To get instance of router you need to call Router::getInstance() instead.
We have added simple rule for url`s like this ‘/books/12345/this-is-a-book-keyname’. Default routing system will identify this url incorrectly (controller => books, action => 12345). So the action 12345 not exists. That`s why we need to add custom rule. It will parse this url correctly: controller => books, action => view. And the rest of parameters will be automatically added to parameteres array. To access this array you need to call $router->getParams(); If you need to get just one parameter, call directly: $var = $router->getParam(0).

This router is really simple and can be used in small web sites that not require complete framework. Also it is fast.

Download file Router.php
View paste

Install Git on CentOS 5.2

Posted by Dan Sosedoff on June 28, 2009

First, we need to install all dependencies:

# yum install gettext-devel expat-devel curl-devel zlib-devel openssl-devel

Next, get the git 1.6.x sources:

# wget http://kernel.org/pub/software/scm/git/git-1.6.3.3.tar.gz

Then, unpack and cd into git sources folder and install it:

# make && make install & make clean

That`s it, now you`ll have git system ready to go.

Jabber bot with Ruby

Posted by Dan Sosedoff on May 20, 2009

Ruby has very powerful tools to create random jabber bots with XMPP protocol. In this article i`ll show just a small sample with a few commands available.

I got xmpp4r-simple library from Google Code page – http://code.google.com/p/xmpp4r-simple/. Its kinda old, not updated since 2006, but the sources are really easy to read and understand, and probably modify.

First, we need to install dependencies (gems):

$ gem install xmpp4r
$ gem install xmpp4r-simple

And now, the actual ruby code:

#!/usr/bin/ruby
require 'rubygems'
require 'xmpp4r'
require 'xmpp4r-simple'
 
$jabber_login = "YOUR_JABBER_ID_HERE" # test@jabber.org
$jabber_password = "YOUR_PASSWORD_HERE" 
$client = nil
 
# create jabber connection
def jabber_connect()
  begin
    conn = Jabber::Simple.new($jabber_login,$jabber_password)
    return conn
  rescue
    return nil
  end
end
 
# send message to jabber client
def jabber_respond(to, msg)
  $client.deliver(to,msg,:chat)	
end
 
# get sender jabber id
def jabber_get_jid(str)
  matches = str.match(/([a-z\d_.\-]{1,32})@([a-z\d.-]{1,32})\//i)
  return "#{matches[1]}@#{matches[2]}"
end
 
# ------------------------------------------------------------------- #
 
# send server time
def app_time(jid)
  jabber_respond(jid, "Server time is: #{Time.now}")
end
 
# send some 'help' :)
def app_help(jid)
  jabber_respond(jid, "Nobody can help you. You`re alone.")
end
 
# process received message
def app_parse_msg(jid, msg)
  cmd = msg.body.strip
  begin
    case cmd
      when /^help$/i then app_help(jid)
      when /^time$/i then app_time(jid)
      when /^jid$/i then jabber_respond(jid, "Your jabber id: #{jid}")
      else
        jabber_respond(jid, "Unknown command. Try something different.")
    end
  rescue Exception => ex
    jabber_respond(jid, "SYSTEM_ERROR: #{ex}")
  end
end
 
# ------------------------------------------------------------------- #
 
puts "Connecting to jabber server..."
$client = jabber_connect()
if $client
  puts "Connected. Waiting for messages..."
  loop do
    $client.received_messages do |message|
      jid = jabber_get_jid(message.from.to_s)
      puts "Received message from #{jid}: #{message.body}"
      app_parse_msg(jid, message)
    end
    sleep 0.1
  end
else
  puts "Cannot connect. Please try again later."
end

I think, the comments in source code are enough to understand what this bot supposed to do. There are 3 commands available: ‘help’, ‘time’, ‘jid’

Download script here – http://files.sosedoff.com/204ab61c/

WebDAV client in ruby 1

Posted by Dan Sosedoff on May 02, 2009

Here is a simple example how to make native WebDAV client with Ruby sockets. No additional gems or extensions needed – just all basic classes.

class WebDAV
	attr_reader :host, :port, :protocol, :chunk_size
	@socket = nil
 
	def initialize(host,port=80,protocol='HTTP/1.1',chunk=8096)
		@host = host.to_s
		@port = port.to_i
		@protocol = protocol
		@chunk_size = chunk.to_i
	end
 
	def build_header(method, path, content_length=nil)
		header = "#{method} #{path} #{@protocol} \r\n"
		header += "Content-Length: #{content_length}\r\n" if !content_length.nil?
		header += "Host: #{@host}\r\n"
		header += "Connection: close\r\n\r\n"
		return header
	end
 
	def request(method, path)
		open
		header = build_header(method, path)
		if @socket.write(header) == header.length then
			return @socket.gets.split[1]
		end
	end
 
	def delete(path)
		request('DELETE', path)
	end
 
	def head(path)
		request('HEAD', path)
	end
 
	def mkcol(path)
		request('MKCOL', path)
	end
 
	def put(path, localfile, auto_head=true)
		if !File.exists?(localfile) || !File.readable?(localfile)
			raise "File not exists or not accessible for reading!"
		end
 
		open
 
		datalen = File.size(localfile)
		header = build_header('PUT', path, datalen)
 
		begin
			if @socket.write(header) == header.length then
				written = 0
				File.open(localfile,'r') do |f| 
					until f.eof? do
						written += @socket.write(f.read(@chunk_size))
					end
				end
 
				if written == datalen
					close
					if !auto_head
						return true
					else
						return head(path)
					end
				end
			end
		rescue Exception => e
			puts e
			return false
		end
	end
 
	def open
		begin 
			@socket = TCPSocket.open(@host,@port)
			return true
		rescue Exception => e
			puts e
			return false
		end
	end
 
	def close
		begin
			return @socket.close
		rescue 
			return false
		end
	end
end

This class supports only basic http/dav methods (PUT, DELETE, MKCOL, HEAD) and can be extended very easily and designed to work with all files, reading them by small chunks (default is 8096 bytes).
Im using this class sometimes with nginx.

Deps:

require 'socket'
require 'digest'

Usage:

# create connection
conn = WebDAV.new('your.host.com')
 
# upload file (without autocheck), return true/false value
result = conn.put('/test.mp3','/home/.../..../..../file.mp3', false)
 
# upload file with autocheck, returns http response code (201, 404, ... ) so you`ll know what exactly happened
result = conn.put('/test2.mp3','/home/.../file.mp3')

Also, here is a wrapper class to produce MD5, SHA1 file hashes that supports big files.

class FileHash 
	def self.md5(path)
		d = Digest::MD5.new
		File.open(path,'r') do |f| 
			d.update(f.read(8192)) until f.eof?
		end
		return d.hexdigest
	end
 
	def self.sha1(path)
		d = Digest::SHA1.new
		File.open(path,'r') do |f| 
			d.update(f.read(8192)) until f.eof?
		end
		return d.hexdigest
	end
end

Usage:

FileHash.md5('/path/to/file')
FileHash.sha1('/path/to/file')

This webdav class not pretending to be stable in production environment, but can be useful for some “one-time” tasks with less code.