rest-man 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/{multi-matrix-test.yml → ci.yml} +10 -1
  3. data/.github/workflows/single-matrix-test.yml +1 -0
  4. data/.gitignore +2 -0
  5. data/.rubocop-disables.yml +4 -29
  6. data/AUTHORS +5 -1
  7. data/CHANGELOG.md +14 -0
  8. data/Gemfile +3 -0
  9. data/README.md +30 -37
  10. data/Rakefile +1 -59
  11. data/_doc/lib/restman/abstract_response/_follow_redirection.rdoc +7 -0
  12. data/_doc/lib/restman/abstract_response/beautify_headers.rdoc +24 -0
  13. data/_doc/lib/restman/abstract_response/cookie_jar.rdoc +4 -0
  14. data/_doc/lib/restman/abstract_response/cookies.rdoc +12 -0
  15. data/_doc/lib/restman/abstract_response/follow_get_redirection.rdoc +2 -0
  16. data/_doc/lib/restman/abstract_response/follow_redirection.rdoc +2 -0
  17. data/_doc/lib/restman/abstract_response/headers.rdoc +2 -0
  18. data/_doc/lib/restman/abstract_response/response_set_vars.rdoc +5 -0
  19. data/_doc/lib/restman/abstract_response/return.rdoc +9 -0
  20. data/_doc/lib/restman/add_before_execution_proc.rdoc +2 -0
  21. data/_doc/lib/restman/create_log.rdoc +2 -0
  22. data/_doc/lib/restman/exception.rdoc +6 -0
  23. data/_doc/lib/restman/exceptions/timeout.rdoc +4 -0
  24. data/_doc/lib/restman/exceptions.rdoc +4 -0
  25. data/_doc/lib/restman/log=.rdoc +3 -0
  26. data/_doc/lib/restman/params_array/new.rdoc +20 -0
  27. data/_doc/lib/restman/params_array/process_pair.rdoc +4 -0
  28. data/_doc/lib/restman/params_array.rdoc +11 -0
  29. data/_doc/lib/restman/platform/jruby?.rdoc +4 -0
  30. data/_doc/lib/restman/platform/mac_mri?.rdoc +5 -0
  31. data/_doc/lib/restman/proxy.rdoc +2 -0
  32. data/_doc/lib/restman/proxy_set?.rdoc +5 -0
  33. data/_doc/lib/restman/raw_response/new.rdoc +6 -0
  34. data/_doc/lib/restman/raw_response.rdoc +10 -0
  35. data/_doc/lib/restman/request/cookie_jar.rdoc +3 -0
  36. data/_doc/lib/restman/request/cookies.rdoc +11 -0
  37. data/_doc/lib/restman/request/default_headers.rdoc +5 -0
  38. data/_doc/lib/restman/request/default_ssl_cert_store.rdoc +8 -0
  39. data/_doc/lib/restman/request/init/cookie_jar.rdoc +55 -0
  40. data/_doc/lib/restman/request/init/http_method.rdoc +15 -0
  41. data/_doc/lib/restman/request/make_cookie_header.rdoc +8 -0
  42. data/_doc/lib/restman/request/make_headers.rdoc +25 -0
  43. data/_doc/lib/restman/request/maybe_convert_extension.rdoc +18 -0
  44. data/_doc/lib/restman/request/process_result.rdoc +4 -0
  45. data/_doc/lib/restman/request/proxy_uri.rdoc +7 -0
  46. data/_doc/lib/restman/request/stringify_headers.rdoc +9 -0
  47. data/_doc/lib/restman/request/use_ssl.rdoc +4 -0
  48. data/_doc/lib/restman/request.rdoc +46 -0
  49. data/_doc/lib/restman/reset_before_execution_procs.rdoc +1 -0
  50. data/_doc/lib/restman/resource/[].rdoc +25 -0
  51. data/_doc/lib/restman/resource.rdoc +33 -0
  52. data/_doc/lib/restman/response/body.rdoc +7 -0
  53. data/_doc/lib/restman/response/create.rdoc +10 -0
  54. data/_doc/lib/restman/response/fix_encoding.rdoc +2 -0
  55. data/_doc/lib/restman/statuses.rdoc +11 -0
  56. data/_doc/lib/restman/utils/cgi_parse_header.rdoc +6 -0
  57. data/_doc/lib/restman/utils/encode_query_string.rdoc +90 -0
  58. data/_doc/lib/restman/utils/escape.rdoc +11 -0
  59. data/_doc/lib/restman/utils/flatten_params.rdoc +16 -0
  60. data/_doc/lib/restman/utils/get_encoding_from_headers.rdoc +24 -0
  61. data/_doc/lib/restman.rdoc +43 -0
  62. data/bin/console +15 -0
  63. data/lib/restman/abstract_response.rb +13 -60
  64. data/lib/restman/exception.rb +43 -0
  65. data/lib/restman/exceptions/exception_with_response.rb +7 -0
  66. data/lib/restman/exceptions/exceptions_map.rb +26 -0
  67. data/lib/restman/exceptions/request_failed.rb +15 -0
  68. data/lib/restman/exceptions/server_broke_connection.rb +13 -0
  69. data/lib/restman/exceptions/timeout.rb +37 -0
  70. data/lib/restman/params_array/process_pair.rb +39 -0
  71. data/lib/restman/params_array.rb +3 -48
  72. data/lib/restman/payload/base.rb +57 -0
  73. data/lib/restman/payload/multipart/write_content_disposition.rb +88 -0
  74. data/lib/restman/payload/multipart.rb +56 -0
  75. data/lib/restman/payload/streamed.rb +22 -0
  76. data/lib/restman/payload/url_encoded.rb +14 -0
  77. data/lib/restman/payload.rb +14 -196
  78. data/lib/restman/platform.rb +2 -18
  79. data/lib/restman/raw_response.rb +2 -14
  80. data/lib/restman/request/default_ssl_cert_store.rb +13 -0
  81. data/lib/restman/request/fetch_body_to_tempfile.rb +58 -0
  82. data/lib/restman/request/init/cookie_jar.rb +65 -0
  83. data/lib/restman/request/init/ssl_opts.rb +70 -0
  84. data/lib/restman/request/init/url/add_query_from_headers.rb +51 -0
  85. data/lib/restman/request/init/url/normalize_url.rb +19 -0
  86. data/lib/restman/request/init/url.rb +40 -0
  87. data/lib/restman/request/init.rb +106 -0
  88. data/lib/restman/request/log_request.rb +46 -0
  89. data/lib/restman/request/make_cookie_header.rb +16 -0
  90. data/lib/restman/request/make_headers.rb +39 -0
  91. data/lib/restman/request/maybe_convert_extension.rb +28 -0
  92. data/lib/restman/request/net_http_object.rb +25 -0
  93. data/lib/restman/request/process_result.rb +36 -0
  94. data/lib/restman/request/proxy_uri.rb +31 -0
  95. data/lib/restman/request/stringify_headers.rb +36 -0
  96. data/lib/restman/request/transmit.rb +152 -0
  97. data/lib/restman/request.rb +60 -745
  98. data/lib/restman/resource.rb +2 -60
  99. data/lib/restman/response.rb +3 -21
  100. data/lib/restman/statuses.rb +75 -0
  101. data/lib/restman/statuses_compatibility.rb +18 -0
  102. data/lib/restman/utils.rb +10 -206
  103. data/lib/restman/version.rb +1 -1
  104. data/lib/restman.rb +24 -62
  105. data/matrixeval.yml +19 -1
  106. data/rest-man.gemspec +4 -10
  107. data/spec/integration/capath_digicert/ce5e74ef.0 +1 -1
  108. data/spec/integration/request_spec.rb +13 -1
  109. data/spec/spec_helper.rb +11 -0
  110. data/spec/unit/abstract_response_spec.rb +14 -0
  111. data/spec/unit/exception_spec.rb +64 -0
  112. data/spec/unit/exceptions/backwards_campatibility_spec.rb +29 -0
  113. data/spec/unit/exceptions/exceptions_map_spec.rb +89 -0
  114. data/spec/unit/exceptions/request_failed_spec.rb +51 -0
  115. data/spec/unit/exceptions/server_broke_connection_spec.rb +8 -0
  116. data/spec/unit/exceptions/timeout_spec.rb +59 -0
  117. data/spec/unit/params_array/process_pair_spec.rb +59 -0
  118. data/spec/unit/params_array_spec.rb +15 -10
  119. data/spec/unit/payload/multipart_spec.rb +116 -0
  120. data/spec/unit/payload/streamed_spec.rb +48 -0
  121. data/spec/unit/payload/url_encoded_spec.rb +65 -0
  122. data/spec/unit/payload_spec.rb +0 -208
  123. data/spec/unit/request/init/url/add_query_from_headers_spec.rb +40 -0
  124. data/spec/unit/request/init/url/normalize_url_spec.rb +25 -0
  125. data/spec/unit/request/init_spec.rb +83 -0
  126. data/spec/unit/request_spec.rb +143 -151
  127. data/spec/unit/utils_spec.rb +96 -104
  128. metadata +132 -16
  129. data/lib/restman/exceptions.rb +0 -238
  130. data/lib/restman/windows/root_certs.rb +0 -105
  131. data/lib/restman/windows.rb +0 -8
  132. data/spec/unit/exceptions_spec.rb +0 -108
  133. data/spec/unit/windows/root_certs_spec.rb +0 -22
@@ -0,0 +1,43 @@
1
+ module RestMan
2
+ # :include: _doc/lib/restman/exception.rdoc
3
+ class Exception < RuntimeError
4
+ attr_accessor :response
5
+ attr_accessor :original_exception
6
+ attr_writer :message
7
+
8
+ def initialize response = nil, initial_response_code = nil
9
+ @response = response
10
+ @message = nil
11
+ @initial_response_code = initial_response_code
12
+ end
13
+
14
+ def http_code
15
+ # return integer for compatibility
16
+ if @response
17
+ @response.code.to_i
18
+ else
19
+ @initial_response_code
20
+ end
21
+ end
22
+
23
+ def http_headers
24
+ @response.headers if @response
25
+ end
26
+
27
+ def http_body
28
+ @response.body if @response
29
+ end
30
+
31
+ def to_s
32
+ message
33
+ end
34
+
35
+ def message
36
+ @message || default_message
37
+ end
38
+
39
+ def default_message
40
+ self.class.name
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ module RestMan
2
+
3
+ # Compatibility
4
+ class ExceptionWithResponse < RestMan::Exception
5
+ end
6
+
7
+ end
@@ -0,0 +1,26 @@
1
+ module RestMan
2
+
3
+ # :include: _doc/lib/restman/exceptions.rdoc
4
+ module Exceptions
5
+ # Map http status codes to the corresponding exception class
6
+ EXCEPTIONS_MAP = {}
7
+ end
8
+
9
+ # Create HTTP status exception classes
10
+ STATUSES.each_pair do |code, message|
11
+ klass = Class.new(RequestFailed) do
12
+ send(:define_method, :default_message) {"#{http_code ? "#{http_code} " : ''}#{message}"}
13
+ end
14
+ klass_constant = const_set(message.delete(' \-\''), klass)
15
+ Exceptions::EXCEPTIONS_MAP[code] = klass_constant
16
+ end
17
+
18
+ # Create HTTP status exception classes used for backwards compatibility
19
+ STATUSES_COMPATIBILITY.each_pair do |code, compat_list|
20
+ klass = Exceptions::EXCEPTIONS_MAP.fetch(code)
21
+ compat_list.each do |old_name|
22
+ const_set(old_name, klass)
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,15 @@
1
+ module RestMan
2
+
3
+ # The request failed with an error code not managed by the code
4
+ class RequestFailed < ExceptionWithResponse
5
+
6
+ def default_message
7
+ "HTTP status code #{http_code}"
8
+ end
9
+
10
+ def to_s
11
+ message
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,13 @@
1
+ module RestMan
2
+
3
+ # The server broke the connection prior to the request completing. Usually
4
+ # this means it crashed, or sometimes that your network connection was
5
+ # severed before it could complete.
6
+ class ServerBrokeConnection < RestMan::Exception
7
+ def initialize(message = 'Server broke connection')
8
+ super nil, nil
9
+ self.message = message
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,37 @@
1
+ module RestMan
2
+ module Exceptions
3
+ # We have to split the Exceptions module like we do here because the
4
+ # EXCEPTIONS_MAP is under Exceptions, but we depend on
5
+ # RestMan::RequestTimeout below.
6
+
7
+ # :include: _doc/lib/restman/exceptions/timeout.rdoc
8
+ class Timeout < RestMan::RequestTimeout
9
+ def initialize(message=nil, original_exception=nil)
10
+ super(nil, nil)
11
+ self.message = message if message
12
+ self.original_exception = original_exception if original_exception
13
+ end
14
+ end
15
+
16
+ # Timeout when connecting to a server. Typically wraps Net::OpenTimeout
17
+ class OpenTimeout < Timeout
18
+ def default_message
19
+ 'Timed out connecting to server'
20
+ end
21
+ end
22
+
23
+ # Timeout when reading from a server. Typically wraps Net::ReadTimeout
24
+ class ReadTimeout < Timeout
25
+ def default_message
26
+ 'Timed out reading data from server'
27
+ end
28
+ end
29
+
30
+ # Timeout when writing to a server. Typically wraps Net::WriteTimeout
31
+ class WriteTimeout < Timeout
32
+ def default_message
33
+ 'Timed out writing data to server'
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,39 @@
1
+ module RestMan
2
+ class ParamsArray
3
+
4
+ # :include: _doc/lib/restman/params_array/process_pair.rdoc
5
+ class ProcessPair < ActiveMethod::Base
6
+
7
+ argument :pair
8
+
9
+ def call
10
+ case pair
11
+ when Hash
12
+ convert_hash_pair_to_array
13
+ when Array
14
+ parse_array_pair
15
+ else
16
+ ProcessPair.call(pair.to_a)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def convert_hash_pair_to_array
23
+ if pair.length != 1
24
+ raise ArgumentError.new("Bad # of fields for pair: #{pair.inspect}")
25
+ end
26
+
27
+ pair.to_a.fetch(0)
28
+ end
29
+
30
+ def parse_array_pair
31
+ if pair.length > 2
32
+ raise ArgumentError.new("Bad # of fields for pair: #{pair.inspect}")
33
+ end
34
+ [pair.fetch(0), pair[1]]
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -1,33 +1,10 @@
1
1
  module RestMan
2
2
 
3
- # The ParamsArray class is used to represent an ordered list of [key, value]
4
- # pairs. Use this when you need to include a key multiple times or want
5
- # explicit control over parameter ordering.
6
- #
7
- # Most of the request payload & parameter functions normally accept a Hash of
8
- # keys => values, which does not allow for duplicated keys.
9
- #
10
- # @see RestMan::Utils.encode_query_string
11
- # @see RestMan::Utils.flatten_params
12
- #
3
+ # :include: _doc/lib/restman/params_array.rdoc
13
4
  class ParamsArray
14
5
  include Enumerable
15
6
 
16
- # @param array [Array<Array>] An array of parameter key,value pairs. These
17
- # pairs may be 2 element arrays [key, value] or single element hashes
18
- # {key => value}. They may also be single element arrays to represent a
19
- # key with no value.
20
- #
21
- # @example
22
- # >> ParamsArray.new([[:foo, 123], [:foo, 456], [:bar, 789]])
23
- # This will be encoded as "foo=123&foo=456&bar=789"
24
- #
25
- # @example
26
- # >> ParamsArray.new({foo: 123, bar: 456})
27
- # This is valid, but there's no reason not to just use the Hash directly
28
- # instead of a ParamsArray.
29
- #
30
- #
7
+ # :include: _doc/lib/restman/params_array/new.rdoc
31
8
  def initialize(array)
32
9
  @array = process_input(array)
33
10
  end
@@ -43,30 +20,8 @@ module RestMan
43
20
  private
44
21
 
45
22
  def process_input(array)
46
- array.map {|v| process_pair(v) }
23
+ array.map {|v| ProcessPair.call(v) }
47
24
  end
48
25
 
49
- # A pair may be:
50
- # - A single element hash, e.g. {foo: 'bar'}
51
- # - A two element array, e.g. ['foo', 'bar']
52
- # - A one element array, e.g. ['foo']
53
- #
54
- def process_pair(pair)
55
- case pair
56
- when Hash
57
- if pair.length != 1
58
- raise ArgumentError.new("Bad # of fields for pair: #{pair.inspect}")
59
- end
60
- pair.to_a.fetch(0)
61
- when Array
62
- if pair.length > 2
63
- raise ArgumentError.new("Bad # of fields for pair: #{pair.inspect}")
64
- end
65
- [pair.fetch(0), pair[1]]
66
- else
67
- # recurse, converting any non-array to an array
68
- process_pair(pair.to_a)
69
- end
70
- end
71
26
  end
72
27
  end
@@ -0,0 +1,57 @@
1
+ require 'stringio'
2
+
3
+ module RestMan
4
+ module Payload
5
+ class Base
6
+ def initialize(params)
7
+ build_stream(params)
8
+ end
9
+
10
+ def build_stream(params)
11
+ @stream = StringIO.new(params)
12
+ @stream.seek(0)
13
+ end
14
+
15
+ def read(*args)
16
+ @stream.read(*args)
17
+ end
18
+
19
+ def to_s
20
+ result = read
21
+ @stream.seek(0)
22
+ result
23
+ end
24
+
25
+ def headers
26
+ {'Content-Length' => size.to_s}
27
+ end
28
+
29
+ def size
30
+ @stream.size
31
+ end
32
+
33
+ alias :length :size
34
+
35
+ def close
36
+ @stream.close unless @stream.closed?
37
+ end
38
+
39
+ def closed?
40
+ @stream.closed?
41
+ end
42
+
43
+ def to_s_inspect
44
+ to_s.inspect
45
+ end
46
+
47
+ def short_inspect
48
+ if size && size > 500
49
+ "#{size} byte(s) length"
50
+ else
51
+ to_s_inspect
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,88 @@
1
+ module RestMan
2
+ module Payload
3
+ class Multipart
4
+ class WriteContentDisposition < ActiveMethod::Base
5
+
6
+ argument :stream
7
+ argument :name
8
+ argument :value
9
+ argument :boundary
10
+
11
+ def call
12
+ if file?
13
+ write_header_for_file_field
14
+ else
15
+ write_header_for_regular_field
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def file?
22
+ value.respond_to?(:read) && value.respond_to?(:path)
23
+ end
24
+
25
+ def write_header_for_regular_field
26
+ write "--#{boundary}\r\n"
27
+ write "Content-Disposition: form-data;#{name_directive}\r\n"
28
+ write "\r\n"
29
+ write "#{value}\r\n"
30
+ end
31
+
32
+ def write_header_for_file_field
33
+ write "--#{boundary}\r\n"
34
+ write "Content-Disposition: form-data;#{name_directive(";")}#{filename_directive}\r\n"
35
+ write "Content-Type: #{content_type}\r\n"
36
+ write "\r\n"
37
+ while data = file.read(8124)
38
+ write data
39
+ end
40
+ write "\r\n"
41
+ ensure
42
+ file.close if file.respond_to?(:close)
43
+ end
44
+
45
+ def name_directive(separator = nil)
46
+ return if name.nil?
47
+ return if name == ''
48
+
49
+ %Q( name="#{name}"#{separator})
50
+ end
51
+
52
+ def filename_directive
53
+ if file.respond_to?(:original_filename)
54
+ %Q( filename="#{file.original_filename}")
55
+ else
56
+ %Q( filename="#{File.basename(file.path)}")
57
+ end
58
+ end
59
+
60
+ def content_type
61
+ if file.respond_to?(:content_type)
62
+ file.content_type
63
+ else
64
+ mime_for file.path
65
+ end
66
+ end
67
+
68
+ def mime_for(path)
69
+ mime = MIME::Types.type_for path
70
+ if mime.empty?
71
+ 'text/plain'
72
+ else
73
+ mime[0].content_type
74
+ end
75
+ end
76
+
77
+ def write(str)
78
+ stream.write str
79
+ end
80
+
81
+ def file
82
+ value
83
+ end
84
+
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,56 @@
1
+ require 'tempfile'
2
+ require 'securerandom'
3
+
4
+ module RestMan
5
+ module Payload
6
+ class Multipart < Base
7
+ autoload :WriteContentDisposition, "#{File.dirname(__FILE__)}/multipart/write_content_disposition"
8
+
9
+ include ActiveMethod
10
+
11
+ active_method :write_content_disposition
12
+
13
+ def headers
14
+ super.merge({'Content-Type' => %Q{multipart/form-data; boundary=#{boundary}}})
15
+ end
16
+
17
+ def build_stream(params)
18
+ @stream = Tempfile.new('rest-man.multipart.')
19
+ @stream.binmode
20
+ flatten(params).each do |name, value|
21
+ write_content_disposition(@stream, name, value, boundary)
22
+ end
23
+ @stream.write "--#{boundary}--\r\n"
24
+ @stream.seek(0)
25
+ end
26
+
27
+ def flatten(params)
28
+ case params
29
+ when Hash, ParamsArray
30
+ Utils.flatten_params(params)
31
+ else
32
+ params
33
+ end
34
+ end
35
+
36
+ def close
37
+ @stream.close!
38
+ end
39
+
40
+ def boundary
41
+ @boundary ||= generate_boundary
42
+ end
43
+
44
+ private
45
+
46
+ # Use the same algorithm used by WebKit: generate 16 random
47
+ # alphanumeric characters, replacing `+` `/` with `A` `B` (included in
48
+ # the list twice) to round out the set of 64.
49
+ def generate_boundary
50
+ s = SecureRandom.base64(12).tr('+/', 'AB')
51
+
52
+ '----RubyFormBoundary' + s
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,22 @@
1
+ module RestMan
2
+ module Payload
3
+ class Streamed < Base
4
+ def build_stream(params = nil)
5
+ @stream = params
6
+ end
7
+
8
+ def size
9
+ if @stream.respond_to?(:size)
10
+ @stream.size
11
+ elsif @stream.is_a?(IO)
12
+ @stream.stat.size
13
+ end
14
+ end
15
+
16
+ # TODO (breaks compatibility): ought to use mime_for() to autodetect the
17
+ # Content-Type for stream objects that have a filename.
18
+
19
+ alias :length :size
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,14 @@
1
+ module RestMan
2
+ module Payload
3
+ class UrlEncoded < Base
4
+ def build_stream(params = nil)
5
+ @stream = StringIO.new(Utils.encode_query_string(params))
6
+ @stream.seek(0)
7
+ end
8
+
9
+ def headers
10
+ super.merge({'Content-Type' => 'application/x-www-form-urlencoded'})
11
+ end
12
+ end
13
+ end
14
+ end