excon 0.62.0 → 0.85.0

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 (121) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.md +1 -1
  3. data/README.md +6 -5
  4. data/data/cacert.pem +939 -1720
  5. data/excon.gemspec +17 -2
  6. data/lib/excon.rb +25 -17
  7. data/lib/excon/connection.rb +206 -139
  8. data/lib/excon/constants.rb +38 -13
  9. data/lib/excon/error.rb +15 -0
  10. data/lib/excon/headers.rb +4 -3
  11. data/lib/excon/instrumentors/logging_instrumentor.rb +4 -15
  12. data/lib/excon/instrumentors/standard_instrumentor.rb +2 -9
  13. data/lib/excon/middlewares/base.rb +6 -0
  14. data/lib/excon/middlewares/capture_cookies.rb +1 -1
  15. data/lib/excon/middlewares/decompress.rb +2 -2
  16. data/lib/excon/middlewares/expects.rb +7 -1
  17. data/lib/excon/middlewares/idempotent.rb +20 -3
  18. data/lib/excon/middlewares/instrumentor.rb +8 -0
  19. data/lib/excon/middlewares/mock.rb +12 -3
  20. data/lib/excon/middlewares/redirect_follower.rb +25 -3
  21. data/lib/excon/middlewares/response_parser.rb +3 -0
  22. data/lib/excon/pretty_printer.rb +1 -8
  23. data/lib/excon/response.rb +12 -9
  24. data/lib/excon/socket.rb +51 -33
  25. data/lib/excon/ssl_socket.rb +33 -13
  26. data/lib/excon/test/plugin/server/exec.rb +2 -2
  27. data/lib/excon/test/server.rb +1 -1
  28. data/lib/excon/unix_socket.rb +1 -0
  29. data/lib/excon/utils.rb +58 -5
  30. data/lib/excon/version.rb +1 -1
  31. metadata +27 -98
  32. data/.document +0 -5
  33. data/.gitignore +0 -13
  34. data/.rspec +0 -3
  35. data/.travis.yml +0 -29
  36. data/Gemfile +0 -19
  37. data/Rakefile +0 -41
  38. data/benchmarks/class_vs_lambda.rb +0 -50
  39. data/benchmarks/concat_vs_insert.rb +0 -21
  40. data/benchmarks/concat_vs_interpolate.rb +0 -22
  41. data/benchmarks/cr_lf.rb +0 -21
  42. data/benchmarks/downcase-eq-eq_vs_casecmp.rb +0 -169
  43. data/benchmarks/excon.rb +0 -69
  44. data/benchmarks/excon_vs.rb +0 -165
  45. data/benchmarks/for_vs_array_each.rb +0 -27
  46. data/benchmarks/for_vs_hash_each.rb +0 -27
  47. data/benchmarks/has_key-vs-lookup.rb +0 -177
  48. data/benchmarks/headers_case_sensitivity.rb +0 -83
  49. data/benchmarks/headers_split_vs_match.rb +0 -34
  50. data/benchmarks/implicit_block-vs-explicit_block.rb +0 -98
  51. data/benchmarks/merging.rb +0 -21
  52. data/benchmarks/single_vs_double_quotes.rb +0 -21
  53. data/benchmarks/string_ranged_index.rb +0 -87
  54. data/benchmarks/strip_newline.rb +0 -115
  55. data/benchmarks/vs_stdlib.rb +0 -82
  56. data/changelog.txt +0 -1083
  57. data/spec/excon/error_spec.rb +0 -139
  58. data/spec/excon/test/server_spec.rb +0 -28
  59. data/spec/excon_spec.rb +0 -7
  60. data/spec/helpers/file_path_helpers.rb +0 -22
  61. data/spec/requests/basic_spec.rb +0 -40
  62. data/spec/requests/eof_requests_spec.rb +0 -36
  63. data/spec/requests/unix_socket_spec.rb +0 -46
  64. data/spec/spec_helper.rb +0 -24
  65. data/spec/support/shared_contexts/test_server_context.rb +0 -83
  66. data/spec/support/shared_examples/shared_example_for_clients.rb +0 -218
  67. data/spec/support/shared_examples/shared_example_for_streaming_clients.rb +0 -20
  68. data/spec/support/shared_examples/shared_example_for_test_servers.rb +0 -16
  69. data/tests/authorization_header_tests.rb +0 -29
  70. data/tests/bad_tests.rb +0 -47
  71. data/tests/basic_tests.rb +0 -351
  72. data/tests/batch_requests.rb +0 -133
  73. data/tests/complete_responses.rb +0 -31
  74. data/tests/data/127.0.0.1.cert.crt +0 -20
  75. data/tests/data/127.0.0.1.cert.key +0 -27
  76. data/tests/data/excon.cert.crt +0 -20
  77. data/tests/data/excon.cert.key +0 -27
  78. data/tests/data/xs +0 -1
  79. data/tests/error_tests.rb +0 -145
  80. data/tests/header_tests.rb +0 -119
  81. data/tests/middlewares/canned_response_tests.rb +0 -34
  82. data/tests/middlewares/capture_cookies_tests.rb +0 -34
  83. data/tests/middlewares/decompress_tests.rb +0 -157
  84. data/tests/middlewares/escape_path_tests.rb +0 -36
  85. data/tests/middlewares/idempotent_tests.rb +0 -206
  86. data/tests/middlewares/instrumentation_tests.rb +0 -315
  87. data/tests/middlewares/mock_tests.rb +0 -304
  88. data/tests/middlewares/redirect_follower_tests.rb +0 -112
  89. data/tests/pipeline_tests.rb +0 -40
  90. data/tests/proxy_tests.rb +0 -306
  91. data/tests/query_string_tests.rb +0 -87
  92. data/tests/rackups/basic.rb +0 -41
  93. data/tests/rackups/basic.ru +0 -3
  94. data/tests/rackups/basic_auth.ru +0 -14
  95. data/tests/rackups/deflater.ru +0 -4
  96. data/tests/rackups/proxy.ru +0 -18
  97. data/tests/rackups/query_string.ru +0 -13
  98. data/tests/rackups/redirecting.ru +0 -23
  99. data/tests/rackups/redirecting_with_cookie.ru +0 -40
  100. data/tests/rackups/request_headers.ru +0 -15
  101. data/tests/rackups/request_methods.ru +0 -21
  102. data/tests/rackups/response_header.ru +0 -18
  103. data/tests/rackups/ssl.ru +0 -16
  104. data/tests/rackups/ssl_mismatched_cn.ru +0 -15
  105. data/tests/rackups/ssl_verify_peer.ru +0 -16
  106. data/tests/rackups/streaming.ru +0 -30
  107. data/tests/rackups/thread_safety.ru +0 -17
  108. data/tests/rackups/timeout.ru +0 -14
  109. data/tests/rackups/webrick_patch.rb +0 -34
  110. data/tests/request_headers_tests.rb +0 -21
  111. data/tests/request_method_tests.rb +0 -47
  112. data/tests/request_tests.rb +0 -59
  113. data/tests/response_tests.rb +0 -197
  114. data/tests/servers/bad.rb +0 -20
  115. data/tests/servers/eof.rb +0 -17
  116. data/tests/servers/error.rb +0 -20
  117. data/tests/servers/good.rb +0 -350
  118. data/tests/test_helper.rb +0 -306
  119. data/tests/thread_safety_tests.rb +0 -39
  120. data/tests/timeout_tests.rb +0 -12
  121. data/tests/utils_tests.rb +0 -81
@@ -1,21 +0,0 @@
1
- Shindo.tests('Excon request methods') do
2
-
3
- with_rackup('request_headers.ru') do
4
-
5
- tests 'empty headers sent' do
6
-
7
- test('Excon.post') do
8
- headers = {
9
- :one => 1,
10
- :two => nil,
11
- :three => 3,
12
- }
13
- r = Excon.post('http://localhost:9292', :headers => headers).body
14
- !r.match(/two:/).nil?
15
- end
16
-
17
- end
18
-
19
- end
20
-
21
- end
@@ -1,47 +0,0 @@
1
- Shindo.tests('Excon request methods') do
2
-
3
- with_rackup('request_methods.ru') do
4
-
5
- tests 'one-offs' do
6
-
7
- tests('Excon.get').returns('GET') do
8
- Excon.get('http://localhost:9292').body
9
- end
10
-
11
- tests('Excon.post').returns('POST') do
12
- Excon.post('http://localhost:9292').body
13
- end
14
-
15
- tests('Excon.delete').returns('DELETE') do
16
- Excon.delete('http://localhost:9292').body
17
- end
18
-
19
- end
20
-
21
- tests 'with a connection object' do
22
- connection = nil
23
-
24
- tests('connection.get').returns('GET') do
25
- connection = Excon.new('http://localhost:9292')
26
- connection.get.body
27
- end
28
-
29
- tests('connection.post').returns('POST') do
30
- connection.post.body
31
- end
32
-
33
- tests('connection.delete').returns('DELETE') do
34
- connection.delete.body
35
- end
36
-
37
- tests('not modifies path argument').returns('path') do
38
- path = 'path'
39
- connection.get(:path => path)
40
- path
41
- end
42
-
43
- end
44
-
45
- end
46
-
47
- end
@@ -1,59 +0,0 @@
1
- Shindo.tests('Request Tests') do
2
- with_server('good') do
3
-
4
- tests('persistent connections') do
5
- ip_ports = %w(127.0.0.1:9292)
6
- ip_ports << "[::1]:9293" unless RUBY_PLATFORM == 'java'
7
- ip_ports.each do |ip_port|
8
-
9
- tests("with default :persistent => true, #{ip_port}") do
10
- connection = nil
11
-
12
- returns(['1', '2'], 'uses a persistent connection') do
13
- connection = Excon.new("http://#{ip_port}", :persistent => true)
14
- 2.times.map do
15
- connection.request(:method => :get, :path => '/echo/request_count').body
16
- end
17
- end
18
-
19
- returns(['3', '1', '2'], ':persistent => false resets connection') do
20
- ret = []
21
- ret << connection.request(:method => :get,
22
- :path => '/echo/request_count',
23
- :persistent => false).body
24
- ret << connection.request(:method => :get,
25
- :path => '/echo/request_count').body
26
- ret << connection.request(:method => :get,
27
- :path => '/echo/request_count').body
28
- end
29
- end
30
-
31
- tests("with default :persistent => false, #{ip_port}") do
32
- connection = nil
33
-
34
- returns(['1', '1'], 'does not use a persistent connection') do
35
- connection = Excon.new("http://#{ip_port}", :persistent => false)
36
- 2.times.map do
37
- connection.request(:method => :get, :path => '/echo/request_count').body
38
- end
39
- end
40
-
41
- returns(['1', '2', '3', '1'], ':persistent => true enables persistence') do
42
- ret = []
43
- ret << connection.request(:method => :get,
44
- :path => '/echo/request_count',
45
- :persistent => true).body
46
- ret << connection.request(:method => :get,
47
- :path => '/echo/request_count',
48
- :persistent => true).body
49
- ret << connection.request(:method => :get,
50
- :path => '/echo/request_count').body
51
- ret << connection.request(:method => :get,
52
- :path => '/echo/request_count').body
53
- end
54
- end
55
-
56
- end
57
- end
58
- end
59
- end
@@ -1,197 +0,0 @@
1
- Shindo.tests('Excon Response Parsing') do
2
- env_init
3
-
4
- with_server('good') do
5
-
6
- tests('responses with chunked transfer-encoding') do
7
-
8
- tests('simple response').returns('hello world') do
9
- Excon.get('http://127.0.0.1:9292/chunked/simple').body
10
- end
11
-
12
- tests('with :response_block') do
13
-
14
- tests('simple response').
15
- returns([['hello ', nil, nil], ['world', nil, nil]]) do
16
- capture_response_block do |block|
17
- Excon.get('http://127.0.0.1:9292/chunked/simple',
18
- :response_block => block,
19
- :chunk_size => 5) # not used
20
- end
21
- end
22
-
23
- tests('simple response has empty body').returns('') do
24
- response_block = lambda { |_, _, _| }
25
- Excon.get('http://127.0.0.1:9292/chunked/simple', :response_block => response_block).body
26
- end
27
-
28
- tests('with expected response status').
29
- returns([['hello ', nil, nil], ['world', nil, nil]]) do
30
- capture_response_block do |block|
31
- Excon.get('http://127.0.0.1:9292/chunked/simple',
32
- :response_block => block,
33
- :expects => 200)
34
- end
35
- end
36
-
37
- tests('with unexpected response status').returns('hello world') do
38
- begin
39
- Excon.get('http://127.0.0.1:9292/chunked/simple',
40
- :response_block => Proc.new { raise 'test failed' },
41
- :expects => 500)
42
- rescue Excon::Errors::HTTPStatusError => err
43
- err.response[:body]
44
- end
45
- end
46
-
47
- end
48
-
49
- tests('merges trailers into headers').
50
- returns('one, two, three, four, five, six') do
51
- Excon.get('http://127.0.0.1:9292/chunked/trailers').headers['Test-Header']
52
- end
53
-
54
- tests("removes 'chunked' from Transfer-Encoding").returns(nil) do
55
- Excon.get('http://127.0.0.1:9292/chunked/simple').headers['Transfer-Encoding']
56
- end
57
-
58
- end
59
-
60
- tests('responses with content-length') do
61
-
62
- tests('simple response').returns('hello world') do
63
- Excon.get('http://127.0.0.1:9292/content-length/simple').body
64
- end
65
-
66
- tests('with :response_block') do
67
-
68
- tests('simple response').
69
- returns([['hello', 6, 11], [' worl', 1, 11], ['d', 0, 11]]) do
70
- capture_response_block do |block|
71
- Excon.get('http://127.0.0.1:9292/content-length/simple',
72
- :response_block => block,
73
- :chunk_size => 5)
74
- end
75
- end
76
-
77
- tests('simple response has empty body').returns('') do
78
- response_block = lambda { |_, _, _| }
79
- Excon.get('http://127.0.0.1:9292/content-length/simple', :response_block => response_block).body
80
- end
81
-
82
- tests('with expected response status').
83
- returns([['hello', 6, 11], [' worl', 1, 11], ['d', 0, 11]]) do
84
- capture_response_block do |block|
85
- Excon.get('http://127.0.0.1:9292/content-length/simple',
86
- :response_block => block,
87
- :chunk_size => 5,
88
- :expects => 200)
89
- end
90
- end
91
-
92
- tests('with unexpected response status').returns('hello world') do
93
- begin
94
- Excon.get('http://127.0.0.1:9292/content-length/simple',
95
- :response_block => Proc.new { raise 'test failed' },
96
- :chunk_size => 5,
97
- :expects => 500)
98
- rescue Excon::Errors::HTTPStatusError => err
99
- err.response[:body]
100
- end
101
- end
102
-
103
- end
104
-
105
- end
106
-
107
- tests('responses with unknown length') do
108
-
109
- tests('simple response').returns('hello world') do
110
- Excon.get('http://127.0.0.1:9292/unknown/simple').body
111
- end
112
-
113
- tests('with :response_block') do
114
-
115
- tests('simple response').
116
- returns([['hello', nil, nil], [' worl', nil, nil], ['d', nil, nil]]) do
117
- capture_response_block do |block|
118
- Excon.get('http://127.0.0.1:9292/unknown/simple',
119
- :response_block => block,
120
- :chunk_size => 5)
121
- end
122
- end
123
-
124
- tests('simple response has empty body').returns('') do
125
- response_block = lambda { |_, _, _| }
126
- Excon.get('http://127.0.0.1:9292/unknown/simple', :response_block => response_block).body
127
- end
128
-
129
- tests('with expected response status').
130
- returns([['hello', nil, nil], [' worl', nil, nil], ['d', nil, nil]]) do
131
- capture_response_block do |block|
132
- Excon.get('http://127.0.0.1:9292/unknown/simple',
133
- :response_block => block,
134
- :chunk_size => 5,
135
- :expects => 200)
136
- end
137
- end
138
-
139
- tests('with unexpected response status').returns('hello world') do
140
- begin
141
- Excon.get('http://127.0.0.1:9292/unknown/simple',
142
- :response_block => Proc.new { raise 'test failed' },
143
- :chunk_size => 5,
144
- :expects => 500)
145
- rescue Excon::Errors::HTTPStatusError => err
146
- err.response[:body]
147
- end
148
- end
149
-
150
- end
151
-
152
- end
153
-
154
- tests('cookies') do
155
-
156
- tests('parses cookies into array').returns(['one, two', 'three, four']) do
157
- resp = Excon.get('http://127.0.0.1:9292/unknown/cookies')
158
- resp[:cookies]
159
- end
160
-
161
- end
162
-
163
- tests('header continuation') do
164
-
165
- tests('proper continuation').returns('one, two, three, four, five, six') do
166
- resp = Excon.get('http://127.0.0.1:9292/unknown/header_continuation')
167
- resp.headers['Test-Header']
168
- end
169
-
170
- tests('malformed header').raises(Excon::Errors::SocketError) do
171
- Excon.get('http://127.0.0.1:9292/bad/malformed_header')
172
- end
173
-
174
- tests('malformed header continuation').raises(Excon::Errors::SocketError) do
175
- Excon.get('http://127.0.0.1:9292/bad/malformed_header_continuation')
176
- end
177
-
178
- end
179
-
180
- tests('status line parsing') do
181
-
182
- tests('proper status code').returns(404) do
183
- resp = Excon.get('http://127.0.0.1:9292/not-found')
184
- resp.status
185
- end
186
-
187
- tests('proper reason phrase').returns("Not Found") do
188
- resp = Excon.get('http://127.0.0.1:9292/not-found')
189
- resp.reason_phrase
190
- end
191
-
192
- end
193
-
194
- end
195
-
196
- env_restore
197
- end
data/tests/servers/bad.rb DELETED
@@ -1,20 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "eventmachine"
4
-
5
- module BadServer
6
- def receive_data(data)
7
- case data
8
- when %r{^GET /eof/no_content_length_and_no_chunking\s}
9
- send_data "HTTP/1.1 200 OK\r\n"
10
- send_data "\r\n"
11
- send_data "hello"
12
- close_connection(true)
13
- end
14
- end
15
- end
16
-
17
- EM.run do
18
- EM.start_server("127.0.0.1", 9292, BadServer)
19
- $stderr.puts "ready"
20
- end
data/tests/servers/eof.rb DELETED
@@ -1,17 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "eventmachine"
4
-
5
- module EOFServer
6
- def receive_data(data)
7
- case data
8
- when %r{^GET /eof\s}
9
- close_connection(true)
10
- end
11
- end
12
- end
13
-
14
- EM.run do
15
- EM.start_server("127.0.0.1", 9292, EOFServer)
16
- $stderr.puts "ready"
17
- end
@@ -1,20 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "eventmachine"
4
-
5
- module ErrorServer
6
- def receive_data(data)
7
- case data
8
- when %r{^GET /error/not_found\s}
9
- send_data "HTTP/1.1 404 Not Found\r\n"
10
- send_data "\r\n"
11
- send_data "server says not found"
12
- close_connection(true)
13
- end
14
- end
15
- end
16
-
17
- EM.run do
18
- EM.start_server("127.0.0.1", 9292, ErrorServer)
19
- $stderr.puts "ready"
20
- end
@@ -1,350 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'eventmachine'
4
- require 'stringio'
5
- require 'uri'
6
- require 'zlib'
7
-
8
- module GoodServer
9
- # This method will be called with each request received.
10
- #
11
- # request = {
12
- # :method => method,
13
- # :uri => URI.parse(uri),
14
- # :headers => {},
15
- # :body => ''
16
- # }
17
- #
18
- # Each connection to this server is persistent unless the client sends
19
- # "Connection: close" in the request. If a response requires the connection
20
- # to be closed, use `start_response(:persistent => false)`.
21
- def send_response(request)
22
- type, path = request[:uri].path.split('/', 3)[1, 2]
23
- case type
24
- when 'echo'
25
- case path
26
- when 'request'
27
- data = Marshal.dump(request)
28
- start_response
29
- send_data "Content-Length: #{ data.size }\r\n"
30
- send_data "\r\n"
31
- send_data data
32
-
33
- when 'request_count'
34
- (@request_count ||= '0').next!
35
- start_response
36
- send_data "Content-Length: #{ @request_count.size }\r\n"
37
- send_data "Connection: Keep-Alive\r\n"
38
- send_data "\r\n"
39
- send_data @request_count
40
-
41
- when /(content|transfer)-encoded\/?(.*)/
42
- if (encoding_type = $1) == 'content'
43
- accept_header = 'Accept-Encoding'
44
- encoding_header = 'Content-Encoding'
45
- else
46
- accept_header = 'TE'
47
- encoding_header = 'Transfer-Encoding'
48
- end
49
- chunked = $2 == 'chunked'
50
-
51
- encodings = parse_encodings(request[:headers][accept_header])
52
- while encoding = encodings.pop
53
- break if ['gzip', 'deflate'].include?(encoding)
54
- end
55
-
56
- case encoding
57
- when 'gzip'
58
- body = request[:body]
59
- if(body.nil? || body.empty?)
60
- body = ''
61
- else
62
- io = (Zlib::GzipWriter.new(StringIO.new) << request[:body]).finish
63
- io.rewind
64
- body = io.read
65
- end
66
- when 'deflate'
67
- # drops the zlib header
68
- deflator = Zlib::Deflate.new(nil, -Zlib::MAX_WBITS)
69
- body = deflator.deflate(request[:body], Zlib::FINISH)
70
- deflator.close
71
- else
72
- body = request[:body]
73
- end
74
-
75
- # simulate server pre/post content encoding
76
- encodings = [
77
- request[:headers]["#{ encoding_header }-Pre"],
78
- encoding,
79
- request[:headers]["#{ encoding_header }-Post"],
80
- ]
81
- if chunked && encoding_type == 'transfer'
82
- encodings << 'chunked'
83
- end
84
- encodings = encodings.compact.join(', ')
85
-
86
- start_response
87
- # let the test know what the server sent
88
- send_data "#{ encoding_header }-Sent: #{ encodings }\r\n"
89
- send_data "#{ encoding_header }: #{ encodings }\r\n" unless encodings.empty?
90
- if chunked
91
- if encoding_type == 'content'
92
- send_data "Transfer-Encoding: chunked\r\n"
93
- end
94
- send_data "\r\n"
95
- send_data chunks_for(body)
96
- send_data "\r\n"
97
- else
98
- send_data "Content-Length: #{ body.size }\r\n"
99
- send_data "\r\n"
100
- send_data body
101
- end
102
- end
103
-
104
- when 'chunked'
105
- case path
106
- when 'simple'
107
- start_response
108
- send_data "Transfer-Encoding: chunked\r\n"
109
- send_data "\r\n"
110
- # chunk-extension is currently ignored.
111
- # this works because "6; chunk-extension".to_i => "6"
112
- send_data "6; chunk-extension\r\n"
113
- send_data "hello \r\n"
114
- send_data "5; chunk-extension\r\n"
115
- send_data "world\r\n"
116
- send_data "0; chunk-extension\r\n" # last-chunk
117
- send_data "\r\n"
118
-
119
- # merged trailers also support continuations
120
- when 'trailers'
121
- start_response
122
- send_data "Transfer-Encoding: chunked\r\n"
123
- send_data "Test-Header: one, two\r\n"
124
- send_data "\r\n"
125
- send_data chunks_for('hello world')
126
- send_data "Test-Header: three, four,\r\n"
127
- send_data "\tfive, six\r\n"
128
- send_data "\r\n"
129
- end
130
-
131
- when 'content-length'
132
- case path
133
- when 'simple'
134
- start_response
135
- send_data "Content-Length: 11\r\n"
136
- send_data "\r\n"
137
- send_data "hello world"
138
- end
139
-
140
- when 'unknown'
141
- case path
142
- when 'cookies'
143
- start_response(:persistent => false)
144
- send_data "Set-Cookie: one, two\r\n"
145
- send_data "Set-Cookie: three, four\r\n"
146
- send_data "\r\n"
147
- send_data "hello world"
148
-
149
- when 'simple'
150
- start_response(:persistent => false)
151
- send_data "\r\n"
152
- send_data "hello world"
153
-
154
- when 'header_continuation'
155
- start_response(:persistent => false)
156
- send_data "Test-Header: one, two\r\n"
157
- send_data "Test-Header: three, four,\r\n"
158
- send_data " five, six\r\n"
159
- send_data "\r\n"
160
- send_data "hello world"
161
- end
162
-
163
- when 'bad'
164
- # Excon will close these connections due to the errors.
165
- case path
166
- when 'malformed_header'
167
- start_response
168
- send_data "Bad-Header\r\n" # no ':'
169
- send_data "\r\n"
170
- send_data "hello world"
171
-
172
- when 'malformed_header_continuation'
173
- send_data "HTTP/1.1 200 OK\r\n"
174
- send_data " Bad-Header: one, two\r\n" # no previous header
175
- send_data "\r\n"
176
- send_data "hello world"
177
- end
178
-
179
- when 'not-found'
180
- start_response(:status => "404 Not Found")
181
- send_data "Content-Length: 11\r\n"
182
- send_data "\r\n"
183
- send_data "hello world"
184
- end
185
- end
186
-
187
- # Sends response status-line, plus headers common to all responses.
188
- def start_response(opts = {})
189
- opts = {
190
- :status => '200 OK',
191
- :persistent => @persistent # true unless client sent Connection: close
192
- }.merge!(opts)
193
-
194
- @persistent = opts[:persistent]
195
- send_data "HTTP/1.1 #{ opts[:status] }\r\n"
196
- send_data "Connection: close\r\n" unless @persistent
197
- end
198
-
199
- def post_init
200
- @buffer = StringIO.new
201
- @buffer.set_encoding('BINARY') if @buffer.respond_to?(:set_encoding)
202
- end
203
-
204
- # Receives a String of +data+ sent from the client.
205
- # +data+ may only be a portion of what the client sent.
206
- # The data is buffered, then processed and removed from the buffer
207
- # as data becomes available until the @request is complete.
208
- def receive_data(data)
209
- @buffer.seek(0, IO::SEEK_END)
210
- @buffer.write(data)
211
-
212
- parse_headers unless @request
213
- parse_body if @request
214
-
215
- if @request_complete
216
- send_response(@request)
217
- if @persistent
218
- @request = nil
219
- @request_complete = false
220
- # process remaining buffer for next request
221
- receive_data('') unless @buffer.eof?
222
- else
223
- close_connection(true)
224
- end
225
- end
226
- end
227
-
228
- # Removes the processed portion of the buffer
229
- # by replacing the buffer with it's contents from the current pos.
230
- def sync_buffer
231
- @buffer.string = @buffer.read
232
- end
233
-
234
- def parse_headers
235
- @buffer.rewind
236
- # wait until buffer contains the end of the headers
237
- if /\sHTTP\/\d+\.\d+\r\n.*?\r\n\r\n/m =~ @buffer.read
238
- @buffer.rewind
239
- # For persistent connections, the buffer could start with the
240
- # \r\n chunked-message terminator from the previous request.
241
- # This will discard anything up to the request-line.
242
- until m = /^(\w+)\s(.*)\sHTTP\/\d+\.\d+$/.match(@buffer.readline.chop!); end
243
- method, uri = m[1, 2]
244
-
245
- headers = {}
246
- last_key = nil
247
- until (line = @buffer.readline.chop!).empty?
248
- if !line.lstrip!.nil?
249
- headers[last_key] << ' ' << line.rstrip
250
- else
251
- key, value = line.split(':', 2)
252
- headers[key] = ([headers[key]] << value.strip).compact.join(', ')
253
- last_key = key
254
- end
255
- end
256
-
257
- sync_buffer
258
-
259
- @chunked = headers['Transfer-Encoding'] =~ /chunked/i
260
- @content_length = headers['Content-Length'].to_i
261
- @persistent = headers['Connection'] !~ /close/i
262
- @request = {
263
- :method => method,
264
- :uri => URI.parse(uri),
265
- :headers => headers,
266
- :body => ''
267
- }
268
- end
269
- end
270
-
271
- def parse_body
272
- if @chunked
273
- @buffer.rewind
274
- until @request_complete || @buffer.eof?
275
- unless @chunk_size
276
- # in case buffer only contains a portion of the chunk-size line
277
- if (line = @buffer.readline) =~ /\r\n\z/
278
- @chunk_size = line.to_i(16)
279
- if @chunk_size > 0
280
- sync_buffer
281
- else # last-chunk
282
- @buffer.read(2) # the final \r\n may or may not be in the buffer
283
- sync_buffer
284
- @chunk_size = nil
285
- @request_complete = true
286
- end
287
- end
288
- end
289
- if @chunk_size
290
- if @buffer.size >= @chunk_size + 2
291
- @request[:body] << @buffer.read(@chunk_size + 2).chop!
292
- @chunk_size = nil
293
- sync_buffer
294
- else
295
- break # wait for more data
296
- end
297
- end
298
- end
299
- elsif @content_length > 0
300
- @buffer.rewind
301
- unless @buffer.eof? # buffer only contained the headers
302
- @request[:body] << @buffer.read(@content_length - @request[:body].size)
303
- sync_buffer
304
- if @request[:body].size == @content_length
305
- @request_complete = true
306
- end
307
- end
308
- else
309
- # no body
310
- @request_complete = true
311
- end
312
- end
313
-
314
- def chunks_for(str)
315
- chunks = ''
316
- str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
317
- chunk_size = str.size / 2
318
- until (chunk = str.slice!(0, chunk_size)).empty?
319
- chunks << chunk.size.to_s(16) << "\r\n"
320
- chunks << chunk << "\r\n"
321
- end
322
- chunks << "0\r\n" # last-chunk
323
- end
324
-
325
- # only supports a single quality parameter for tokens
326
- def parse_encodings(encodings)
327
- return [] if encodings.nil?
328
- split_header_value(encodings).map do |value|
329
- token, q_val = /^(.*?)(?:;q=(.*))?$/.match(value.strip)[1, 2]
330
- if q_val && q_val.to_f == 0
331
- nil
332
- else
333
- [token, (q_val || 1).to_f]
334
- end
335
- end.compact.sort_by {|_, q_val| q_val }.map {|token, _| token }
336
- end
337
-
338
- # Splits a header value +str+ according to HTTP specification.
339
- def split_header_value(str)
340
- return [] if str.nil?
341
- str.strip.scan(%r'\G((?:"(?:\\.|[^"])+?"|[^",]+)+)
342
- (?:,\s*|\Z)'xn).flatten
343
- end
344
- end
345
-
346
- EM.run do
347
- EM.start_server("127.0.0.1", 9292, GoodServer)
348
- EM.start_server("::1", 9293, GoodServer) unless RUBY_PLATFORM == 'java'
349
- $stderr.puts "ready"
350
- end