Posted by Dan Sosedoff
on July 03, 2010
Most of Ruby web frameworks have terminal logging in development environments. It makes application debugging process much easier than using file logging. Especially for AJAX requests. Of course there is simple solution – use Firebug and javascript console. Unfortunately it is not that convenient.
So, one day i came up with idea to make the same ruby-like style of application logging, even with colorized output.
I decided to use EventMachine library as a server platform. And API is based on JSON packets, via json/pure. Simple, isnt it?
And i called it DebugServer. Pretty understandable
Installation
To install just type
$ sudo gem install debugserver
And for information type:
Usage: debugserver [options]
-h, --host HOSTNAME Server hostname
-p, --port PORT Server port
-i, --info Get usage information
By default it will start on localhost:9000.
Usage in PHP
First, download library from GitHub repository: http://github.com/sosedoff/debugclient-php
And then:
// place it into app initialization file
$debug = DebugClient::instance();
$debug->connect();
// .... and write it to terminal output
$obj = array(
'id' => rand(0, 0xFFFF),
'name' => 'Sample name',
'time' => strftime('%m-%d-%Y', time())
);
// these functions are globally defined
debug_clear(); // clear terminal
debug('This is a plain text message.');
debug_info('This is an informational message.');
debug_warning('This is a warning message.');
debug_error('This is an error message.');
debug_dump($obj);
// optional
$debug->close();
Results:

Sources
DebugServer: http://github.com/sosedoff/debugserver
PHP Client: http://github.com/sosedoff/debugclient-php
Posted by Dan Sosedoff
on June 18, 2010
Made a simple gem for website sitemap generation. Could be used in any Ruby/Rails/Merb/Sinatra application. It does not have any caching in that case if you want to use framework built-in cache methods.
Installation:
$ sudo gem install xml-sitemap
Example
pages = Page.all(:order => [:updated_at.desc] # DM model
map = XmlSitemap::Map.new('somedomain.com') do |m|
m.add(:url => '/', :period => :daily, :priority => 1.0)
m.add(:url => '/contractors', :period => :daily, :priority => 1.0)
pages.each do |p|
m.add(
:url => url_for_page(p),
:updated => p.updated_at,
:priority => 0.5,
:period => :never
)
end
end
# render the sitemap
puts map.render
Sinatra Example
# ... your code
get '/sitemap.xml' do
map = XmlSitemap::Map.new('domain.com') do |m|
m.add(:url => '/')
m.add(:url => '/posts', :period => :weekly)
end
headers['Content-Type'] = 'text/xml'
map.render
end
# ... more code
Options
:url – page path, relative to domain (ex.: /test), String.
:period – freqchange attribute, Symbol, :none, :never, :always, :hourly, :daily, :weekly, :monthly, :yearly
:priority – priority attribute, Float class,(0.0..1.0)
:updated – (optional) last_update attribute, Time class
Source Code
http://github.com/sosedoff/xml-sitemap
Posted by Dan Sosedoff
on June 15, 2010
Amazon has an awesome image service. You can use their product images on your site, adjusting them for you needs. All you have to know – one image url of your product. Having that string will provide you an access to its dynamic image scaling service which i had to use recently.
So, lets say you have books on your website, but you dont have any good images for them. There is 2 ways to solve your problem: 1) download it from whatever place and resize 2) use amazon!
Here goes small overview.
Unfortunately, i didnt have any time to play with image service for different countries, but i assume that wont change that much. Lets take a look on a regular image:
http://ecx.images-amazon.com/images/I/41ygBmdaIfL._SL500_SS100_.jpg
It has different parts:
1) URL base: http://ecx.images-amazon.com/images/I/
2) Image code: 41ygBmdaIfL
3) Size format (surrounded by underscores): _SL500_SS100_
4) Format: jpg/gif/png
Some words about image format. It can vary from square thumbnails to images with specific max width and height. For example: _SX100_ will produce image that 100 pixels wide, height will be calculated proportionally. SH100 will give opposite result, scaled by 100 pixels maximum height, SS100 – 100×100 pixels thumbnail. And so on, you can find other similar crop codes while exploring amazon store on different pages, all you need is to take a look on image sources.
Now, we need to use this with Ruby:
require 'net/http'
module Amazon
# parse amazon image url and get image code and extension
def self.parse_image(url)
result = url.scan(/^http:\/\/ecx.images-amazon.com\/images\/I\/([a-z0-9\-\%]{1,})(.*)_.(jpg|jpeg|gif)/i)
unless result.nil?
unless result[0].nil?
match = result.first
return {:code => match.first.to_s, :extension => match.last.to_s}
end
end
end
# make a new amazon image url based on code and size
def self.make_image(image, size)
"http://ecx.images-amazon.com/images/I/#{image[:code]}._#{size.upcase}.#{image[:extension]}"
end
# check if actual image exists
def self.check_image(url)
begin
uri = URI.parse(url)
req = Net::HTTP::Get.new(uri.path)
res = Net::HTTP.start(uri.host, uri.port) { |http| http.request(req) }
return res.code == '200' && res.content_length.to_i > 0
rescue Exception
false
end
end
end
And usage:
url = 'http://ecx.images-amazon.com/images/I/51O65dIoZCL._SX117_.jpg'
info = Amazon.parse_image(url)
unless info.nil?
new_url = Amazon.make_image(info, 'sx100')
if Amazon.check_image(new_url)
puts "Cool! Resized image: #{new_url}"
else
puts "Sorry, this image does not exist!"
end
else
puts "Cant identify image!"
end
Some notes about the process. The only reason why method “check_image” uses GET method instead of HEAD is because if image cannot be generated or not found in amazon`s cache the response is still valid sometimes. I`ve checked it on 50k images and sometimes HEAD request indicates that response is valid while it not supposed to. Otherwise i would use HEAD.
Posted by Dan Sosedoff
on June 13, 2010
While working on one of the projects, i tried to find multi-purpose HTTP request class that can use different network interfaces/ip addresses with retry option (if connection slow or server not responding for some reason).
Here is a small class wrapper build on top of Ruby Curb implemented as a module:
module ApiRequest
USER_AGENTS = [
'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727)',
'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3',
'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.70 Safari/533.4',
'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.2) Gecko/20100323 Namoroka/3.6.2',
'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.9) Gecko/20100401 Ubuntu/9.10 (karmic) Firefox/3.5.9'
]
CONNECTION_TIMEOUT = 10
@@interfaces = []
# get random user-agent string for usage
def random_agent
USER_AGENTS[rand(USER_AGENTS.size-1)]
end
# get random IP/network interface specified in @@interfaces
def random_interface
size = @@interfaces.size
size > 0 ? @@interfaces[rand(size-1)] : nil
end
# perform request, assign_to - specify network interface/ip
def perform(url, assign_to=nil)
puts url
interface = assign_to.nil? ? self.random_interface : assign_to
req = Curl::Easy.new(url)
req.timeout = CONNECTION_TIMEOUT
req.interface = interface unless interface.nil?
req.headers['User-Agent'] = self.random_agent
begin
req.perform
if req.response_code == 200
return req.downloaded_bytes > 0 ? req.body_str : nil
else
nil
end
rescue Exception
return nil
end
end
# perform request by number of attempts
def fetch(url, attempts=3)
result = nil
1.upto(attempts) do |a|
result = self.perform(url)
break unless result.nil?
end
return result
end
end
And sample usage:
class TestRequest
include ApiRequest
def foo
body = self.fetch('http://google.com')
end
end
If module variable “@@interfaces” is array of ip addresses or network interfaces then one of them (randomly selected) will be used to perform request. Also, function “fetch” has parameter “attempts” which set to 3 by default. It means that operation will be invoked n times until result is downloaded from url. Otherwise – it returns nil.
Function perform has a parameter “assign_to” (which it not used in “fetch” function) that allows to bind request to specified interface. It is useful if you have situation when you might use different workers that bound to exact interface or just one that uses random ip`s. Also, class ApiRequest has a list of user agents which it uses randomly for each performed request.
Pastie: http://pastie.org/private/j19j3hbebte9bjqaydslmg
Posted by Dan Sosedoff
on June 09, 2010
At some point you will find that you have reached requests per IP limit while using some API or crawling resources. And if you`re doing it via standard Net::HTTP you`ll face the problem that you cannot assign request class to specified network interface (or IP). Bummer? No. Even if you cant do it with core class you might take a look on Curb – libcurl ruby binding. It has everything that you need to make regular get/post/etc requests. And of course – easy.
A simple example (real ip`s are changed):
require 'rubygems'
require 'curb'
ip_addresses = [
'1.1.1.1',
'2.2.2.2',
'3.3.3.3',
'4.4.4.4',
'5.5.5.5'
]
ip_addresses.each do |ip|
req = Curl::Easy.new('http://www.ip-adress.com/')
req.interface = ip
req.perform
result_ip = req.body_str.scan(/<h2>My IP address is: ([\d\.]{1,})<\/h2>/).first
puts("for #{ip} got response: #{result_ip}")
end
Output (ip`s are changed):
for 1.1.1.1 got response: 1.1.1.1
for 2.2.2.2 got response: 2.2.2.2
for 3.3.3.3 got response: 3.3.3.3
for 4.4.4.4 got response: 4.4.4.4
for 5.5.5.5 got response: 5.5.5.5
At least its working. Havent done any performance tests.
Sample on pastie: http://pastie.org/private/afxlcuk1npwjov3wer5hw
Posted by Dan Sosedoff
on June 01, 2010
If you develop some console application you might want your output be more informative, have different colors for operations or logging purposes. It is possible to do with general ANSI escape codes, which are supported by most common console terminals.
The ASCII escape structure is pretty simple. It begins with “ESC” symbol, which is code 27 in ASCII table. Then “[” symbol. Parameters that goes after “[” symbol are separated by “;” and finally ends with closing sequence: “ESC[0m“.
You can extend basic ruby String class with following code:
class String
# colorize functions
def red; colorize(self, "\e[1m\e[31m"); end
def green; colorize(self, "\e[1m\e[32m"); end
def dark_green; colorize(self, "\e[32m"); end
def yellow; colorize(self, "\e[1m\e[33m"); end
def blue; colorize(self, "\e[1m\e[34m"); end
def dark_blue; colorize(self, "\e[34m"); end
def pur; colorize(self, "\e[1m\e[35m"); end
def colorize(text, color_code) "#{color_code}#{text}\e[0m" ; end
end
And sample usage code:
puts "Starting some job...".blue
puts "Processing thing 1 [#{"OK".green}]"
puts "Processing thing 2 [#{"FAIL".red}]"
puts "Oooops! This is a warning!".yellow
puts "Another color!".pur
The output:

Nice and useful.
Posted by Dan Sosedoff
on May 03, 2010
Once you need to test mailers within your Sinara, Rails or Merb application you wish to see a real output. No need to setup delivery via SMTP. Just create an executable ruby script somewhere, for example: /usr/bin/fake-sendmail.sh with following content:
$ touch /usr/bin/fake-sendmail.sh
$ chmod +x /usr/bin/fake-sendmail.sh # make it executable (will require root priv.)
#!/usr/bin/ruby
path = "/tmp/fake-mailer"
Dir.mkdir(path) if !File.exists?(path)
File.open("#{path}/#{Time.now.to_i}.txt", "w") do |f|
f.puts ARGV.inspect
$stdin.each_line { |line| f.puts line }
end
And then configure your mailing system:
1. Sinatra
There is a plugin for Sinatra (ported from Merb, http://github.com/foca/sinatra-mailer),
Sinatra::Mailer.config = {:sendmail_path => "/usr/bin/fake-sendmail.sh"}
Sinatra::Mailer.delivery_method = :sendmail
2. Merb
Edit your development configuration file (config/environments/development.rb)
Merb::BootLoader.after_app_loads do
Merb::Mailer.delivery_method = :sendmail
Merb::Mailer.config = { :sendmail_path => '/usr/bin/fake-sendmail.sh' }
end
3. Rails.
Edit your development environment file with following options:
config.action_mailer.delivery_method = :sendmail
config.action_mailer.sendmail_settings = {:location => "/usr/bin/fake-sendmail.sh"}
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/
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.
Posted by Dan Sosedoff
on April 16, 2009
Here is two small functions to convert IP addresses from string representation to integer and vice versa.
def inet_aton(ip)
ip.split(/\./).map{|c| c.to_i}.pack("C*").unpack("N").first
end
def inet_ntoa(n)
[n].pack("N").unpack("C*").join "."
end