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,235 @@
1
+ require 'cgi'
2
+ module Tilia
3
+ module Http
4
+ # The Request class represents a single HTTP request.
5
+ #
6
+ # You can either simply construct the object from scratch, or if you need
7
+ # access to the current HTTP request, use Sapi::getRequest.
8
+ class Request
9
+ include Tilia::Http::Message
10
+ include Tilia::Http::RequestInterface
11
+
12
+ protected
13
+
14
+ # HTTP Method
15
+ #
16
+ # @return [String]
17
+ attr_accessor :method
18
+
19
+ # Request Url
20
+ #
21
+ # @return [String]
22
+ attr_accessor :url
23
+
24
+ public
25
+
26
+ # Creates the request object
27
+ #
28
+ # @param [String] method
29
+ # @param [String] url
30
+ # @param array headers
31
+ # @param resource body
32
+ def initialize(method = nil, url = nil, headers = nil, body = nil)
33
+ initialize_message
34
+ @base_url = '/' # RUBY
35
+ @post_data = {}
36
+ @raw_server_data = {}
37
+
38
+ fail ArgumentError, 'The first argument for this constructor should be a string or null, not an array. Did you upgrade from sabre/http 1.0 to 2.0?' if method.is_a?(Array)
39
+
40
+ self.method = method if method
41
+ self.url = url if url
42
+ update_headers(headers) if headers
43
+ self.body = body if body
44
+ end
45
+
46
+ # Returns the current HTTP method
47
+ #
48
+ # @return [String]
49
+ attr_reader :method
50
+
51
+ # Sets the HTTP method
52
+ #
53
+ # @param [String] method
54
+ # @return [void]
55
+ attr_writer :method
56
+
57
+ # Returns the request url.
58
+ #
59
+ # @return [String]
60
+ attr_reader :url
61
+
62
+ # Sets the request url.
63
+ #
64
+ # @param [String] url
65
+ # @return [void]
66
+ attr_writer :url
67
+
68
+ # Returns the list of query parameters.
69
+ #
70
+ # This is equivalent to PHP's $_GET superglobal.
71
+ #
72
+ # @return array
73
+ def query_parameters
74
+ url = self.url
75
+
76
+ if !(index = url.index('?'))
77
+ {}
78
+ else
79
+ query_params = CGI.parse(url[index + 1..-1])
80
+ query_params.keys.each do |key|
81
+ query_params[key] = query_params[key][0] if query_params[key].size == 1
82
+ query_params[key] = nil if query_params[key].size == 0
83
+ end
84
+ query_params
85
+ end
86
+ end
87
+
88
+ # Sets the absolute url.
89
+ #
90
+ # @param [String] url
91
+ # @return [void]
92
+ attr_writer :absolute_url
93
+
94
+ # Returns the absolute url.
95
+ #
96
+ # @return [String]
97
+ attr_reader :absolute_url
98
+
99
+ protected
100
+
101
+ # Base url
102
+ #
103
+ # @return [String]
104
+ attr_accessor :base_url
105
+
106
+ public
107
+
108
+ # Sets a base url.
109
+ #
110
+ # This url is used for relative path calculations.
111
+ #
112
+ # @param [String] url
113
+ # @return [void]
114
+ attr_writer :base_url
115
+
116
+ # Returns the current base url.
117
+ #
118
+ # @return [String]
119
+ attr_reader :base_url
120
+
121
+ # Returns the relative path.
122
+ #
123
+ # This is being calculated using the base url. This path will not start
124
+ # with a slash, so it will always return something like
125
+ # 'example/path.html'.
126
+ #
127
+ # If the full path is equal to the base url, this method will return an
128
+ # empty string.
129
+ #
130
+ # This method will also urldecode the path, and if the url was incoded as
131
+ # ISO-8859-1, it will convert it to UTF-8.
132
+ #
133
+ # If the path is outside of the base url, a LogicException will be thrown.
134
+ #
135
+ # @return [String]
136
+ def path
137
+ # Removing duplicated slashes.
138
+ uri = url.gsub('//', '/')
139
+
140
+ uri = Tilia::Uri.normalize(uri)
141
+ base_uri = Tilia::Uri.normalize(base_url)
142
+
143
+ if uri.index(base_uri) == 0
144
+ # We're not interested in the query part (everything after the ?).
145
+ uri = uri.split('?').first
146
+ return Tilia::Http::UrlUtil.decode_path(uri[base_uri.size..-1]).gsub(%r{^/+|/+$}, '')
147
+ elsif uri + '/' == base_uri
148
+ # A special case, if the baseUri was accessed without a trailing
149
+ # slash, we'll accept it as well.
150
+ return ''
151
+ end
152
+
153
+ fail "Requested uri (#{url}) is out of base uri (#{base_url})"
154
+ end
155
+
156
+ protected
157
+
158
+ # Equivalent of PHP's $_POST.
159
+ #
160
+ # @return array
161
+ attr_accessor :post_data
162
+
163
+ public
164
+
165
+ # Sets the post data.
166
+ #
167
+ # This is equivalent to PHP's $_POST superglobal.
168
+ #
169
+ # This would not have been needed, if POST data was accessible as
170
+ # php://input, but unfortunately we need to special case it.
171
+ #
172
+ # @param array post_data
173
+ # @return [void]
174
+ attr_writer :post_data
175
+
176
+ # Returns the POST data.
177
+ #
178
+ # This is equivalent to PHP's $_POST superglobal.
179
+ #
180
+ # @return array
181
+ attr_reader :post_data
182
+
183
+ protected
184
+
185
+ # An array containing the raw _SERVER array.
186
+ #
187
+ # @return array
188
+ attr_accessor :raw_server_data
189
+
190
+ public
191
+
192
+ # Returns an item from the _SERVER array.
193
+ #
194
+ # If the value does not exist in the array, null is returned.
195
+ #
196
+ # @param [String] value_name
197
+ # @return [String, nil]
198
+ def raw_server_value(value_name)
199
+ @raw_server_data[value_name]
200
+ end
201
+
202
+ # Sets the _SERVER array.
203
+ #
204
+ # @param array data
205
+ # @return [void]
206
+ def raw_server_data=(data)
207
+ @raw_server_data = data.dup
208
+ end
209
+
210
+ # Serializes the request object as a string.
211
+ #
212
+ # This is useful for debugging purposes.
213
+ #
214
+ # @return [String]
215
+ def to_s
216
+ out = "#{method} #{url} HTTP/#{http_version}\r\n"
217
+
218
+ headers.each do |key, value|
219
+ value.each do |v|
220
+ if key == 'Authorization'
221
+ v = v.split(' ').first
222
+ v << ' REDACTED'
223
+ end
224
+ out << "#{key}: #{v}\r\n"
225
+ end
226
+ end
227
+
228
+ out << "\r\n"
229
+ out << body_as_string
230
+
231
+ out
232
+ end
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,160 @@
1
+ module Tilia
2
+ module Http
3
+ # Request Decorator
4
+ #
5
+ # This helper class allows you to easily create decorators for the Request
6
+ # object.
7
+ class RequestDecorator
8
+ include Tilia::Http::RequestInterface
9
+ include Tilia::Http::MessageDecoratorTrait
10
+
11
+ # Constructor.
12
+ #
13
+ # @param RequestInterface inner
14
+ def initialize(inner)
15
+ @inner = inner
16
+ end
17
+
18
+ # Returns the current HTTP method
19
+ #
20
+ # @return [String]
21
+ def method
22
+ @inner.method
23
+ end
24
+
25
+ # Sets the HTTP method
26
+ #
27
+ # @param [String] method
28
+ # @return [void]
29
+ def method=(method)
30
+ @inner.method = method
31
+ end
32
+
33
+ # Returns the request url.
34
+ #
35
+ # @return [String]
36
+ def url
37
+ @inner.url
38
+ end
39
+
40
+ # Sets the request url.
41
+ #
42
+ # @param [String] url
43
+ # @return [void]
44
+ def url=(url)
45
+ @inner.url = url
46
+ end
47
+
48
+ # Returns the absolute url.
49
+ #
50
+ # @return [String]
51
+ def absolute_url
52
+ @inner.absolute_url
53
+ end
54
+
55
+ # Sets the absolute url.
56
+ #
57
+ # @param [String] url
58
+ # @return [void]
59
+ def absolute_url=(url)
60
+ @inner.absolute_url = url
61
+ end
62
+
63
+ # Returns the current base url.
64
+ #
65
+ # @return [String]
66
+ def base_url
67
+ @inner.base_url
68
+ end
69
+
70
+ # Sets a base url.
71
+ #
72
+ # This url is used for relative path calculations.
73
+ #
74
+ # The base url should default to /
75
+ #
76
+ # @param [String] url
77
+ # @return [void]
78
+ def base_url=(url)
79
+ @inner.base_url = url
80
+ end
81
+
82
+ # Returns the relative path.
83
+ #
84
+ # This is being calculated using the base url. This path will not start
85
+ # with a slash, so it will always return something like
86
+ # 'example/path.html'.
87
+ #
88
+ # If the full path is equal to the base url, this method will return an
89
+ # empty string.
90
+ #
91
+ # This method will also urldecode the path, and if the url was incoded as
92
+ # ISO-8859-1, it will convert it to UTF-8.
93
+ #
94
+ # If the path is outside of the base url, a LogicException will be thrown.
95
+ #
96
+ # @return [String]
97
+ def path
98
+ @inner.path
99
+ end
100
+
101
+ # Returns the list of query parameters.
102
+ #
103
+ # This is equivalent to PHP's $_GET superglobal.
104
+ #
105
+ # @return array
106
+ def query_parameters
107
+ @inner.query_parameters
108
+ end
109
+
110
+ # Returns the POST data.
111
+ #
112
+ # This is equivalent to PHP's $_POST superglobal.
113
+ #
114
+ # @return array
115
+ def post_data
116
+ @inner.post_data
117
+ end
118
+
119
+ # Sets the post data.
120
+ #
121
+ # This is equivalent to PHP's $_POST superglobal.
122
+ #
123
+ # This would not have been needed, if POST data was accessible as
124
+ # php://input, but unfortunately we need to special case it.
125
+ #
126
+ # @param array post_data
127
+ # @return [void]
128
+ def post_data=(post_data)
129
+ @inner.post_data = post_data
130
+ end
131
+
132
+ # Returns an item from the _SERVER array.
133
+ #
134
+ # If the value does not exist in the array, null is returned.
135
+ #
136
+ # @param [String] value_name
137
+ # @return [String, nil]
138
+ def raw_server_value(value_name)
139
+ @inner.raw_server_value(value_name)
140
+ end
141
+
142
+ # Sets the _SERVER array.
143
+ #
144
+ # @param array data
145
+ # @return [void]
146
+ def raw_server_data=(data)
147
+ @inner.raw_server_data = data
148
+ end
149
+
150
+ # Serializes the request object as a string.
151
+ #
152
+ # This is useful for debugging purposes.
153
+ #
154
+ # @return [String]
155
+ def to_s
156
+ @inner.to_s
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,126 @@
1
+ module Tilia
2
+ module Http
3
+ # The RequestInterface represents a HTTP request.
4
+ module RequestInterface
5
+ include Tilia::Http::MessageInterface
6
+
7
+ # Returns the current HTTP method
8
+ #
9
+ # @return [String]
10
+ def method
11
+ end
12
+
13
+ # Sets the HTTP method
14
+ #
15
+ # @param [String] method
16
+ # @return [void]
17
+ def method=(_method)
18
+ end
19
+
20
+ # Returns the request url.
21
+ #
22
+ # @return [String]
23
+ def url
24
+ end
25
+
26
+ # Sets the request url.
27
+ #
28
+ # @param [String] url
29
+ # @return [void]
30
+ def url=(_url)
31
+ end
32
+
33
+ # Returns the absolute url.
34
+ #
35
+ # @return [String]
36
+ def absolute_url
37
+ end
38
+
39
+ # Sets the absolute url.
40
+ #
41
+ # @param [String] url
42
+ # @return [void]
43
+ def absolute_url=(_url)
44
+ end
45
+
46
+ # Returns the current base url.
47
+ #
48
+ # @return [String]
49
+ def base_url
50
+ end
51
+
52
+ # Sets a base url.
53
+ #
54
+ # This url is used for relative path calculations.
55
+ #
56
+ # The base url should default to /
57
+ #
58
+ # @param [String] url
59
+ # @return [void]
60
+ def base_url=(_url)
61
+ end
62
+
63
+ # Returns the relative path.
64
+ #
65
+ # This is being calculated using the base url. This path will not start
66
+ # with a slash, so it will always return something like
67
+ # 'example/path.html'.
68
+ #
69
+ # If the full path is equal to the base url, this method will return an
70
+ # empty string.
71
+ #
72
+ # This method will also urldecode the path, and if the url was incoded as
73
+ # ISO-8859-1, it will convert it to UTF-8.
74
+ #
75
+ # If the path is outside of the base url, a LogicException will be thrown.
76
+ #
77
+ # @return [String]
78
+ def path
79
+ end
80
+
81
+ # Returns the list of query parameters.
82
+ #
83
+ # This is equivalent to PHP's $_GET superglobal.
84
+ #
85
+ # @return array
86
+ def query_parameters
87
+ end
88
+
89
+ # Returns the POST data.
90
+ #
91
+ # This is equivalent to PHP's $_POST superglobal.
92
+ #
93
+ # @return array
94
+ def post_data
95
+ end
96
+
97
+ # Sets the post data.
98
+ #
99
+ # This is equivalent to PHP's $_POST superglobal.
100
+ #
101
+ # This would not have been needed, if POST data was accessible as
102
+ # php://input, but unfortunately we need to special case it.
103
+ #
104
+ # @param array post_data
105
+ # @return [void]
106
+ def post_data=(_post_data)
107
+ end
108
+
109
+ # Returns an item from the _SERVER array.
110
+ #
111
+ # If the value does not exist in the array, null is returned.
112
+ #
113
+ # @param [String] value_name
114
+ # @return [String, nil]
115
+ def raw_server_value(_value_name)
116
+ end
117
+
118
+ # Sets the _SERVER array.
119
+ #
120
+ # @param array data
121
+ # @return [void]
122
+ def raw_server_data=(_data)
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,164 @@
1
+ module Tilia
2
+ module Http
3
+ # This class represents a single HTTP response.
4
+ class Response
5
+ include Tilia::Http::Message
6
+ include Tilia::Http::ResponseInterface
7
+
8
+ # This is the list of currently registered HTTP status codes.
9
+ #
10
+ # @return array
11
+ def self.status_codes
12
+ {
13
+ 100 => 'Continue',
14
+ 101 => 'Switching Protocols',
15
+ 102 => 'Processing',
16
+ 200 => 'OK',
17
+ 201 => 'Created',
18
+ 202 => 'Accepted',
19
+ 203 => 'Non-Authorative Information',
20
+ 204 => 'No Content',
21
+ 205 => 'Reset Content',
22
+ 206 => 'Partial Content',
23
+ 207 => 'Multi-Status', # RFC 4918
24
+ 208 => 'Already Reported', # RFC 5842
25
+ 226 => 'IM Used', # RFC 3229
26
+ 300 => 'Multiple Choices',
27
+ 301 => 'Moved Permanently',
28
+ 302 => 'Found',
29
+ 303 => 'See Other',
30
+ 304 => 'Not Modified',
31
+ 305 => 'Use Proxy',
32
+ 307 => 'Temporary Redirect',
33
+ 308 => 'Permanent Redirect',
34
+ 400 => 'Bad Request',
35
+ 401 => 'Unauthorized',
36
+ 402 => 'Payment Required',
37
+ 403 => 'Forbidden',
38
+ 404 => 'Not Found',
39
+ 405 => 'Method Not Allowed',
40
+ 406 => 'Not Acceptable',
41
+ 407 => 'Proxy Authentication Required',
42
+ 408 => 'Request Timeout',
43
+ 409 => 'Conflict',
44
+ 410 => 'Gone',
45
+ 411 => 'Length Required',
46
+ 412 => 'Precondition failed',
47
+ 413 => 'Request Entity Too Large',
48
+ 414 => 'Request-URI Too Long',
49
+ 415 => 'Unsupported Media Type',
50
+ 416 => 'Requested Range Not Satisfiable',
51
+ 417 => 'Expectation Failed',
52
+ 418 => 'I\'m a teapot', # RFC 2324
53
+ 421 => 'Misdirected Request', # RFC7540 (HTTP/2)
54
+ 422 => 'Unprocessable Entity', # RFC 4918
55
+ 423 => 'Locked', # RFC 4918
56
+ 424 => 'Failed Dependency', # RFC 4918
57
+ 426 => 'Upgrade Required',
58
+ 428 => 'Precondition Required', # RFC 6585
59
+ 429 => 'Too Many Requests', # RFC 6585
60
+ 431 => 'Request Header Fields Too Large', # RFC 6585
61
+ 451 => 'Unavailable For Legal Reasons', # draft-tbray-http-legally-restricted-status
62
+ 500 => 'Internal Server Error',
63
+ 501 => 'Not Implemented',
64
+ 502 => 'Bad Gateway',
65
+ 503 => 'Service Unavailable',
66
+ 504 => 'Gateway Timeout',
67
+ 505 => 'HTTP Version not supported',
68
+ 506 => 'Variant Also Negotiates',
69
+ 507 => 'Insufficient Storage', # RFC 4918
70
+ 508 => 'Loop Detected', # RFC 5842
71
+ 509 => 'Bandwidth Limit Exceeded', # non-standard
72
+ 510 => 'Not extended',
73
+ 511 => 'Network Authentication Required' # RFC 6585
74
+ }
75
+ end
76
+
77
+ protected
78
+
79
+ # HTTP status code
80
+ #
81
+ # @return int
82
+ attr_accessor :status
83
+
84
+ # HTTP status text
85
+ #
86
+ # @return [String]
87
+ attr_accessor :status_text
88
+
89
+ public
90
+
91
+ # Creates the response object
92
+ #
93
+ # @param [String, Fixnum] status
94
+ # @param array headers
95
+ # @param resource body
96
+ # @return [void]
97
+ def initialize(status = nil, headers = nil, body = nil)
98
+ initialize_message # RUBY
99
+
100
+ self.status = status if status
101
+ update_headers(headers) if headers
102
+ self.body = body if body
103
+ end
104
+
105
+ # Returns the current HTTP status code.
106
+ #
107
+ # @return int
108
+ attr_reader :status
109
+
110
+ # Returns the human-readable status string.
111
+ #
112
+ # In the case of a 200, this may for example be 'OK'.
113
+ #
114
+ # @return [String]
115
+ attr_reader :status_text
116
+
117
+ # Sets the HTTP status code.
118
+ #
119
+ # This can be either the full HTTP status code with human readable string,
120
+ # for example: "403 I can't let you do that, Dave".
121
+ #
122
+ # Or just the code, in which case the appropriate default message will be
123
+ # added.
124
+ #
125
+ # @param [String, Fixnum] status
126
+ # @throws \InvalidArgumentExeption
127
+ # @return [void]
128
+ def status=(status)
129
+ if status.is_a?(Fixnum) || status =~ /^\d+$/
130
+ status_code = status
131
+ status_text = self.class.status_codes.key?(status.to_i) ? self.class.status_codes[status.to_i] : 'Unkown'
132
+ else
133
+ (
134
+ status_code,
135
+ status_text
136
+ ) = status.split(' ', 2)
137
+ end
138
+
139
+ fail ArgumentError, 'The HTTP status code must be exactly 3 digits' if status_code.to_i < 100 || status_code.to_i > 999
140
+
141
+ @status = status_code.to_i
142
+ @status_text = status_text
143
+ end
144
+
145
+ # Serializes the response object as a string.
146
+ #
147
+ # This is useful for debugging purposes.
148
+ #
149
+ # @return [String]
150
+ def to_s
151
+ str = "HTTP/#{http_version} #{status} #{status_text}\r\n"
152
+ headers.each do |key, value|
153
+ value.each do |v|
154
+ str << "#{key}: #{v}\r\n"
155
+ end
156
+ end
157
+
158
+ str << "\r\n"
159
+ str << body_as_string
160
+ str
161
+ end
162
+ end
163
+ end
164
+ end