tilia-http 4.1.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 (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