rest-client-next 1.1.0 → 1.3.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.
- data/README.rdoc +122 -7
- data/Rakefile +4 -4
- data/VERSION +1 -1
- data/history.md +28 -0
- data/lib/restclient.rb +83 -39
- data/lib/restclient/exceptions.rb +114 -82
- data/lib/restclient/mixin/response.rb +56 -40
- data/lib/restclient/net_http_ext.rb +11 -11
- data/lib/restclient/payload.rb +175 -168
- data/lib/restclient/raw_response.rb +22 -22
- data/lib/restclient/request.rb +272 -248
- data/lib/restclient/resource.rb +133 -132
- data/lib/restclient/response.rb +13 -13
- data/spec/exceptions_spec.rb +48 -50
- data/spec/integration_spec.rb +38 -0
- data/spec/mixin/response_spec.rb +38 -38
- data/spec/payload_spec.rb +98 -98
- data/spec/raw_response_spec.rb +11 -11
- data/spec/request_spec.rb +542 -493
- data/spec/resource_spec.rb +95 -71
- data/spec/response_spec.rb +68 -17
- data/spec/restclient_spec.rb +59 -49
- metadata +9 -5
@@ -1,48 +1,64 @@
|
|
1
1
|
module RestClient
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
module Mixin
|
3
|
+
module Response
|
4
|
+
attr_reader :net_http_res
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
6
|
+
# HTTP status code
|
7
|
+
def code
|
8
|
+
@code ||= @net_http_res.code.to_i
|
9
|
+
end
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
# A hash of the headers, beautified with symbols and underscores.
|
12
|
+
# e.g. "Content-type" will become :content_type.
|
13
|
+
def headers
|
14
|
+
@headers ||= self.class.beautify_headers(@net_http_res.to_hash)
|
15
|
+
end
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
# The raw headers.
|
18
|
+
def raw_headers
|
19
|
+
@raw_headers ||= @net_http_res.to_hash
|
20
|
+
end
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
22
|
+
# Hash of cookies extracted from response headers
|
23
|
+
def cookies
|
24
|
+
@cookies ||= (self.headers[:set_cookie] || []).inject({}) do |out, cookie_content|
|
25
|
+
# correctly parse comma-separated cookies containing HTTP dates (which also contain a comma)
|
26
|
+
cookie_content.split(/,\s*/).inject([""]) { |array, blob|
|
27
|
+
blob =~ /expires=.+?$/ ? array.push(blob) : array.last.concat(blob)
|
28
|
+
array
|
29
|
+
}.each do |cookie|
|
30
|
+
next if cookie.empty?
|
31
|
+
key, *val = cookie.split(";").first.split("=")
|
32
|
+
out[key] = val.join("=")
|
33
|
+
end
|
34
|
+
out
|
35
|
+
end
|
36
|
+
end
|
33
37
|
|
34
|
-
|
35
|
-
|
36
|
-
|
38
|
+
# Return the default behavior corresponding to the response code:
|
39
|
+
# the response itself for code in 200..206 and an exception in other cases
|
40
|
+
def return!
|
41
|
+
if (200..206).include? code
|
42
|
+
self
|
43
|
+
elsif Exceptions::EXCEPTIONS_MAP[code]
|
44
|
+
raise Exceptions::EXCEPTIONS_MAP[code], self
|
45
|
+
else
|
46
|
+
raise RequestFailed, self
|
47
|
+
end
|
48
|
+
end
|
37
49
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
50
|
+
def self.included(receiver)
|
51
|
+
receiver.extend(RestClient::Mixin::Response::ClassMethods)
|
52
|
+
end
|
53
|
+
|
54
|
+
module ClassMethods
|
55
|
+
def beautify_headers(headers)
|
56
|
+
headers.inject({}) do |out, (key, value)|
|
57
|
+
out[key.gsub(/-/, '_').downcase.to_sym] = %w{set-cookie}.include?(key.downcase) ? value : value.first
|
58
|
+
out
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
48
64
|
end
|
@@ -6,16 +6,16 @@
|
|
6
6
|
# http://www.missiondata.com/blog/ruby/29/streaming-data-to-s3-with-ruby/
|
7
7
|
|
8
8
|
module Net
|
9
|
-
|
10
|
-
|
9
|
+
class HTTP
|
10
|
+
alias __request__ request
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
12
|
+
def request(req, body=nil, &block)
|
13
|
+
if body != nil && body.respond_to?(:read)
|
14
|
+
req.body_stream = body
|
15
|
+
return __request__(req, nil, &block)
|
16
|
+
else
|
17
|
+
return __request__(req, body, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
21
|
end
|
data/lib/restclient/payload.rb
CHANGED
@@ -1,171 +1,178 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require 'tempfile'
|
2
|
+
require 'stringio'
|
3
|
+
require 'mime/types'
|
4
4
|
|
5
5
|
module RestClient
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
6
|
+
module Payload
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def generate(params)
|
10
|
+
if params.is_a?(String)
|
11
|
+
Base.new(params)
|
12
|
+
elsif params
|
13
|
+
if params.delete(:multipart) == true || has_file?(params)
|
14
|
+
Multipart.new(params)
|
15
|
+
else
|
16
|
+
UrlEncoded.new(params)
|
17
|
+
end
|
18
|
+
else
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_file?(params)
|
24
|
+
params.any? do |_, v|
|
25
|
+
case v
|
26
|
+
when Hash
|
27
|
+
has_file?(v)
|
28
|
+
else
|
29
|
+
v.respond_to?(:path) && v.respond_to?(:read)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Base
|
35
|
+
def initialize(params)
|
36
|
+
build_stream(params)
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_stream(params)
|
40
|
+
@stream = StringIO.new(params)
|
41
|
+
@stream.seek(0)
|
42
|
+
end
|
43
|
+
|
44
|
+
def read(bytes=nil)
|
45
|
+
@stream.read(bytes)
|
46
|
+
end
|
47
|
+
|
48
|
+
alias :to_s :read
|
49
|
+
|
50
|
+
def escape(v)
|
51
|
+
URI.escape(v.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
52
|
+
end
|
53
|
+
|
54
|
+
# Flatten parameters by converting hashes of hashes to flat hashes
|
55
|
+
# {keys1 => {keys2 => value}} will be transformed into {keys1[key2] => value}
|
56
|
+
def flatten_params(params, parent_key = nil)
|
57
|
+
result = {}
|
58
|
+
params.keys.map do |key|
|
59
|
+
calculated_key = parent_key ? "#{parent_key}[#{escape key}]" : escape(key)
|
60
|
+
value = params[key]
|
61
|
+
if value.is_a? Hash
|
62
|
+
result.merge!(flatten_params(value, calculated_key))
|
63
|
+
else
|
64
|
+
result[calculated_key] = value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
result
|
68
|
+
end
|
69
|
+
|
70
|
+
def headers
|
71
|
+
{ 'Content-Length' => size.to_s }
|
72
|
+
end
|
73
|
+
|
74
|
+
def size
|
75
|
+
@stream.size
|
76
|
+
end
|
77
|
+
|
78
|
+
alias :length :size
|
79
|
+
|
80
|
+
def close
|
81
|
+
@stream.close
|
82
|
+
end
|
83
|
+
|
84
|
+
def inspect
|
85
|
+
result = to_s.inspect
|
86
|
+
@stream.seek(0)
|
87
|
+
result
|
88
|
+
end
|
89
|
+
|
90
|
+
def short_inspect
|
91
|
+
(size > 100 ? "#{size} byte length" : inspect)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
class UrlEncoded < Base
|
97
|
+
def build_stream(params = nil)
|
98
|
+
@stream = StringIO.new(flatten_params(params).map do |k, v|
|
99
|
+
"#{k}=#{escape(v)}"
|
100
|
+
end.join("&"))
|
101
|
+
@stream.seek(0)
|
102
|
+
end
|
103
|
+
|
104
|
+
def headers
|
105
|
+
super.merge({ 'Content-Type' => 'application/x-www-form-urlencoded' })
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class Multipart < Base
|
110
|
+
EOL = "\r\n"
|
111
|
+
|
112
|
+
def build_stream(params)
|
113
|
+
b = "--#{boundary}"
|
114
|
+
|
115
|
+
@stream = Tempfile.new("RESTClient.Stream.#{rand(1000)}")
|
116
|
+
@stream.write(b + EOL)
|
117
|
+
|
118
|
+
if params.is_a? Hash
|
119
|
+
x = flatten_params(params).to_a
|
120
|
+
else
|
121
|
+
x = params.to_a
|
122
|
+
end
|
123
|
+
|
124
|
+
last_index = x.length - 1
|
125
|
+
x.each_with_index do |a, index|
|
126
|
+
k, v = *a
|
127
|
+
if v.respond_to?(:read) && v.respond_to?(:path)
|
128
|
+
create_file_field(@stream, k, v)
|
129
|
+
else
|
130
|
+
create_regular_field(@stream, k, v)
|
131
|
+
end
|
132
|
+
@stream.write(EOL + b)
|
133
|
+
@stream.write(EOL) unless last_index == index
|
134
|
+
end
|
135
|
+
@stream.write('--')
|
136
|
+
@stream.write(EOL)
|
137
|
+
@stream.seek(0)
|
138
|
+
end
|
139
|
+
|
140
|
+
def create_regular_field(s, k, v)
|
141
|
+
s.write("Content-Disposition: form-data; name=\"#{k}\"")
|
142
|
+
s.write(EOL)
|
143
|
+
s.write(EOL)
|
144
|
+
s.write(v)
|
145
|
+
end
|
146
|
+
|
147
|
+
def create_file_field(s, k, v)
|
148
|
+
begin
|
149
|
+
s.write("Content-Disposition: form-data; name=\"#{k}\"; filename=\"#{v.respond_to?(:original_filename) ? v.original_filename : File.basename(v.path)}\"#{EOL}")
|
150
|
+
s.write("Content-Type: #{v.respond_to?(:content_type) ? v.content_type : mime_for(v.path)}#{EOL}")
|
151
|
+
s.write(EOL)
|
152
|
+
while data = v.read(8124)
|
153
|
+
s.write(data)
|
154
|
+
end
|
155
|
+
ensure
|
156
|
+
v.close
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def mime_for(path)
|
161
|
+
mime = MIME::Types.type_for path
|
162
|
+
mime.empty? ? 'text/plain' : mime[0].content_type
|
163
|
+
end
|
164
|
+
|
165
|
+
def boundary
|
166
|
+
@boundary ||= rand(1_000_000).to_s
|
167
|
+
end
|
168
|
+
|
169
|
+
def headers
|
170
|
+
super.merge({'Content-Type' => %Q{multipart/form-data; boundary="#{boundary}"}})
|
171
|
+
end
|
172
|
+
|
173
|
+
def close
|
174
|
+
@stream.close
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
171
178
|
end
|