rest-man 1.0.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 (78) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/multi-matrix-test.yml +35 -0
  3. data/.github/workflows/single-matrix-test.yml +27 -0
  4. data/.gitignore +13 -0
  5. data/.mailmap +10 -0
  6. data/.rspec +2 -0
  7. data/.rubocop +2 -0
  8. data/.rubocop-disables.yml +386 -0
  9. data/.rubocop.yml +8 -0
  10. data/AUTHORS +106 -0
  11. data/CHANGELOG.md +7 -0
  12. data/Gemfile +11 -0
  13. data/LICENSE +21 -0
  14. data/README.md +843 -0
  15. data/Rakefile +140 -0
  16. data/exe/restman +92 -0
  17. data/lib/rest-man.rb +2 -0
  18. data/lib/rest_man.rb +2 -0
  19. data/lib/restman/abstract_response.rb +252 -0
  20. data/lib/restman/exceptions.rb +238 -0
  21. data/lib/restman/params_array.rb +72 -0
  22. data/lib/restman/payload.rb +234 -0
  23. data/lib/restman/platform.rb +49 -0
  24. data/lib/restman/raw_response.rb +49 -0
  25. data/lib/restman/request.rb +859 -0
  26. data/lib/restman/resource.rb +178 -0
  27. data/lib/restman/response.rb +90 -0
  28. data/lib/restman/utils.rb +274 -0
  29. data/lib/restman/version.rb +8 -0
  30. data/lib/restman/windows/root_certs.rb +105 -0
  31. data/lib/restman/windows.rb +8 -0
  32. data/lib/restman.rb +183 -0
  33. data/matrixeval.yml +73 -0
  34. data/rest-man.gemspec +41 -0
  35. data/spec/ISS.jpg +0 -0
  36. data/spec/cassettes/request_httpbin_with_basic_auth.yml +83 -0
  37. data/spec/cassettes/request_httpbin_with_cookies.yml +49 -0
  38. data/spec/cassettes/request_httpbin_with_cookies_2.yml +94 -0
  39. data/spec/cassettes/request_httpbin_with_cookies_3.yml +49 -0
  40. data/spec/cassettes/request_httpbin_with_encoding_deflate.yml +45 -0
  41. data/spec/cassettes/request_httpbin_with_encoding_deflate_and_accept_headers.yml +44 -0
  42. data/spec/cassettes/request_httpbin_with_encoding_gzip.yml +45 -0
  43. data/spec/cassettes/request_httpbin_with_encoding_gzip_and_accept_headers.yml +44 -0
  44. data/spec/cassettes/request_httpbin_with_user_agent.yml +44 -0
  45. data/spec/cassettes/request_mozilla_org.yml +151 -0
  46. data/spec/cassettes/request_mozilla_org_callback_returns_true.yml +178 -0
  47. data/spec/cassettes/request_mozilla_org_with_system_cert.yml +152 -0
  48. data/spec/cassettes/request_mozilla_org_with_system_cert_and_callback.yml +151 -0
  49. data/spec/helpers.rb +54 -0
  50. data/spec/integration/_lib.rb +1 -0
  51. data/spec/integration/capath_digicert/README +8 -0
  52. data/spec/integration/capath_digicert/ce5e74ef.0 +1 -0
  53. data/spec/integration/capath_digicert/digicert.crt +20 -0
  54. data/spec/integration/capath_digicert/update +1 -0
  55. data/spec/integration/capath_verisign/415660c1.0 +14 -0
  56. data/spec/integration/capath_verisign/7651b327.0 +14 -0
  57. data/spec/integration/capath_verisign/README +8 -0
  58. data/spec/integration/capath_verisign/verisign.crt +14 -0
  59. data/spec/integration/certs/digicert.crt +20 -0
  60. data/spec/integration/certs/verisign.crt +14 -0
  61. data/spec/integration/httpbin_spec.rb +137 -0
  62. data/spec/integration/integration_spec.rb +118 -0
  63. data/spec/integration/request_spec.rb +134 -0
  64. data/spec/spec_helper.rb +40 -0
  65. data/spec/unit/_lib.rb +1 -0
  66. data/spec/unit/abstract_response_spec.rb +145 -0
  67. data/spec/unit/exceptions_spec.rb +108 -0
  68. data/spec/unit/params_array_spec.rb +36 -0
  69. data/spec/unit/payload_spec.rb +295 -0
  70. data/spec/unit/raw_response_spec.rb +22 -0
  71. data/spec/unit/request2_spec.rb +54 -0
  72. data/spec/unit/request_spec.rb +1205 -0
  73. data/spec/unit/resource_spec.rb +134 -0
  74. data/spec/unit/response_spec.rb +252 -0
  75. data/spec/unit/restclient_spec.rb +80 -0
  76. data/spec/unit/utils_spec.rb +147 -0
  77. data/spec/unit/windows/root_certs_spec.rb +22 -0
  78. metadata +336 -0
@@ -0,0 +1,238 @@
1
+ module RestMan
2
+
3
+ # Hash of HTTP status code => message.
4
+ #
5
+ # 1xx: Informational - Request received, continuing process
6
+ # 2xx: Success - The action was successfully received, understood, and
7
+ # accepted
8
+ # 3xx: Redirection - Further action must be taken in order to complete the
9
+ # request
10
+ # 4xx: Client Error - The request contains bad syntax or cannot be fulfilled
11
+ # 5xx: Server Error - The server failed to fulfill an apparently valid
12
+ # request
13
+ #
14
+ # @see
15
+ # http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
16
+ #
17
+ STATUSES = {100 => 'Continue',
18
+ 101 => 'Switching Protocols',
19
+ 102 => 'Processing', #WebDAV
20
+
21
+ 200 => 'OK',
22
+ 201 => 'Created',
23
+ 202 => 'Accepted',
24
+ 203 => 'Non-Authoritative Information', # http/1.1
25
+ 204 => 'No Content',
26
+ 205 => 'Reset Content',
27
+ 206 => 'Partial Content',
28
+ 207 => 'Multi-Status', #WebDAV
29
+ 208 => 'Already Reported', # RFC5842
30
+ 226 => 'IM Used', # RFC3229
31
+
32
+ 300 => 'Multiple Choices',
33
+ 301 => 'Moved Permanently',
34
+ 302 => 'Found',
35
+ 303 => 'See Other', # http/1.1
36
+ 304 => 'Not Modified',
37
+ 305 => 'Use Proxy', # http/1.1
38
+ 306 => 'Switch Proxy', # no longer used
39
+ 307 => 'Temporary Redirect', # http/1.1
40
+ 308 => 'Permanent Redirect', # RFC7538
41
+
42
+ 400 => 'Bad Request',
43
+ 401 => 'Unauthorized',
44
+ 402 => 'Payment Required',
45
+ 403 => 'Forbidden',
46
+ 404 => 'Not Found',
47
+ 405 => 'Method Not Allowed',
48
+ 406 => 'Not Acceptable',
49
+ 407 => 'Proxy Authentication Required',
50
+ 408 => 'Request Timeout',
51
+ 409 => 'Conflict',
52
+ 410 => 'Gone',
53
+ 411 => 'Length Required',
54
+ 412 => 'Precondition Failed',
55
+ 413 => 'Payload Too Large', # RFC7231 (renamed, see below)
56
+ 414 => 'URI Too Long', # RFC7231 (renamed, see below)
57
+ 415 => 'Unsupported Media Type',
58
+ 416 => 'Range Not Satisfiable', # RFC7233 (renamed, see below)
59
+ 417 => 'Expectation Failed',
60
+ 418 => 'I\'m A Teapot', #RFC2324
61
+ 421 => 'Too Many Connections From This IP',
62
+ 422 => 'Unprocessable Entity', #WebDAV
63
+ 423 => 'Locked', #WebDAV
64
+ 424 => 'Failed Dependency', #WebDAV
65
+ 425 => 'Unordered Collection', #WebDAV
66
+ 426 => 'Upgrade Required',
67
+ 428 => 'Precondition Required', #RFC6585
68
+ 429 => 'Too Many Requests', #RFC6585
69
+ 431 => 'Request Header Fields Too Large', #RFC6585
70
+ 449 => 'Retry With', #Microsoft
71
+ 450 => 'Blocked By Windows Parental Controls', #Microsoft
72
+
73
+ 500 => 'Internal Server Error',
74
+ 501 => 'Not Implemented',
75
+ 502 => 'Bad Gateway',
76
+ 503 => 'Service Unavailable',
77
+ 504 => 'Gateway Timeout',
78
+ 505 => 'HTTP Version Not Supported',
79
+ 506 => 'Variant Also Negotiates',
80
+ 507 => 'Insufficient Storage', #WebDAV
81
+ 508 => 'Loop Detected', # RFC5842
82
+ 509 => 'Bandwidth Limit Exceeded', #Apache
83
+ 510 => 'Not Extended',
84
+ 511 => 'Network Authentication Required', # RFC6585
85
+ }
86
+
87
+ STATUSES_COMPATIBILITY = {
88
+ # The RFCs all specify "Not Found", but "Resource Not Found" was used in
89
+ # earlier RestMan releases.
90
+ 404 => ['ResourceNotFound'],
91
+
92
+ # HTTP 413 was renamed to "Payload Too Large" in RFC7231.
93
+ 413 => ['RequestEntityTooLarge'],
94
+
95
+ # HTTP 414 was renamed to "URI Too Long" in RFC7231.
96
+ 414 => ['RequestURITooLong'],
97
+
98
+ # HTTP 416 was renamed to "Range Not Satisfiable" in RFC7233.
99
+ 416 => ['RequestedRangeNotSatisfiable'],
100
+ }
101
+
102
+
103
+ # This is the base RestMan exception class. Rescue it if you want to
104
+ # catch any exception that your request might raise
105
+ # You can get the status code by e.http_code, or see anything about the
106
+ # response via e.response.
107
+ # For example, the entire result body (which is
108
+ # probably an HTML error page) is e.response.
109
+ class Exception < RuntimeError
110
+ attr_accessor :response
111
+ attr_accessor :original_exception
112
+ attr_writer :message
113
+
114
+ def initialize response = nil, initial_response_code = nil
115
+ @response = response
116
+ @message = nil
117
+ @initial_response_code = initial_response_code
118
+ end
119
+
120
+ def http_code
121
+ # return integer for compatibility
122
+ if @response
123
+ @response.code.to_i
124
+ else
125
+ @initial_response_code
126
+ end
127
+ end
128
+
129
+ def http_headers
130
+ @response.headers if @response
131
+ end
132
+
133
+ def http_body
134
+ @response.body if @response
135
+ end
136
+
137
+ def to_s
138
+ message
139
+ end
140
+
141
+ def message
142
+ @message || default_message
143
+ end
144
+
145
+ def default_message
146
+ self.class.name
147
+ end
148
+ end
149
+
150
+ # Compatibility
151
+ class ExceptionWithResponse < RestMan::Exception
152
+ end
153
+
154
+ # The request failed with an error code not managed by the code
155
+ class RequestFailed < ExceptionWithResponse
156
+
157
+ def default_message
158
+ "HTTP status code #{http_code}"
159
+ end
160
+
161
+ def to_s
162
+ message
163
+ end
164
+ end
165
+
166
+ # RestMan exception classes. TODO: move all exceptions into this module.
167
+ #
168
+ # We will a create an exception for each status code, see
169
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
170
+ #
171
+ module Exceptions
172
+ # Map http status codes to the corresponding exception class
173
+ EXCEPTIONS_MAP = {}
174
+ end
175
+
176
+ # Create HTTP status exception classes
177
+ STATUSES.each_pair do |code, message|
178
+ klass = Class.new(RequestFailed) do
179
+ send(:define_method, :default_message) {"#{http_code ? "#{http_code} " : ''}#{message}"}
180
+ end
181
+ klass_constant = const_set(message.delete(' \-\''), klass)
182
+ Exceptions::EXCEPTIONS_MAP[code] = klass_constant
183
+ end
184
+
185
+ # Create HTTP status exception classes used for backwards compatibility
186
+ STATUSES_COMPATIBILITY.each_pair do |code, compat_list|
187
+ klass = Exceptions::EXCEPTIONS_MAP.fetch(code)
188
+ compat_list.each do |old_name|
189
+ const_set(old_name, klass)
190
+ end
191
+ end
192
+
193
+ module Exceptions
194
+ # We have to split the Exceptions module like we do here because the
195
+ # EXCEPTIONS_MAP is under Exceptions, but we depend on
196
+ # RestMan::RequestTimeout below.
197
+
198
+ # Base class for request timeouts.
199
+ #
200
+ # NB: Previous releases of rest-man would raise RequestTimeout both for
201
+ # HTTP 408 responses and for actual connection timeouts.
202
+ class Timeout < RestMan::RequestTimeout
203
+ def initialize(message=nil, original_exception=nil)
204
+ super(nil, nil)
205
+ self.message = message if message
206
+ self.original_exception = original_exception if original_exception
207
+ end
208
+ end
209
+
210
+ # Timeout when connecting to a server. Typically wraps Net::OpenTimeout (in
211
+ # ruby 2.0 or greater).
212
+ class OpenTimeout < Timeout
213
+ def default_message
214
+ 'Timed out connecting to server'
215
+ end
216
+ end
217
+
218
+ # Timeout when reading from a server. Typically wraps Net::ReadTimeout (in
219
+ # ruby 2.0 or greater).
220
+ class ReadTimeout < Timeout
221
+ def default_message
222
+ 'Timed out reading data from server'
223
+ end
224
+ end
225
+ end
226
+
227
+
228
+ # The server broke the connection prior to the request completing. Usually
229
+ # this means it crashed, or sometimes that your network connection was
230
+ # severed before it could complete.
231
+ class ServerBrokeConnection < RestMan::Exception
232
+ def initialize(message = 'Server broke connection')
233
+ super nil, nil
234
+ self.message = message
235
+ end
236
+ end
237
+
238
+ end
@@ -0,0 +1,72 @@
1
+ module RestMan
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
+ #
13
+ class ParamsArray
14
+ include Enumerable
15
+
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
+ #
31
+ def initialize(array)
32
+ @array = process_input(array)
33
+ end
34
+
35
+ def each(*args, &blk)
36
+ @array.each(*args, &blk)
37
+ end
38
+
39
+ def empty?
40
+ @array.empty?
41
+ end
42
+
43
+ private
44
+
45
+ def process_input(array)
46
+ array.map {|v| process_pair(v) }
47
+ end
48
+
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
+ end
72
+ end
@@ -0,0 +1,234 @@
1
+ require 'tempfile'
2
+ require 'securerandom'
3
+ require 'stringio'
4
+
5
+ begin
6
+ # Use mime/types/columnar if available, for reduced memory usage
7
+ require 'mime/types/columnar'
8
+ rescue LoadError
9
+ require 'mime/types'
10
+ end
11
+
12
+ module RestMan
13
+ module Payload
14
+ extend self
15
+
16
+ def generate(params)
17
+ if params.is_a?(RestMan::Payload::Base)
18
+ # pass through Payload objects unchanged
19
+ params
20
+ elsif params.is_a?(String)
21
+ Base.new(params)
22
+ elsif params.is_a?(Hash)
23
+ if params.delete(:multipart) == true || has_file?(params)
24
+ Multipart.new(params)
25
+ else
26
+ UrlEncoded.new(params)
27
+ end
28
+ elsif params.is_a?(ParamsArray)
29
+ if _has_file?(params)
30
+ Multipart.new(params)
31
+ else
32
+ UrlEncoded.new(params)
33
+ end
34
+ elsif params.respond_to?(:read)
35
+ Streamed.new(params)
36
+ else
37
+ nil
38
+ end
39
+ end
40
+
41
+ def has_file?(params)
42
+ unless params.is_a?(Hash)
43
+ raise ArgumentError.new("Must pass Hash, not #{params.inspect}")
44
+ end
45
+ _has_file?(params)
46
+ end
47
+
48
+ def _has_file?(obj)
49
+ case obj
50
+ when Hash, ParamsArray
51
+ obj.any? {|_, v| _has_file?(v) }
52
+ when Array
53
+ obj.any? {|v| _has_file?(v) }
54
+ else
55
+ obj.respond_to?(:path) && obj.respond_to?(:read)
56
+ end
57
+ end
58
+
59
+ class Base
60
+ def initialize(params)
61
+ build_stream(params)
62
+ end
63
+
64
+ def build_stream(params)
65
+ @stream = StringIO.new(params)
66
+ @stream.seek(0)
67
+ end
68
+
69
+ def read(*args)
70
+ @stream.read(*args)
71
+ end
72
+
73
+ def to_s
74
+ result = read
75
+ @stream.seek(0)
76
+ result
77
+ end
78
+
79
+ def headers
80
+ {'Content-Length' => size.to_s}
81
+ end
82
+
83
+ def size
84
+ @stream.size
85
+ end
86
+
87
+ alias :length :size
88
+
89
+ def close
90
+ @stream.close unless @stream.closed?
91
+ end
92
+
93
+ def closed?
94
+ @stream.closed?
95
+ end
96
+
97
+ def to_s_inspect
98
+ to_s.inspect
99
+ end
100
+
101
+ def short_inspect
102
+ if size && size > 500
103
+ "#{size} byte(s) length"
104
+ else
105
+ to_s_inspect
106
+ end
107
+ end
108
+
109
+ end
110
+
111
+ class Streamed < Base
112
+ def build_stream(params = nil)
113
+ @stream = params
114
+ end
115
+
116
+ def size
117
+ if @stream.respond_to?(:size)
118
+ @stream.size
119
+ elsif @stream.is_a?(IO)
120
+ @stream.stat.size
121
+ end
122
+ end
123
+
124
+ # TODO (breaks compatibility): ought to use mime_for() to autodetect the
125
+ # Content-Type for stream objects that have a filename.
126
+
127
+ alias :length :size
128
+ end
129
+
130
+ class UrlEncoded < Base
131
+ def build_stream(params = nil)
132
+ @stream = StringIO.new(Utils.encode_query_string(params))
133
+ @stream.seek(0)
134
+ end
135
+
136
+ def headers
137
+ super.merge({'Content-Type' => 'application/x-www-form-urlencoded'})
138
+ end
139
+ end
140
+
141
+ class Multipart < Base
142
+ EOL = "\r\n"
143
+
144
+ def build_stream(params)
145
+ b = '--' + boundary
146
+
147
+ @stream = Tempfile.new('rest-man.multipart.')
148
+ @stream.binmode
149
+ @stream.write(b + EOL)
150
+
151
+ case params
152
+ when Hash, ParamsArray
153
+ x = Utils.flatten_params(params)
154
+ else
155
+ x = params
156
+ end
157
+
158
+ last_index = x.length - 1
159
+ x.each_with_index do |a, index|
160
+ k, v = * a
161
+ if v.respond_to?(:read) && v.respond_to?(:path)
162
+ create_file_field(@stream, k, v)
163
+ else
164
+ create_regular_field(@stream, k, v)
165
+ end
166
+ @stream.write(EOL + b)
167
+ @stream.write(EOL) unless last_index == index
168
+ end
169
+ @stream.write('--')
170
+ @stream.write(EOL)
171
+ @stream.seek(0)
172
+ end
173
+
174
+ def create_regular_field(s, k, v)
175
+ s.write("Content-Disposition: form-data; name=\"#{k}\"")
176
+ s.write(EOL)
177
+ s.write(EOL)
178
+ s.write(v)
179
+ end
180
+
181
+ def create_file_field(s, k, v)
182
+ begin
183
+ s.write("Content-Disposition: form-data;")
184
+ s.write(" name=\"#{k}\";") unless (k.nil? || k=='')
185
+ s.write(" filename=\"#{v.respond_to?(:original_filename) ? v.original_filename : File.basename(v.path)}\"#{EOL}")
186
+ s.write("Content-Type: #{v.respond_to?(:content_type) ? v.content_type : mime_for(v.path)}#{EOL}")
187
+ s.write(EOL)
188
+ while (data = v.read(8124))
189
+ s.write(data)
190
+ end
191
+ ensure
192
+ v.close if v.respond_to?(:close)
193
+ end
194
+ end
195
+
196
+ def mime_for(path)
197
+ mime = MIME::Types.type_for path
198
+ mime.empty? ? 'text/plain' : mime[0].content_type
199
+ end
200
+
201
+ def boundary
202
+ return @boundary if defined?(@boundary) && @boundary
203
+
204
+ # Use the same algorithm used by WebKit: generate 16 random
205
+ # alphanumeric characters, replacing `+` `/` with `A` `B` (included in
206
+ # the list twice) to round out the set of 64.
207
+ s = SecureRandom.base64(12)
208
+ s.tr!('+/', 'AB')
209
+
210
+ @boundary = '----RubyFormBoundary' + s
211
+ end
212
+
213
+ # for Multipart do not escape the keys
214
+ #
215
+ # Ostensibly multipart keys MAY be percent encoded per RFC 7578, but in
216
+ # practice no major browser that I'm aware of uses percent encoding.
217
+ #
218
+ # Further discussion of multipart encoding:
219
+ # https://github.com/rest-man/rest-man/pull/403#issuecomment-156976930
220
+ #
221
+ def handle_key key
222
+ key
223
+ end
224
+
225
+ def headers
226
+ super.merge({'Content-Type' => %Q{multipart/form-data; boundary=#{boundary}}})
227
+ end
228
+
229
+ def close
230
+ @stream.close!
231
+ end
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,49 @@
1
+ require 'rbconfig'
2
+
3
+ module RestMan
4
+ module Platform
5
+ # Return true if we are running on a darwin-based Ruby platform. This will
6
+ # be false for jruby even on OS X.
7
+ #
8
+ # @return [Boolean]
9
+ def self.mac_mri?
10
+ RUBY_PLATFORM.include?('darwin')
11
+ end
12
+
13
+ # Return true if we are running on Windows.
14
+ #
15
+ # @return [Boolean]
16
+ #
17
+ def self.windows?
18
+ # Ruby only sets File::ALT_SEPARATOR on Windows, and the Ruby standard
19
+ # library uses that to test what platform it's on.
20
+ !!File::ALT_SEPARATOR
21
+ end
22
+
23
+ # Return true if we are running on jruby.
24
+ #
25
+ # @return [Boolean]
26
+ #
27
+ def self.jruby?
28
+ # defined on mri >= 1.9
29
+ RUBY_ENGINE == 'jruby'
30
+ end
31
+
32
+ def self.architecture
33
+ "#{RbConfig::CONFIG['host_os']} #{RbConfig::CONFIG['host_cpu']}"
34
+ end
35
+
36
+ def self.ruby_agent_version
37
+ case RUBY_ENGINE
38
+ when 'jruby'
39
+ "jruby/#{JRUBY_VERSION} (#{RUBY_VERSION}p#{RUBY_PATCHLEVEL})"
40
+ else
41
+ "#{RUBY_ENGINE}/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}"
42
+ end
43
+ end
44
+
45
+ def self.default_user_agent
46
+ "rest-man/#{VERSION} (#{architecture}) #{ruby_agent_version}"
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,49 @@
1
+ module RestMan
2
+ # The response from RestMan on a raw request looks like a string, but is
3
+ # actually one of these. 99% of the time you're making a rest call all you
4
+ # care about is the body, but on the occasion you want to fetch the
5
+ # headers you can:
6
+ #
7
+ # RestMan.get('http://example.com').headers[:content_type]
8
+ #
9
+ # In addition, if you do not use the response as a string, you can access
10
+ # a Tempfile object at res.file, which contains the path to the raw
11
+ # downloaded request body.
12
+ class RawResponse
13
+
14
+ include AbstractResponse
15
+
16
+ attr_reader :file, :request, :start_time, :end_time
17
+
18
+ def inspect
19
+ "<RestMan::RawResponse @code=#{code.inspect}, @file=#{file.inspect}, @request=#{request.inspect}>"
20
+ end
21
+
22
+ # @param [Tempfile] tempfile The temporary file containing the body
23
+ # @param [Net::HTTPResponse] net_http_res
24
+ # @param [RestMan::Request] request
25
+ # @param [Time] start_time
26
+ def initialize(tempfile, net_http_res, request, start_time=nil)
27
+ @file = tempfile
28
+
29
+ # reopen the tempfile so we can read it
30
+ @file.open
31
+
32
+ response_set_vars(net_http_res, request, start_time)
33
+ end
34
+
35
+ def to_s
36
+ body
37
+ end
38
+
39
+ def body
40
+ @file.rewind
41
+ @file.read
42
+ end
43
+
44
+ def size
45
+ file.size
46
+ end
47
+
48
+ end
49
+ end