raptor-io 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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