Net::HTTP Alternatives

Here at Optoro we’ve been building some external tools that interact with our website via http. For simplicity’s sake, we’ve been using curl.

I was discussing this with some friends when someone recommended I check out Curb. I was already in the middle of transitioning to HTTParty, due to its improved syntax, but I figured I’d check them all out in a head-to-head HTTP deathmatch.

I wrote this simple Sinatra app to hit for testing purposes:

require 'rubygems'
require 'sinatra'

get '/' do
  "Hi"
end

post '/' do
  "HiPost"
end

And then a benchmark suite:

require 'rubygems'
require 'httparty'
require 'curb'
require 'net/http'
require 'benchmark'
include Benchmark

RUNS = 1000

url = 'http://localhost:4567/'
puts "Measuring GETs"
Benchmark.benchmark(" "*10 + CAPTION, 10, FMTSTR) do |x|
  x.report("Net::HTTP") do 
    RUNS.times do 
      # NOTE: This works slightly faster without URI.parse, but I'm trying to make this a little more real-world.
      Net::HTTP.get URI.parse(url)
    end
  end

  x.report("HTTParty") do
    RUNS.times do
      HTTParty.get(url)
    end
  end

  x.report("Curb") do
    RUNS.times do
      Curl::Easy.perform(url)
    end
  end

  # NOTE: This situation might not be typical, but it is a lot faster.
  x.report("Curb-reuse") do
    c = Curl::Easy.new
    RUNS.times do
      c.url = url
      c.perform
    end
  end

  x.report("curl") do
    RUNS.times do
      `curl "#{url}" 2>/dev/null`
    end
  end
end


params = {'q' => 'test'}
puts "Measuring POSTs"
Benchmark.benchmark(" "*10 + CAPTION, 10, FMTSTR) do |x|
  x.report("Net::HTTP") do 
    RUNS.times do 
      Net::HTTP.post_form URI.parse(url), params
    end
  end

  x.report("HTTParty") do
    RUNS.times do
      HTTParty.post(url, :body => params)
    end
  end

  x.report("Curb") do
    curb_params = params.collect{|(k,v)| Curl::PostField.content(k, v) }
    RUNS.times do
      Curl::Easy.http_post(url, curb_params)
    end
  end

  x.report("Curb-reuse") do
    c = Curl::Easy.new
    curb_params = params.collect{|(k,v)| Curl::PostField.content(k, v) }
    RUNS.times do
      c.url = url
      c.http_post curb_params
    end
  end

  x.report("curl") do
    curl_params = params.collect{|(k,v)| "-F \"#{k}=#{v}\"" }.join(" ")
    RUNS.times do
      `curl -X POST -s #{curl_params} "#{url}" 2>/dev/null`
    end
  end
end

Here are the results:

$ ruby test_http_clients.rb 

Measuring GETs
                user     system      total        real
Net::HTTP   0.520000   0.200000   0.720000 (  1.334052)
HTTParty    0.670000   0.250000   0.920000 (  1.616191)
Curb        0.330000   0.200000   0.530000 (  1.477893)
Curb-reuse  0.110000   0.040000   0.150000 (  0.830893)
curl        0.060000   0.980000   1.040000 (  8.760388)

Measuring POSTs
                user     system      total        real
Net::HTTP   0.730000   0.180000   0.910000 (  1.577040)
HTTParty    0.780000   0.240000   1.020000 (  1.677723)
Curb        0.370000   0.150000   0.520000 (  1.425708)
Curb-reuse  0.160000   0.030000   0.190000 (  0.849151)
curl        0.110000   1.130000   1.240000 (1202.826407)

Clearly, libcurl-based Curb is the fastest alternative. It’s not all that much faster than (pure-ruby) HTTParty, though, so I think the improved syntax of HTTParty will keep me on that. What’s truly interesting though is how slow shelling-out to curl is. Even though the ‘total’ time is negligibly more, the ‘real’ time it takes to open that subprocesses and execute curl is far longer, 8.7 seconds total for plain GETs, and a whopping 1202 seconds for POSTs.

comments powered by Disqus