tilia-http 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +35 -0
  5. data/.simplecov +4 -0
  6. data/.travis.yml +3 -0
  7. data/CHANGELOG.sabre.md +235 -0
  8. data/CONTRIBUTING.md +25 -0
  9. data/Gemfile +18 -0
  10. data/Gemfile.lock +69 -0
  11. data/LICENSE +27 -0
  12. data/LICENSE.sabre +27 -0
  13. data/README.md +68 -0
  14. data/Rakefile +17 -0
  15. data/examples/asyncclient.rb +45 -0
  16. data/examples/basicauth.rb +39 -0
  17. data/examples/client.rb +20 -0
  18. data/examples/reverseproxy.rb +39 -0
  19. data/examples/stringify.rb +37 -0
  20. data/lib/tilia/http/auth/abstract_auth.rb +51 -0
  21. data/lib/tilia/http/auth/aws.rb +191 -0
  22. data/lib/tilia/http/auth/basic.rb +43 -0
  23. data/lib/tilia/http/auth/bearer.rb +37 -0
  24. data/lib/tilia/http/auth/digest.rb +187 -0
  25. data/lib/tilia/http/auth.rb +12 -0
  26. data/lib/tilia/http/client.rb +452 -0
  27. data/lib/tilia/http/client_exception.rb +15 -0
  28. data/lib/tilia/http/client_http_exception.rb +37 -0
  29. data/lib/tilia/http/http_exception.rb +21 -0
  30. data/lib/tilia/http/message.rb +241 -0
  31. data/lib/tilia/http/message_decorator_trait.rb +183 -0
  32. data/lib/tilia/http/message_interface.rb +154 -0
  33. data/lib/tilia/http/request.rb +235 -0
  34. data/lib/tilia/http/request_decorator.rb +160 -0
  35. data/lib/tilia/http/request_interface.rb +126 -0
  36. data/lib/tilia/http/response.rb +164 -0
  37. data/lib/tilia/http/response_decorator.rb +58 -0
  38. data/lib/tilia/http/response_interface.rb +36 -0
  39. data/lib/tilia/http/sapi.rb +165 -0
  40. data/lib/tilia/http/url_util.rb +70 -0
  41. data/lib/tilia/http/util.rb +51 -0
  42. data/lib/tilia/http/version.rb +9 -0
  43. data/lib/tilia/http.rb +416 -0
  44. data/test/http/auth/aws_test.rb +189 -0
  45. data/test/http/auth/basic_test.rb +60 -0
  46. data/test/http/auth/bearer_test.rb +47 -0
  47. data/test/http/auth/digest_test.rb +141 -0
  48. data/test/http/client_mock.rb +101 -0
  49. data/test/http/client_test.rb +331 -0
  50. data/test/http/message_decorator_test.rb +67 -0
  51. data/test/http/message_test.rb +163 -0
  52. data/test/http/request_decorator_test.rb +87 -0
  53. data/test/http/request_test.rb +132 -0
  54. data/test/http/response_decorator_test.rb +28 -0
  55. data/test/http/response_test.rb +38 -0
  56. data/test/http/sapi_mock.rb +12 -0
  57. data/test/http/sapi_test.rb +133 -0
  58. data/test/http/url_util_test.rb +155 -0
  59. data/test/http/util_test.rb +186 -0
  60. data/test/http_test.rb +102 -0
  61. data/test/test_helper.rb +6 -0
  62. data/tilia-http.gemspec +18 -0
  63. metadata +192 -0
@@ -0,0 +1,163 @@
1
+ require 'test_helper'
2
+ require 'stringio'
3
+
4
+ module Tilia
5
+ module Http
6
+ class MessageTest < Minitest::Test
7
+ def test_construct
8
+ message = MessageMock.new
9
+ assert_kind_of(Message, message)
10
+ end
11
+
12
+ def test_stream_body
13
+ body = 'foo'
14
+ h = StringIO.new
15
+ h.write(body)
16
+ h.rewind
17
+
18
+ message = MessageMock.new
19
+ message.body = h
20
+
21
+ assert_equal(body, message.body_as_string)
22
+ h.rewind
23
+ assert_equal(body, message.body_as_stream.readlines.join(''))
24
+ h.rewind
25
+ assert_equal(body, message.body.readlines.join(''))
26
+ end
27
+
28
+ def test_string_body
29
+ body = 'foo'
30
+
31
+ message = MessageMock.new
32
+ message.body = body
33
+
34
+ assert_equal(body, message.body_as_string)
35
+ assert_equal(body, message.body_as_stream.readlines.join(''))
36
+ assert_equal(body, message.body)
37
+ end
38
+
39
+ def test_get_empty_body_stream
40
+ message = MessageMock.new
41
+ body = message.body_as_stream
42
+
43
+ assert_equal('', body.readlines.join(''))
44
+ end
45
+
46
+ def test_get_empty_body_string
47
+ message = MessageMock.new
48
+ body = message.body_as_string
49
+
50
+ assert_equal('', body)
51
+ end
52
+
53
+ def test_headers
54
+ message = MessageMock.new
55
+ message.update_header('X-Foo', 'bar')
56
+
57
+ # Testing caselessness
58
+ assert_equal('bar', message.header('X-Foo'))
59
+ assert_equal('bar', message.header('x-fOO'))
60
+
61
+ assert(message.remove_header('X-FOO'))
62
+ assert_nil(message.header('X-Foo'))
63
+ refute(message.remove_header('X-FOO'))
64
+ end
65
+
66
+ def test_set_headers
67
+ message = MessageMock.new
68
+
69
+ headers = {
70
+ 'X-Foo' => ['1'],
71
+ 'X-Bar' => ['2']
72
+ }
73
+
74
+ message.update_headers(headers)
75
+ assert_equal(headers, message.headers)
76
+
77
+ message.update_headers(
78
+ 'X-Foo' => ['3', '4'],
79
+ 'X-Bar' => '5'
80
+ )
81
+
82
+ expected = {
83
+ 'X-Foo' => ['3', '4'],
84
+ 'X-Bar' => ['5']
85
+ }
86
+
87
+ assert_equal(expected, message.headers)
88
+ end
89
+
90
+ def test_add_headers
91
+ message = MessageMock.new
92
+
93
+ headers = {
94
+ 'X-Foo' => ['1'],
95
+ 'X-Bar' => ['2']
96
+ }
97
+
98
+ message.add_headers(headers)
99
+ assert_equal(headers, message.headers)
100
+
101
+ message.add_headers(
102
+ 'X-Foo' => ['3', '4'],
103
+ 'X-Bar' => '5'
104
+ )
105
+
106
+ expected = {
107
+ 'X-Foo' => ['1', '3', '4'],
108
+ 'X-Bar' => ['2', '5']
109
+ }
110
+
111
+ assert_equal(expected, message.headers)
112
+ end
113
+
114
+ def test_send_body
115
+ message = MessageMock.new
116
+
117
+ # String
118
+ message.body = 'foo'
119
+
120
+ # Stream
121
+ h = StringIO.new
122
+ h.write('bar')
123
+ h.rewind
124
+ message.body = h
125
+
126
+ body = message.body
127
+ body.rewind
128
+
129
+ assert_equal('bar', body.readlines.join(''))
130
+ end
131
+
132
+ def test_multiple_headers
133
+ message = MessageMock.new
134
+ message.update_header('a', '1')
135
+ message.add_header('A', '2')
136
+
137
+ assert_equal('1,2', message.header('A'))
138
+ assert_equal('1,2', message.header('a'))
139
+
140
+ assert_equal(['1', '2'], message.header_as_array('A'))
141
+ assert_equal(['1', '2'], message.header_as_array('a'))
142
+
143
+ assert_equal([], message.header_as_array('B'))
144
+ end
145
+
146
+ def test_has_headers
147
+ message = MessageMock.new
148
+
149
+ refute(message.header?('X-Foo'))
150
+ message.update_header('X-Foo', 'Bar')
151
+ assert(message.header?('X-Foo'))
152
+ end
153
+ end
154
+
155
+ class MessageMock
156
+ include Message
157
+
158
+ def initialize
159
+ initialize_message
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,87 @@
1
+ require 'test_helper'
2
+
3
+ module Tilia
4
+ module Http
5
+ class RequestDecoratorTest < Minitest::Test
6
+ def setup
7
+ @inner = Request.new
8
+ @outer = RequestDecorator.new(@inner)
9
+ end
10
+
11
+ def test_method
12
+ @outer.method = 'FOO'
13
+ assert_equal('FOO', @inner.method)
14
+ assert_equal('FOO', @outer.method)
15
+ end
16
+
17
+ def test_url
18
+ @outer.url = '/foo'
19
+ assert_equal('/foo', @inner.url)
20
+ assert_equal('/foo', @outer.url)
21
+ end
22
+
23
+ def test_absolute_url
24
+ @outer.absolute_url = 'http://example.org/foo'
25
+ assert_equal('http://example.org/foo', @inner.absolute_url)
26
+ assert_equal('http://example.org/foo', @outer.absolute_url)
27
+ end
28
+
29
+ def test_base_url
30
+ @outer.base_url = '/foo'
31
+ assert_equal('/foo', @inner.base_url)
32
+ assert_equal('/foo', @outer.base_url)
33
+ end
34
+
35
+ def test_path
36
+ @outer.base_url = '/foo'
37
+ @outer.url = '/foo/bar'
38
+ assert_equal('bar', @inner.path)
39
+ assert_equal('bar', @outer.path)
40
+ end
41
+
42
+ def test_query_params
43
+ @outer.url = '/foo?a=b&c=d&e'
44
+ expected = {
45
+ 'a' => 'b',
46
+ 'c' => 'd',
47
+ 'e' => nil
48
+ }
49
+
50
+ assert_equal(expected, @inner.query_parameters)
51
+ assert_equal(expected, @outer.query_parameters)
52
+ end
53
+
54
+ def test_post_data
55
+ post_data = {
56
+ 'a' => 'b',
57
+ 'c' => 'd',
58
+ 'e' => nil
59
+ }
60
+
61
+ @outer.post_data = post_data
62
+ assert_equal(post_data, @outer.post_data)
63
+ assert_equal(post_data, @inner.post_data)
64
+ end
65
+
66
+ def test_server_data
67
+ server_data = { 'HTTPS' => 'On' }
68
+
69
+ @outer.raw_server_data = server_data
70
+ assert_equal('On', @inner.raw_server_value('HTTPS'))
71
+ assert_equal('On', @outer.raw_server_value('HTTPS'))
72
+
73
+ assert_nil(@inner.raw_server_value('FOO'))
74
+ assert_nil(@outer.raw_server_value('FOO'))
75
+ end
76
+
77
+ def test_to_string
78
+ @inner.method = 'POST'
79
+ @inner.url = '/foo/bar/'
80
+ @inner.body = 'foo'
81
+ @inner.update_header('foo', 'bar')
82
+
83
+ assert_equal(@inner.to_s, @outer.to_s)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,132 @@
1
+ require 'test_helper'
2
+ require 'http/sapi_mock'
3
+
4
+ module Tilia
5
+ module Http
6
+ class RequestTest < Minitest::Test
7
+ def test_construct
8
+ request = Request.new('GET', '/foo', 'User-Agent' => 'Evert')
9
+ assert_equal('GET', request.method)
10
+ assert_equal('/foo', request.url)
11
+ assert_equal({ 'User-Agent' => ['Evert'] }, request.headers)
12
+ end
13
+
14
+ def test_get_query_parameters
15
+ request = Request.new('GET', '/foo?a=b&c&d=e')
16
+ assert_equal(
17
+ {
18
+ 'a' => 'b',
19
+ 'c' => nil,
20
+ 'd' => 'e'
21
+ },
22
+ request.query_parameters
23
+ )
24
+ end
25
+
26
+ def test_get_query_parameters_no_data
27
+ request = Request.new('GET', '/foo')
28
+ assert_equal({}, request.query_parameters)
29
+ end
30
+
31
+ def test_create_from_php_request
32
+ sapi = SapiMock.new('REQUEST_METHOD' => 'PUT')
33
+
34
+ request = sapi.request
35
+ assert_equal('PUT', request.method)
36
+ end
37
+
38
+ def test_get_absolute_url
39
+ s = {
40
+ 'HTTP_HOST' => 'sabredav.org',
41
+ 'REQUEST_URI' => '/foo'
42
+ }
43
+
44
+ r = Sapi.create_from_server_array(s)
45
+
46
+ assert_equal('http://sabredav.org/foo', r.absolute_url)
47
+
48
+ s = {
49
+ 'HTTP_HOST' => 'sabredav.org',
50
+ 'REQUEST_URI' => '/foo',
51
+ 'HTTPS' => 'on'
52
+ }
53
+
54
+ r = Sapi.create_from_server_array(s)
55
+
56
+ assert_equal('https://sabredav.org/foo', r.absolute_url)
57
+ end
58
+
59
+ def test_get_post_data
60
+ post = { 'bla' => 'foo' }
61
+ r = Request.new
62
+ r.post_data = post
63
+ assert_equal(post, r.post_data)
64
+ end
65
+
66
+ def test_get_path
67
+ request = Request.new
68
+ request.base_url = '/foo'
69
+ request.url = '/foo/bar/'
70
+
71
+ assert_equal('bar', request.path)
72
+ end
73
+
74
+ def test_get_path_stripped_query
75
+ request = Request.new
76
+ request.base_url = '/foo'
77
+ request.url = '/foo/bar/?a=b'
78
+
79
+ assert_equal('bar', request.path)
80
+ end
81
+
82
+ def test_get_path_missing_slash
83
+ request = Request.new
84
+ request.base_url = '/foo/'
85
+ request.url = '/foo'
86
+
87
+ assert_equal('', request.path)
88
+ end
89
+
90
+ def test_get_path_outside_base_url
91
+ request = Request.new
92
+ request.base_url = '/foo/'
93
+ request.url = '/bar/'
94
+
95
+ assert_raises(RuntimeError) { request.path }
96
+ end
97
+
98
+ def test_to_string
99
+ request = Request.new('PUT', '/foo/bar', 'Content-Type' => 'text/xml')
100
+ request.body = 'foo'
101
+
102
+ expected = <<HI
103
+ PUT /foo/bar HTTP/1.1\r
104
+ Content-Type: text/xml\r
105
+ \r
106
+ foo
107
+ HI
108
+ expected.chomp!
109
+ assert_equal(expected, request.to_s)
110
+ end
111
+
112
+ def test_to_string_authorization
113
+ request = Request.new('PUT', '/foo/bar', 'Content-Type' => 'text/xml', 'Authorization' => 'Basic foobar')
114
+ request.body = 'foo'
115
+
116
+ expected = <<HI
117
+ PUT /foo/bar HTTP/1.1\r
118
+ Content-Type: text/xml\r
119
+ Authorization: Basic REDACTED\r
120
+ \r
121
+ foo
122
+ HI
123
+ expected.chomp!
124
+ assert_equal(expected, request.to_s)
125
+ end
126
+
127
+ def test_constructor_with_array
128
+ assert_raises(ArgumentError) { Request.new([]) }
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,28 @@
1
+ require 'test_helper'
2
+
3
+ module Tilia
4
+ module Http
5
+ class ResponseDecoratorTest < Minitest::Test
6
+ def setup
7
+ @inner = Response.new
8
+ @outer = ResponseDecorator.new(@inner)
9
+ end
10
+
11
+ def test_status
12
+ @outer.status = 201
13
+ assert_equal(201, @inner.status)
14
+ assert_equal(201, @outer.status)
15
+ assert_equal('Created', @inner.status_text)
16
+ assert_equal('Created', @outer.status_text)
17
+ end
18
+
19
+ def test_to_string
20
+ @inner.status = 201
21
+ @inner.body = 'foo'
22
+ @inner.update_header('foo', 'bar')
23
+
24
+ assert_equal(@inner.to_s, @outer.to_s)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,38 @@
1
+ require 'test_helper'
2
+
3
+ module Tilia
4
+ module Http
5
+ class ResponseTest < Minitest::Test
6
+ def test_construct
7
+ response = Response.new(200, 'Content-Type' => 'text/xml')
8
+ assert_equal(200, response.status)
9
+ assert_equal('OK', response.status_text)
10
+ end
11
+
12
+ def test_set_status
13
+ response = Response.new
14
+ response.status = '402 Where\'s my money?'
15
+ assert_equal(402, response.status)
16
+ assert_equal('Where\'s my money?', response.status_text)
17
+ end
18
+
19
+ def test_invalid_status
20
+ assert_raises(ArgumentError) { Response.new(1000) }
21
+ end
22
+
23
+ def test_to_string
24
+ response = Response.new(200, 'Content-Type' => 'text/xml')
25
+ response.body = 'foo'
26
+
27
+ expected = <<HI
28
+ HTTP/1.1 200 OK\r
29
+ Content-Type: text/xml\r
30
+ \r
31
+ foo
32
+ HI
33
+ expected.chomp!
34
+ assert_equal(expected, response.to_s)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ module Tilia
2
+ module Http
3
+ # TODO: document
4
+ class SapiMock < Sapi
5
+ # TODO: document
6
+ def initialize(env = {})
7
+ env = Rack::MockRequest.env_for.merge env
8
+ super(env)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,133 @@
1
+ require 'test_helper'
2
+ require 'base64'
3
+ require 'stringio'
4
+ require 'http/sapi_mock'
5
+
6
+ module Tilia
7
+ module Http
8
+ class SapiTest < Minitest::Test
9
+ def test_construct_from_server_array
10
+ request = Sapi.create_from_server_array(
11
+ 'REQUEST_URI' => '/foo',
12
+ 'REQUEST_METHOD' => 'GET',
13
+ 'HTTP_USER_AGENT' => 'Evert',
14
+ 'CONTENT_TYPE' => 'text/xml',
15
+ 'CONTENT_LENGTH' => '400',
16
+ 'SERVER_PROTOCOL' => 'HTTP/1.0'
17
+ )
18
+
19
+ assert_equal('GET', request.method)
20
+ assert_equal('/foo', request.url)
21
+ assert_equal(
22
+ {
23
+ 'User-Agent' => ['Evert'],
24
+ 'Content-Type' => ['text/xml'],
25
+ 'Content-Length' => ['400']
26
+ },
27
+ request.headers
28
+ )
29
+
30
+ assert_equal('1.0', request.http_version)
31
+
32
+ assert_equal('400', request.raw_server_value('CONTENT_LENGTH'))
33
+ assert_nil(request.raw_server_value('FOO'))
34
+ end
35
+
36
+ def test_construct_php_auth
37
+ request = Sapi.create_from_server_array(
38
+ 'REQUEST_URI' => '/foo',
39
+ 'REQUEST_METHOD' => 'GET',
40
+ 'PHP_AUTH_USER' => 'user',
41
+ 'PHP_AUTH_PW' => 'pass'
42
+ )
43
+
44
+ assert_equal('GET', request.method)
45
+ assert_equal('/foo', request.url)
46
+ assert_equal(
47
+ {
48
+ 'Authorization' => ["Basic #{Base64.strict_encode64('user:pass')}"]
49
+ },
50
+ request.headers
51
+ )
52
+ end
53
+
54
+ def test_construct_php_auth_digest
55
+ request = Sapi.create_from_server_array(
56
+ 'REQUEST_URI' => '/foo',
57
+ 'REQUEST_METHOD' => 'GET',
58
+ 'PHP_AUTH_DIGEST' => 'blabla'
59
+ )
60
+
61
+ assert_equal('GET', request.method)
62
+ assert_equal('/foo', request.url)
63
+ assert_equal(
64
+ {
65
+ 'Authorization' => ['Digest blabla']
66
+ },
67
+ request.headers
68
+ )
69
+ end
70
+
71
+ def test_construct_redirect_auth
72
+ request = Sapi.create_from_server_array(
73
+ 'REQUEST_URI' => '/foo',
74
+ 'REQUEST_METHOD' => 'GET',
75
+ 'REDIRECT_HTTP_AUTHORIZATION' => 'Basic bla'
76
+ )
77
+
78
+ assert_equal('GET', request.method)
79
+ assert_equal('/foo', request.url)
80
+ assert_equal(
81
+ {
82
+ 'Authorization' => ['Basic bla']
83
+ },
84
+ request.headers
85
+ )
86
+ end
87
+
88
+ def test_send
89
+ response = Response.new(204, 'Content-Type' => 'text/xml;charset=UTF-8')
90
+
91
+ # Second Content-Type header. Normally this doesn't make sense.
92
+ response.add_header('Content-Type', 'application/xml')
93
+ response.body = 'foo'
94
+
95
+ (_status, headers, body) = Sapi.send_response(response)
96
+
97
+ assert_equal(
98
+ {
99
+ 'Content-Type' => "text/xml;charset=UTF-8\napplication/xml"
100
+ },
101
+ headers
102
+ )
103
+
104
+ assert_equal('foo', body.readlines.join(''))
105
+ end
106
+
107
+ def test_send_limited_by_content_length_string
108
+ response = Response.new(200)
109
+
110
+ response.add_header('Content-Length', 19)
111
+ response.body = 'Send this sentence. Ignore this one.'
112
+
113
+ (_status, _headers, body) = Sapi.send_response(response)
114
+
115
+ assert_equal('Send this sentence.', body.readlines.join(''))
116
+ end
117
+
118
+ def test_send_limited_by_content_length_stream
119
+ response = Response.new(200, 'Content-Length' => 19)
120
+
121
+ body = StringIO.new
122
+ body.write('Ignore this. Send this sentence. Ignore this too.')
123
+ body.rewind
124
+ body.read(13)
125
+ response.body = body
126
+
127
+ (_status, _headers, body) = Sapi.send_response(response)
128
+
129
+ assert_equal('Send this sentence.', body.readlines.join(''))
130
+ end
131
+ end
132
+ end
133
+ end