raptor-io 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +30 -0
  3. data/README.md +51 -0
  4. data/lib/rack/handler/raptor-io.rb +130 -0
  5. data/lib/raptor-io.rb +11 -0
  6. data/lib/raptor-io/error.rb +19 -0
  7. data/lib/raptor-io/protocol.rb +6 -0
  8. data/lib/raptor-io/protocol/error.rb +10 -0
  9. data/lib/raptor-io/protocol/http.rb +34 -0
  10. data/lib/raptor-io/protocol/http/client.rb +685 -0
  11. data/lib/raptor-io/protocol/http/error.rb +16 -0
  12. data/lib/raptor-io/protocol/http/headers.rb +132 -0
  13. data/lib/raptor-io/protocol/http/message.rb +67 -0
  14. data/lib/raptor-io/protocol/http/request.rb +307 -0
  15. data/lib/raptor-io/protocol/http/request/manipulator.rb +117 -0
  16. data/lib/raptor-io/protocol/http/request/manipulators.rb +217 -0
  17. data/lib/raptor-io/protocol/http/request/manipulators/authenticator.rb +110 -0
  18. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/basic.rb +36 -0
  19. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/digest.rb +135 -0
  20. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/negotiate.rb +69 -0
  21. data/lib/raptor-io/protocol/http/request/manipulators/authenticators/ntlm.rb +29 -0
  22. data/lib/raptor-io/protocol/http/request/manipulators/redirect_follower.rb +65 -0
  23. data/lib/raptor-io/protocol/http/response.rb +166 -0
  24. data/lib/raptor-io/protocol/http/server.rb +446 -0
  25. data/lib/raptor-io/ruby.rb +4 -0
  26. data/lib/raptor-io/ruby/hash.rb +24 -0
  27. data/lib/raptor-io/ruby/ipaddr.rb +15 -0
  28. data/lib/raptor-io/ruby/openssl.rb +23 -0
  29. data/lib/raptor-io/ruby/string.rb +27 -0
  30. data/lib/raptor-io/socket.rb +175 -0
  31. data/lib/raptor-io/socket/comm.rb +143 -0
  32. data/lib/raptor-io/socket/comm/local.rb +94 -0
  33. data/lib/raptor-io/socket/comm/sapni.rb +75 -0
  34. data/lib/raptor-io/socket/comm/socks.rb +237 -0
  35. data/lib/raptor-io/socket/comm_chain.rb +30 -0
  36. data/lib/raptor-io/socket/error.rb +45 -0
  37. data/lib/raptor-io/socket/switch_board.rb +183 -0
  38. data/lib/raptor-io/socket/switch_board/route.rb +42 -0
  39. data/lib/raptor-io/socket/tcp.rb +231 -0
  40. data/lib/raptor-io/socket/tcp/ssl.rb +77 -0
  41. data/lib/raptor-io/socket/tcp_server.rb +16 -0
  42. data/lib/raptor-io/socket/tcp_server/ssl.rb +52 -0
  43. data/lib/raptor-io/socket/udp.rb +0 -0
  44. data/lib/raptor-io/version.rb +6 -0
  45. data/lib/tasks/yard.rake +26 -0
  46. data/spec/rack/handler/raptor_spec.rb +140 -0
  47. data/spec/raptor-io/protocol/http/client_spec.rb +671 -0
  48. data/spec/raptor-io/protocol/http/headers_spec.rb +189 -0
  49. data/spec/raptor-io/protocol/http/message_spec.rb +5 -0
  50. data/spec/raptor-io/protocol/http/request/manipulators/authenticator_spec.rb +193 -0
  51. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/basic_spec.rb +32 -0
  52. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/digest_spec.rb +76 -0
  53. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/negotiate_spec.rb +52 -0
  54. data/spec/raptor-io/protocol/http/request/manipulators/authenticators/ntlm_spec.rb +37 -0
  55. data/spec/raptor-io/protocol/http/request/manipulators/redirect_follower_spec.rb +51 -0
  56. data/spec/raptor-io/protocol/http/request/manipulators_spec.rb +202 -0
  57. data/spec/raptor-io/protocol/http/request_spec.rb +965 -0
  58. data/spec/raptor-io/protocol/http/response_spec.rb +236 -0
  59. data/spec/raptor-io/protocol/http/server_spec.rb +345 -0
  60. data/spec/raptor-io/ruby/hash_spec.rb +20 -0
  61. data/spec/raptor-io/ruby/string_spec.rb +20 -0
  62. data/spec/raptor-io/socket/comm/local_spec.rb +50 -0
  63. data/spec/raptor-io/socket/switch_board/route_spec.rb +49 -0
  64. data/spec/raptor-io/socket/switch_board_spec.rb +87 -0
  65. data/spec/raptor-io/socket/tcp/ssl_spec.rb +18 -0
  66. data/spec/raptor-io/socket/tcp_server/ssl_spec.rb +59 -0
  67. data/spec/raptor-io/socket/tcp_server_spec.rb +19 -0
  68. data/spec/raptor-io/socket/tcp_spec.rb +14 -0
  69. data/spec/raptor-io/socket_spec.rb +16 -0
  70. data/spec/raptor-io/version_spec.rb +10 -0
  71. data/spec/spec_helper.rb +56 -0
  72. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/manifoolators/fooer.rb +25 -0
  73. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/niccolo_machiavelli.rb +20 -0
  74. data/spec/support/fixtures/raptor/protocol/http/request/manipulators/options_validator.rb +28 -0
  75. data/spec/support/fixtures/raptor/socket/ssl_server.crt +18 -0
  76. data/spec/support/fixtures/raptor/socket/ssl_server.key +15 -0
  77. data/spec/support/lib/path_helpers.rb +11 -0
  78. data/spec/support/lib/webserver_option_parser.rb +26 -0
  79. data/spec/support/lib/webservers.rb +120 -0
  80. data/spec/support/shared/contexts/with_ssl_server.rb +70 -0
  81. data/spec/support/shared/contexts/with_tcp_server.rb +58 -0
  82. data/spec/support/shared/examples/raptor/comm_examples.rb +26 -0
  83. data/spec/support/shared/examples/raptor/protocols/http/message.rb +106 -0
  84. data/spec/support/shared/examples/raptor/socket_examples.rb +135 -0
  85. data/spec/support/webservers/raptor/protocols/http/client.rb +100 -0
  86. data/spec/support/webservers/raptor/protocols/http/client_close_connection.rb +29 -0
  87. data/spec/support/webservers/raptor/protocols/http/client_https.rb +43 -0
  88. data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/basic.rb +9 -0
  89. data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/digest.rb +22 -0
  90. data/spec/support/webservers/raptor/protocols/http/request/manipulators/redirect_follower.rb +11 -0
  91. metadata +336 -0
@@ -0,0 +1,20 @@
1
+ module RaptorIO
2
+ module Protocol::HTTP
3
+ class Request
4
+
5
+ #
6
+ # Test manipulator.
7
+ #
8
+ # @author Tasos Laskos <tasos_laskos@rapid7.com>
9
+ #
10
+ class Manipulators::NiccoloMachiavelli < Manipulator
11
+
12
+ def run
13
+ [client, request, options, datastore]
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ module RaptorIO
2
+ module Protocol::HTTP
3
+ class Request
4
+
5
+ #
6
+ # Test manipulator.
7
+ #
8
+ # @author Tasos Laskos <tasos_laskos@rapid7.com>
9
+ #
10
+ class Manipulators::OptionsValidator < Manipulator
11
+
12
+ validate_options do |options, client, request|
13
+ errors = {}
14
+ next errors if options[:mandatory_string].is_a? String
15
+
16
+ errors[:mandatory_string] = 'Must be string.'
17
+ errors
18
+ end
19
+
20
+ def run
21
+ options[:mandatory_string] * 10
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIC6TCCAlKgAwIBAgIEIRYEajANBgkqhkiG9w0BAQUFADBRMQswCQYDVQQGEwJV
3
+ UzELMAkGA1UECAwCVFgxDzANBgNVBAcMBkF1c3RpbjEPMA0GA1UECgwGUmFwaWQ3
4
+ MRMwEQYDVQQDDApyc3BlYy1ob3N0MB4XDTEzMDgxMDAyMjI1MVoXDTEzMDkwOTEy
5
+ MjI1MVowUTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlRYMQ8wDQYDVQQHDAZBdXN0
6
+ aW4xDzANBgNVBAoMBlJhcGlkNzETMBEGA1UEAwwKcnNwZWMtaG9zdDCBnzANBgkq
7
+ hkiG9w0BAQEFAAOBjQAwgYkCgYEAz5QxP0R5i0curO/f0Fgn8mZ/1ygJUUbrgsVp
8
+ OOU1CarECbZmOU9iEhR/QB2IknOsm0QfVlkCH/gusOZWbQdIaqmrD5mnrgKLHS4N
9
+ w/hvzg4tgTLtuJvlGq2zFXh9FKoTSV8IiXevc0i9DmbZ0Cx2uSjrx+DYdDTDdq9j
10
+ 1DVuKrECAwEAAaOBzTCByjAJBgNVHRMEAjAAMB0GA1UdDgQWBBQ1Um68pnvPrkGt
11
+ lJsAT+drEdu7CzATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBLAwfAYD
12
+ VR0jBHUwc4AUNVJuvKZ7z65BrZSbAE/naxHbuwuhVaRTMFExCzAJBgNVBAYTAlVT
13
+ MQswCQYDVQQIDAJUWDEPMA0GA1UEBwwGQXVzdGluMQ8wDQYDVQQKDAZSYXBpZDcx
14
+ EzARBgNVBAMMCnJzcGVjLWhvc3SCBCEWBGowDQYJKoZIhvcNAQEFBQADgYEAw98n
15
+ wL0Z/FIDflLJyVdFnohOqDadLTr3ms44u2ahSCf1dmXn0fd5784lz8tl44w/HeIO
16
+ xCJvr33i+9+RS9bzkaogq73mw5spc/ZvBK1ivs6/rw+bH7MURr2htdO4gYsYLUVS
17
+ baOu/+8LPjopJZEC/sj+b4ez8Fb9ex/7k1BZfnA=
18
+ -----END CERTIFICATE-----
@@ -0,0 +1,15 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIICXQIBAAKBgQDPlDE/RHmLRy6s79/QWCfyZn/XKAlRRuuCxWk45TUJqsQJtmY5
3
+ T2ISFH9AHYiSc6ybRB9WWQIf+C6w5lZtB0hqqasPmaeuAosdLg3D+G/ODi2BMu24
4
+ m+UarbMVeH0UqhNJXwiJd69zSL0OZtnQLHa5KOvH4Nh0NMN2r2PUNW4qsQIDAQAB
5
+ AoGASP3eN1YXuz8DjbInrHZjTZx3VavxYtAiXnCWaHhIpyaSGqw10+8zGBJ3EI+S
6
+ B5V/W3Wf41gXJDC8El5cg6gs8RoNz9koRrQ6+Rv5pivkuGsaHZWY2CnVzYfc7pCr
7
+ wjqwkVgXgFT41LfoOLruppSxYuL8Bts0UT3Q1pSaH31lGsUCQQD7gkp4DAQ+ZS51
8
+ NccmiVhTuzEYg6TEd29x1arLe9AXrqL3DtN6eDxkSmfc0ZUpL9xudD0nM/L1GEzW
9
+ D8ygvnxzAkEA00kWGlpKPKMAaF0lXFOzCgjRKtEw7XrDENMdOvh1s1206vtn48OK
10
+ /88q9twfjzPN6pc/dg4qd0+q9eGCTdE3SwJBANKzAR32uytmango+FDRaNykimnG
11
+ ByfMAuHzpSTY8aiVVdLxabtEtRsztjUoovQhM2KZII4SGCy6EcyW6c+UJP8CQGfc
12
+ jZj2uXeFSTYEU9FG88QDAY9itgKHTkx++ud6K6G4dq7sVu2HulR1qlEfdAQZGygu
13
+ oWuPGyD7cLbd3AgUyHECQQC9wNYpbA4pSC3OBtzIOyIuCi+ROJN7nSx6kNuMm5LZ
14
+ uX9znuOE6t6Yb0yw8M3Z9LUIKLJ1B9fhjiJb9fD9wCIC
15
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,11 @@
1
+ def manipulator_fixtures_path
2
+ "#{fixtures_path}/raptor/protocol/http/request/manipulators"
3
+ end
4
+
5
+ def fixtures_path
6
+ "#{spec_path}support/fixtures/"
7
+ end
8
+
9
+ def spec_path
10
+ File.expand_path( "#{File.dirname( __FILE__ )}/../.." ) + '/'
11
+ end
@@ -0,0 +1,26 @@
1
+ require 'optparse'
2
+
3
+ class WebServerOptionParser
4
+ DEFAULT = {
5
+ address: '0.0.0.0',
6
+ port: 4567
7
+ }
8
+
9
+ def self.parse
10
+ options = {}
11
+
12
+ OptionParser.new do |opts|
13
+
14
+ opts.on( '-o', '--addr [host]', "set the host (default is #{options[:address]})" ) do |address|
15
+ options[:address] = address
16
+ end
17
+
18
+ opts.on( '-p', '--port [port]', Integer, "set the port (default is #{options[:port]})" ) do |port|
19
+ options[:port] = port
20
+ end
21
+
22
+ end.parse!
23
+
24
+ DEFAULT.merge( options )
25
+ end
26
+ end
@@ -0,0 +1,120 @@
1
+ require 'singleton'
2
+ require 'net/http'
3
+
4
+ class WebServers
5
+ include Singleton
6
+
7
+ attr_reader :lib
8
+
9
+ def initialize
10
+ @lib = File.expand_path( File.dirname( __FILE__ ) + '/../webservers' )
11
+ @servers = {}
12
+
13
+ Dir.glob( File.join( @lib + '/**', '*.rb' ) ) do |path|
14
+ @servers[normalize_name( File.basename( path, '.rb' ) )] = {
15
+ port: available_port,
16
+ path: path
17
+ }
18
+ end
19
+ end
20
+
21
+ def start( name )
22
+ return if up?( name )
23
+
24
+ server_info = data_for( name )
25
+ server_info[:pid] = Process.spawn(
26
+ 'ruby', server_info[:path], '-p', server_info[:port].to_s,
27
+ :out=>"/dev/null", :err=>"/dev/null"
28
+ )
29
+
30
+ sleep 0.2 while !up?( name )
31
+ end
32
+
33
+ def url_for( name )
34
+ "#{protocol_for( name )}://#{address_for( name )}:#{port_for( name )}"
35
+ end
36
+
37
+ def address_for( name )
38
+ '127.0.0.1'
39
+ end
40
+
41
+ def protocol_for( name )
42
+ 'http'
43
+ end
44
+
45
+ def port_for( name )
46
+ data_for( name )[:port]
47
+ end
48
+
49
+ def target_for( name )
50
+ WebTarget.new( address_for( name ), port_for( name ) )
51
+ end
52
+
53
+ def data_for( name )
54
+ @servers[normalize_name( name )]
55
+ end
56
+
57
+ def up?( name )
58
+ begin
59
+ ::Net::HTTP.get_response( URI.parse( url_for( name ) ) )
60
+ true
61
+ rescue Errno::ECONNRESET
62
+ true
63
+ rescue => e
64
+ false
65
+ end
66
+ end
67
+
68
+ def kill( name )
69
+ server_info = data_for( name )
70
+ return if !server_info[:pid]
71
+
72
+ begin
73
+ 10.times { Process.kill( 'KILL', server_info[:pid] ) }
74
+ return false
75
+ rescue Errno::ESRCH
76
+ server_info.delete( :pid )
77
+ return true
78
+ end
79
+ end
80
+
81
+ def killall
82
+ @servers.keys.each { |n| kill n }
83
+ end
84
+
85
+ def available_port
86
+ loop do
87
+ port = 5555 + rand( 9999 )
88
+ begin
89
+ socket = ::Socket.new( :INET, :STREAM, 0 )
90
+ socket.bind(::Socket.sockaddr_in(port, "127.0.0.1"))
91
+ socket.close
92
+ return port
93
+ rescue Errno::EADDRINUSE => e
94
+ end
95
+ end
96
+ end
97
+
98
+ def normalize_name( name )
99
+ name.to_s.to_sym
100
+ end
101
+
102
+ def self.method_missing( sym, *args, &block )
103
+ if instance.respond_to?( sym )
104
+ instance.send( sym, *args, &block )
105
+ elsif
106
+ super( sym, *args, &block )
107
+ end
108
+ end
109
+
110
+ def self.respond_to?( m )
111
+ super( m ) || instance.respond_to?( m )
112
+ end
113
+
114
+ private
115
+
116
+ def set_data_for( name, data )
117
+ @servers[normalize_name( name )] = data
118
+ end
119
+
120
+ end
@@ -0,0 +1,70 @@
1
+
2
+ shared_context 'with ssl server' do
3
+ include_context 'with tcp server'
4
+
5
+ let(:server_cert) { File.read(File.join(fixtures_path, 'raptor', 'socket', 'ssl_server.crt')) }
6
+ let(:server_key) { File.read(File.join(fixtures_path, 'raptor', 'socket', 'ssl_server.key')) }
7
+ let(:server_context) do
8
+ OpenSSL::SSL::SSLContext.new.tap do |context|
9
+ context.cert = OpenSSL::X509::Certificate.new(server_cert)
10
+ context.key = OpenSSL::PKey::RSA.new(server_key)
11
+ end
12
+ end
13
+
14
+ let(:ssl_server_sock) do
15
+ @ssl_server_sock = OpenSSL::SSL::SSLSocket.new(server_sock, server_context)
16
+ end
17
+
18
+ let(:io) do
19
+ #$stderr.puts("with_ssl_server :io, starting tcp server thread")
20
+ server_thread
21
+
22
+ #$stderr.puts(":io client_sock connecting")
23
+ client_sock
24
+
25
+ #$stderr.puts("starting ssl accept thread")
26
+ @ssl_server_thread = Thread.new { ssl_server_sock.accept }
27
+ #$stderr.puts(" ssl accept thread, #{@ssl_server_thread.inspect}")
28
+
29
+ # pass to make sure the server thread gets a slice before we return
30
+ Thread.pass
31
+
32
+ # this should now be a connected ::TCPSocket
33
+ client_sock
34
+ end
35
+
36
+ let(:server_thread) do
37
+ @server_thread = Thread.new do
38
+ begin
39
+ #$stderr.puts("ssl_server_sock.accept_nonblock")
40
+ peer = ssl_server_sock.accept_nonblock
41
+ rescue IO::WaitReadable, IO::WaitWritable
42
+ #$stderr.puts(":server_sock waiting for a client")
43
+ select([server_sock], [server_sock])
44
+ #$stderr.puts(":server_sock retrying")
45
+ retry
46
+ end
47
+ #$stderr.puts(":server_sock accepted peer #{peer.inspect}")
48
+ peer
49
+ end
50
+ @server_thread
51
+
52
+ end
53
+
54
+ subject do
55
+ #$stderr.puts("with_ssl_server subject (#{described_class})")
56
+ s = described_class.new(io, opts)
57
+
58
+ #$stderr.puts("Subject: #{s.inspect}")
59
+ peer = @ssl_server_thread.value
60
+ #$stderr.puts("ssl_server_thread.value #{peer}")
61
+ s
62
+ end
63
+
64
+ after(:each) do
65
+ @ssl_server_sock.close if @ssl_server_sock
66
+ @ssl_server_thread.kill if @ssl_server_thread
67
+ end
68
+
69
+ end
70
+
@@ -0,0 +1,58 @@
1
+
2
+ shared_context 'with tcp server' do
3
+ after(:each) do
4
+ if @server_sock
5
+ @server_sock.close rescue nil
6
+ end
7
+ if @server_thread
8
+ @server_thread.kill
9
+ end
10
+ end
11
+
12
+ let(:server_sock) do
13
+ @server_sock = TCPServer.new(example_addr, example_ssl_port)
14
+ end
15
+
16
+ let(:server_thread) do
17
+ @server_thread = Thread.new do
18
+ begin
19
+ #$stderr.puts("server_sock.accept_nonblock")
20
+ peer = server_sock.accept_nonblock
21
+ rescue IO::WaitReadable, IO::WaitWritable
22
+ #$stderr.puts(":server_sock waiting for a client")
23
+ select([server_sock], [server_sock])
24
+ #$stderr.puts(":server_sock retrying")
25
+ retry
26
+ end
27
+ #$stderr.puts(":server_sock accepted peer #{peer.inspect}")
28
+ peer
29
+ end
30
+ @server_thread
31
+ end
32
+
33
+ let(:client_sock) do
34
+ #$stderr.puts "client_sock connecting"
35
+ retries = 3
36
+ begin
37
+ #$stderr.puts "client_sock trying to connect (#{retries} left)"
38
+ connected_socket = TCPSocket.new(example_addr, example_ssl_port)
39
+ rescue Errno::ECONNREFUSED
40
+ # Give the server thread another chance to get it up
41
+ Thread.pass
42
+ sleep 0.1
43
+ retry if (retries -= 1) > 0
44
+ raise $!
45
+ end
46
+ #$stderr.puts "client_sock connected #{connected_socket}"
47
+ connected_socket
48
+ end
49
+
50
+ let(:io) do
51
+ server_thread
52
+ client_sock
53
+ server_thread.value.write(data)
54
+ client_sock
55
+ end
56
+
57
+ end
58
+
@@ -0,0 +1,26 @@
1
+
2
+ # Requires let(:tcp_port) and let(:udp_port) to be set to something
3
+ shared_examples "a comm" do
4
+ it { should respond_to(:create_tcp) }
5
+ it { should respond_to(:create_tcp_server) }
6
+ it { should respond_to(:resolve) }
7
+ it { should respond_to(:reverse_resolve) }
8
+ #pending { should respond_to(:create_udp) }
9
+ #pending { should respond_to(:create_udp_server) }
10
+
11
+ let(:peer_host) { "127.0.0.1" }
12
+
13
+ describe "#create_tcp" do
14
+ it "should create a TCP socket" do
15
+ sock = comm.create_tcp(peer_host: peer_host, peer_port: tcp_port)
16
+ sock.should be_a(RaptorIO::Socket::TCP)
17
+ raddr = sock.remote_address
18
+ raddr.should be_a(::Addrinfo)
19
+ raddr.ip_address.should == peer_host
20
+ raddr.ip_port.should == tcp_port
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+
@@ -0,0 +1,106 @@
1
+ shared_examples_for 'RaptorIO::Protocol::HTTP::Message' do
2
+
3
+ let(:url) { 'http://test.com' }
4
+
5
+ describe '#initialize' do
6
+ it 'sets the instance attributes by the options' do
7
+ options = {
8
+ url: url,
9
+ version: '1.0',
10
+ headers: {
11
+ 'X-Stuff' => 'Blah'
12
+ }
13
+ }
14
+ r = described_class.new( options )
15
+ r.version.should == options[:version]
16
+ r.headers.should == options[:headers]
17
+ end
18
+ end
19
+
20
+ describe '#version' do
21
+ it 'defaults to 1.1' do
22
+ described_class.new( url: url ).version.should == '1.1'
23
+ end
24
+ end
25
+
26
+ describe '#keep_alive?' do
27
+ context 'when the protocol version is 1.1 or higher' do
28
+ context 'and connection-token is set to \'close\'' do
29
+ it 'returns false' do
30
+ described_class.new( url: url,
31
+ version: '1.1',
32
+ headers: { 'connection' => 'close' }
33
+ ).keep_alive?.should be_false
34
+ end
35
+ end
36
+
37
+ it 'returns true' do
38
+ described_class.new( url: url, version: '1.1' ).keep_alive?.should be_true
39
+ described_class.new( url: url, version: '1.2' ).keep_alive?.should be_true
40
+ end
41
+ end
42
+
43
+ context 'when the protocol version is lower than 1.1' do
44
+ context 'and connection-token is set to \'keep-alive\'' do
45
+ it 'returns false' do
46
+ described_class.new( url: url,
47
+ version: '1.0',
48
+ headers: { 'connection' => 'keep-alive' }
49
+ ).keep_alive?.should be_true
50
+ end
51
+ end
52
+
53
+ it 'returns false' do
54
+ described_class.new( url: url, version: '1.0' ).keep_alive?.should be_false
55
+ end
56
+ end
57
+ end
58
+
59
+ describe '#http_1_1?' do
60
+ context 'when the protocol version is 1.1' do
61
+ it 'returns true' do
62
+ described_class.new( url: url, version: '1.1' ).http_1_1?.should be_true
63
+ end
64
+ end
65
+
66
+ context 'when the protocol version is not 1.1' do
67
+ it 'returns false' do
68
+ described_class.new( url: url, version: '1.2' ).http_1_1?.should be_false
69
+ end
70
+ end
71
+ end
72
+
73
+ describe '#http_1_0?' do
74
+ context 'when the protocol version is 1.0' do
75
+ it 'returns true' do
76
+ described_class.new( url: url, version: '1.0' ).http_1_0?.should be_true
77
+ end
78
+ end
79
+
80
+ context 'when the protocol version is not 1.0' do
81
+ it 'returns false' do
82
+ described_class.new( url: url, version: '1.2' ).http_1_0?.should be_false
83
+ end
84
+ end
85
+ end
86
+
87
+ describe '#headers' do
88
+ context 'when not configured' do
89
+ it 'defaults to an empty Hash' do
90
+ described_class.new( url: url ).headers.should == {}
91
+ end
92
+ end
93
+
94
+ it 'returns the configured value' do
95
+ headers = { 'Content-Type' => 'text/plain' }
96
+ described_class.new( url: url, headers: headers ).headers.should == headers
97
+ end
98
+ end
99
+
100
+ describe '#body' do
101
+ it 'returns the configured body' do
102
+ body = 'Stuff...'
103
+ described_class.new( url: url, body: body ).body.should == body
104
+ end
105
+ end
106
+ end