Last active
August 29, 2015 13:57
-
-
Save hbin/9776582 to your computer and use it in GitHub Desktop.
Net HTTP Benchmark(net/http, open-uri, curb, typhoeus)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rack' | |
require 'rack/handler/webrick' | |
require 'net/http' | |
# The code for this is inspired by Capybara's server: | |
# http://github.com/jnicklas/capybara/blob/0.3.9/lib/capybara/server.rb | |
class LocalhostServer | |
READY_MESSAGE = "Server ready" | |
class Identify | |
def initialize(app) | |
@app = app | |
end | |
def call(env) | |
if env["PATH_INFO"] == "/__identify__" | |
[200, {}, [LocalhostServer::READY_MESSAGE]] | |
else | |
@app.call(env) | |
end | |
end | |
end | |
attr_reader :port | |
def initialize(rack_app, port = nil) | |
@port = port || find_available_port | |
@rack_app = rack_app | |
concurrently { boot } | |
wait_until(10, "Boot failed.") { booted? } | |
end | |
private | |
def find_available_port | |
server = TCPServer.new('127.0.0.1', 0) | |
server.addr[1] | |
ensure | |
server.close if server | |
end | |
def boot | |
# Use WEBrick since it's part of the ruby standard library and is available on all ruby interpreters. | |
options = { :Port => port } | |
options.merge!(:AccessLog => [], :Logger => WEBrick::BasicLog.new(StringIO.new)) unless ENV['VERBOSE_SERVER'] | |
Rack::Handler::WEBrick.run(Identify.new(@rack_app), options) | |
end | |
def booted? | |
res = ::Net::HTTP.get_response("localhost", '/__identify__', port) | |
if res.is_a?(::Net::HTTPSuccess) or res.is_a?(::Net::HTTPRedirection) | |
return res.body == READY_MESSAGE | |
end | |
rescue Errno::ECONNREFUSED, Errno::EBADF | |
return false | |
end | |
def concurrently | |
if should_use_subprocess? | |
pid = Process.fork do | |
trap(:INT) { ::Rack::Handler::WEBrick.shutdown } | |
yield | |
exit # manually exit; otherwise this sub-process will re-run the specs that haven't run yet. | |
end | |
at_exit do | |
Process.kill('INT', pid) | |
begin | |
Process.wait(pid) | |
rescue Errno::ECHILD | |
# ignore this error...I think it means the child process has already exited. | |
end | |
end | |
else | |
Thread.new { yield } | |
end | |
end | |
def should_use_subprocess? | |
# !ENV['THREADED'] | |
false | |
end | |
def wait_until(timeout, error_message, &block) | |
start_time = Time.now | |
while true | |
return if yield | |
raise TimeoutError.new(error_message) if (Time.now - start_time) > timeout | |
sleep(0.05) | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'json' | |
require 'zlib' | |
require 'sinatra/base' | |
require 'rack/typhoeus' | |
TESTSERVER = Sinatra.new do | |
set :logging, false | |
use Rack::Typhoeus::Middleware::ParamsDecoder | |
fail_count = 0 | |
post '/file' do | |
{ | |
'content-type' => params[:file][:type], | |
'filename' => params[:file][:filename], | |
'content' => params[:file][:tempfile].read, | |
'request-content-type' => request.env['CONTENT_TYPE'] | |
}.to_json | |
end | |
get '/multiple-headers' do | |
[200, { 'Set-Cookie' => %w[ foo bar ], 'Content-Type' => 'text/plain' }, ['']] | |
end | |
get '/fail/:number' do | |
if fail_count >= params[:number].to_i | |
"ok" | |
else | |
fail_count += 1 | |
error 500, "oh noes!" | |
end | |
end | |
get '/fail_forever' do | |
error 500, "oh noes!" | |
end | |
get '/redirect' do | |
redirect '/' | |
end | |
get '/bad_redirect' do | |
redirect '/bad_redirect' | |
end | |
get '/auth_basic/:username/:password' do | |
@auth ||= Rack::Auth::Basic::Request.new(request.env) | |
# Check that we've got a basic auth, and that it's credentials match the ones | |
# provided in the request | |
if @auth.provided? && @auth.basic? && @auth.credentials == [ params[:username], params[:password] ] | |
# auth is valid - confirm it | |
true | |
else | |
# invalid auth - request the authentication | |
response['WWW-Authenticate'] = %(Basic realm="Testing HTTP Auth") | |
throw(:halt, [401, "Not authorized\n"]) | |
end | |
end | |
get '/auth_ntlm' do | |
# we're just checking for the existence if NTLM auth header here. It's validation | |
# is too troublesome and really doesn't bother is much, it's up to libcurl to make | |
# it valid | |
response['WWW-Authenticate'] = 'NTLM' | |
is_ntlm_auth = /^NTLM/ =~ request.env['HTTP_AUTHORIZATION'] | |
true if is_ntlm_auth | |
throw(:halt, [401, "Not authorized\n"]) if !is_ntlm_auth | |
end | |
get '/gzipped' do | |
req_env = request.env.to_json | |
z = Zlib::Deflate.new | |
gzipped_env = z.deflate(req_env, Zlib::FINISH) | |
z.close | |
response['Content-Encoding'] = 'gzip' | |
gzipped_env | |
end | |
get '/**' do | |
sleep params["delay"].to_i if params.has_key?("delay") | |
request.env.merge!(:body => request.body.read).to_json | |
end | |
head '/**' do | |
sleep params["delay"].to_i if params.has_key?("delay") | |
end | |
put '/**' do | |
request.env.merge!(:body => request.body.read).to_json | |
end | |
post '/**' do | |
request.env.merge!(:body => request.body.read).to_json | |
end | |
delete '/**' do | |
request.env.merge!(:body => request.body.read).to_json | |
end | |
patch '/**' do | |
request.env.merge!(:body => request.body.read).to_json | |
end | |
options '/**' do | |
request.env.merge!(:body => request.body.read).to_json | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'curb' | |
require 'typhoeus' | |
require 'net/http' | |
require 'open-uri' | |
require 'benchmark' | |
URL = "http://localhost:300" | |
hydra = Typhoeus::Hydra.new(max_concurrency: 3) | |
if defined? require_relative | |
require_relative 'localhost_server.rb' | |
require_relative 'server.rb' | |
else | |
require 'localhost_server.rb' | |
require 'server.rb' | |
end | |
LocalhostServer.new(TESTSERVER.new, 3000) | |
LocalhostServer.new(TESTSERVER.new, 3001) | |
LocalhostServer.new(TESTSERVER.new, 3002) | |
def url_for(i) | |
"#{URL}#{i%3}/" | |
end | |
Benchmark.bm do |bm| | |
[1000].each do |calls| | |
puts "[ #{calls} requests ]" | |
bm.report("net/http ") do | |
calls.times do |i| | |
uri = URI.parse(url_for(i)) | |
Net::HTTP.get_response(uri) | |
end | |
end | |
bm.report("open ") do | |
calls.times do |i| | |
open(url_for(i)) | |
end | |
end | |
bm.report("curb ") do | |
calls.times do |i| | |
Curl.get(url_for(i)) | |
end | |
end | |
bm.report("request ") do | |
calls.times do |i| | |
Typhoeus::Request.get(url_for(i)) | |
end | |
end | |
bm.report("hydra ") do | |
calls.times do |i| | |
hydra.queue(Typhoeus::Request.new(url_for(i))) | |
end | |
hydra.run | |
end | |
bm.report("hydra memoize ") do | |
Typhoeus::Config.memoize = true | |
calls.times do |i| | |
hydra.queue(Typhoeus::Request.new(url_for(i))) | |
end | |
hydra.run | |
Typhoeus::Config.memoize = false | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Ubuntu 13.10 64-bit, Intel® Core™ i5-2450M CPU @ 2.50GHz × 4, 8 GB Memory, SSD
➜ ~ ruby vs_nethttp.rb
user system total real
[ 1000 requests ]
net/http 6.860000 0.990000 7.850000 ( 7.383645)
open 7.610000 0.930000 8.540000 ( 8.034777)
curb 8.370000 2.690000 11.060000 ( 90.985541)
request 6.140000 0.380000 6.520000 ( 33.269623)
hydra 7.260000 0.830000 8.090000 ( 14.778413)
hydra memoize 0.120000 0.010000 0.130000 ( 0.166089)