Your IP : 3.135.249.119
#!/usr/bin/env ruby
# A simple pure-Ruby HTTP server, meant as a helper tool in benchmarks.
# It supports HTTP keep-alive and it supports forwarding the request to
# another server.
require 'thread'
require 'socket'
require 'optparse'
class TestServer
REQUEST =
"GET / HTTP/1.1\r\n" <<
"Connection: Keep-Alive\r\n" <<
"Host: 127.0.0.1:3001\r\n" <<
"User-Agent: ApacheBench/2.3\r\n" <<
"Accept: */*\r\n\r\n"
RESPONSE =
"HTTP/1.1 200 OK\r\n" <<
"Status: 200 OK\r\n" <<
"Content-Type: text/plain\r\n" <<
"Content-Length: 3\r\n" <<
"Connection: keep-alive\r\n" <<
"\r\n" <<
"ok\n"
def initialize(options = {})
@options = options
@options[:transport] ||= :tcp
@options[:protocol] ||= :http
@options[:port] ||= 3000
@options[:file] ||= './socket'
@options[:threads] ||= 2
@options[:processes] ||= 2
@forward = @options[:forward]
@forward_transport = @options[:forward_transport]
@forward_file = @options[:forward_file]
@forward_port = @options[:forward_port]
@forward_keep_alive = @options[:forward_keep_alive]
end
def run
case @options[:transport]
when :tcp
@server = TCPServer.new('127.0.0.1', @options[:port])
@server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
puts "Listening on http://127.0.0.1:#{@options[:port]}/"
when :unix
File.unlink(@options[:file]) rescue nil
@server = UNIXServer.new(@options[:file])
puts "Listening on Unix domain socket: #{@options[:file]}"
else
abort "Unknown transport #{@options[:transport]}"
end
@server.listen(100)
case @options[:protocol]
when :http
puts "Using HTTP protocol"
@protocol = :http
when :session
puts "Using session protocol"
@protocol = :session
else
abort "Unknown protocol #{@options[:protocol]}"
end
if @forward
case @forward_transport
when :tcp
puts "Forwarding to http://127.0.0.1:#{@forward_port}/"
when :unix
puts "Forwarding to Unix domain socket: #{@forward_file}"
end
end
puts "Using #{@options[:processes]} processes"
puts "Using #{@options[:threads]} threads per process"
fork_children
threads = []
@options[:threads].times { threads << start_thread }
begin
threads.each { |t| t.join }
rescue Interrupt
end
end
private
def fork_children
if @options[:processes] == 1
return
end
children = []
@options[:processes].times do
pid = fork
if pid
# Parent
puts "Spawned child process: #{pid}"
children << pid
else
return
end
end
if !children.empty?
# Parent
begin
sleep 999999
rescue Interrupt
exit
ensure
children.each do |pid|
puts "Reaping child process: #{pid}"
Process.kill('INT', pid)
end
children.each do |pid|
Process.waitpid(pid)
end
end
end
end
def start_thread
Thread.new do
Thread.current.abort_on_exception = true
if @forward && @forward_keep_alive
forward_connection = connect_to_forwarding_target
end
while true
handle_next_client(forward_connection)
end
end
end
def handle_next_client(forward_connection)
client = @server.accept
begin
buffer = "".force_encoding("binary")
while true
begin
read_header(client, buffer)
if @forward
forward(forward_connection)
end
# Write response
client.write(RESPONSE)
rescue EOFError, Errno::ECONNRESET
break
end
end
ensure
client.close
end
end
def read_header(client, buffer)
if @protocol == :http
while client.readline != "\r\n"
# Do nothing.
end
else
temp = client.read(4, buffer)
raise EOFError if temp.nil?
size = temp.unpack('N')[0]
temp = client.read(size, buffer)
raise EOFError if temp.nil?
end
end
def forward(target_connection)
if target_connection
io = target_connection
else
io = connect_to_forwarding_target
end
begin
io.write(REQUEST)
while io.readline != "ok\n"
# Do nothing
end
ensure
if !target_connection
io.close
end
end
end
def connect_to_forwarding_target
if @forward_transport == :unix
UNIXSocket.new(@forward_file)
else
TCPSocket.new('127.0.0.1', @forward_port)
end
end
end
options = {}
parser = OptionParser.new do |opts|
opts.banner = "Usage: ./ruby.rb [options]"
opts.separator ""
opts.separator "Options:"
opts.on("--port PORT", Integer, "Listen on the given TCP port. Default: 3000") do |val|
options[:transport] = :tcp
options[:port] = val
end
opts.on("--file PATH", String, "Listen on the given Unix domain socket file") do |val|
options[:transport] = :unix
options[:file] = val
end
opts.on("--session-protocol", "Accept session protocol instead of HTTP") do
options[:protocol] = :session
end
opts.on("--threads N", Integer, "Number of threads to use. Default: 2") do |val|
options[:threads] = val
end
opts.on("--processes N", Integer, "Number of processes to use. Default: 2") do |val|
options[:processes] = val
end
opts.on("--forward-tcp PORT", Integer, "Forward request to another TCP server") do |val|
options[:forward] = true
options[:forward_transport] = :tcp
options[:forward_port] = val
end
opts.on("--forward-file PATH", String, "Forward request to another Unix domain socket server") do |val|
options[:forward] = true
options[:forward_transport] = :unix
options[:forward_file] = val
end
opts.on("--forward-keep-alive", "Use keep-alive when forwarding") do
options[:forward_keep_alive] = true
end
end
begin
parser.parse!
rescue OptionParser::ParseError => e
puts e
puts
puts "Please see '--help' for valid options."
exit 1
end
TestServer.new(options).run