rest-client 1.1.0 → 1.2.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.
Potentially problematic release.
This version of rest-client might be problematic. Click here for more details.
- data/README.rdoc +3 -0
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/lib/restclient.rb +32 -31
- data/lib/restclient/exceptions.rb +70 -69
- data/lib/restclient/mixin/response.rb +40 -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 +283 -248
- data/lib/restclient/resource.rb +132 -132
- data/lib/restclient/response.rb +13 -13
- data/spec/exceptions_spec.rb +45 -45
- data/spec/mixin/response_spec.rb +38 -38
- data/spec/payload_spec.rb +92 -92
- data/spec/raw_response_spec.rb +11 -11
- data/spec/request_spec.rb +517 -493
- data/spec/resource_spec.rb +57 -57
- data/spec/response_spec.rb +15 -15
- data/spec/restclient_spec.rb +49 -49
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -8,10 +8,13 @@ of specifying actions: get, put, post, delete.
|
|
8
8
|
require 'rest_client'
|
9
9
|
|
10
10
|
RestClient.get 'http://example.com/resource'
|
11
|
+
|
11
12
|
RestClient.get 'https://user:password@example.com/private/resource'
|
12
13
|
|
13
14
|
RestClient.post 'http://example.com/resource', :param1 => 'one', :nested => { :param2 => 'two' }
|
14
15
|
|
16
|
+
RestClient.post "http://example.com/resource", { 'x' => 1 }.to_json, :content_type => :json, :accept => :json
|
17
|
+
|
15
18
|
RestClient.delete 'http://example.com/resource'
|
16
19
|
|
17
20
|
== Multipart
|
data/Rakefile
CHANGED
@@ -7,8 +7,8 @@ Jeweler::Tasks.new do |s|
|
|
7
7
|
s.description = "A simple REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete."
|
8
8
|
s.summary = "Simple REST client for Ruby, inspired by microframework syntax for specifying actions."
|
9
9
|
s.author = "Adam Wiggins"
|
10
|
-
s.email = "
|
11
|
-
s.homepage = "http://rest-client
|
10
|
+
s.email = "rest.client@librelist.com"
|
11
|
+
s.homepage = "http://github.com/archiloque/rest-client"
|
12
12
|
s.rubyforge_project = "rest-client"
|
13
13
|
s.has_rdoc = true
|
14
14
|
s.files = FileList["[A-Z]*", "{bin,lib,spec}/**/*"]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.2.0
|
data/lib/restclient.rb
CHANGED
@@ -3,10 +3,10 @@ require 'zlib'
|
|
3
3
|
require 'stringio'
|
4
4
|
|
5
5
|
begin
|
6
|
-
|
6
|
+
require 'net/https'
|
7
7
|
rescue LoadError => e
|
8
|
-
|
9
|
-
|
8
|
+
raise e unless RUBY_PLATFORM =~ /linux/
|
9
|
+
raise LoadError, "no such file to load -- net/https. Try running apt-get install libopenssl-ruby"
|
10
10
|
end
|
11
11
|
|
12
12
|
require File.dirname(__FILE__) + '/restclient/request'
|
@@ -63,41 +63,42 @@ require File.dirname(__FILE__) + '/restclient/net_http_ext'
|
|
63
63
|
# => "PUT http://rest-test.heroku.com/resource with a 7 byte payload, content type application/x-www-form-urlencoded {\"foo\"=>\"baz\"}"
|
64
64
|
#
|
65
65
|
module RestClient
|
66
|
-
def self.get(url, headers={})
|
67
|
-
Request.execute(:method => :get, :url => url, :headers => headers)
|
68
|
-
end
|
69
66
|
|
70
|
-
|
71
|
-
|
72
|
-
|
67
|
+
def self.get(url, headers={})
|
68
|
+
Request.execute(:method => :get, :url => url, :headers => headers)
|
69
|
+
end
|
73
70
|
|
74
|
-
|
75
|
-
|
76
|
-
|
71
|
+
def self.post(url, payload, headers={})
|
72
|
+
Request.execute(:method => :post, :url => url, :payload => payload, :headers => headers)
|
73
|
+
end
|
77
74
|
|
78
|
-
|
79
|
-
|
80
|
-
|
75
|
+
def self.put(url, payload, headers={})
|
76
|
+
Request.execute(:method => :put, :url => url, :payload => payload, :headers => headers)
|
77
|
+
end
|
81
78
|
|
82
|
-
|
83
|
-
|
84
|
-
|
79
|
+
def self.delete(url, headers={})
|
80
|
+
Request.execute(:method => :delete, :url => url, :headers => headers)
|
81
|
+
end
|
85
82
|
|
86
|
-
|
87
|
-
|
88
|
-
|
83
|
+
def self.head(url, headers={})
|
84
|
+
Request.execute(:method => :head, :url => url, :headers => headers)
|
85
|
+
end
|
89
86
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
87
|
+
class << self
|
88
|
+
attr_accessor :proxy
|
89
|
+
end
|
90
|
+
|
91
|
+
# Print log of RestClient calls. Value can be stdout, stderr, or a filename.
|
92
|
+
# You can also configure logging by the environment variable RESTCLIENT_LOG.
|
93
|
+
def self.log=(log)
|
94
|
+
@@log = log
|
95
|
+
end
|
95
96
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
97
|
+
def self.log # :nodoc:
|
98
|
+
return ENV['RESTCLIENT_LOG'] if ENV['RESTCLIENT_LOG']
|
99
|
+
return @@log if defined? @@log
|
100
|
+
nil
|
101
|
+
end
|
101
102
|
|
102
103
|
def self.version
|
103
104
|
version_path = File.dirname(__FILE__) + "/../VERSION"
|
@@ -1,88 +1,89 @@
|
|
1
1
|
module RestClient
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
# This is the base RestClient exception class. Rescue it if you want to
|
3
|
+
# catch any exception that your request might raise
|
4
|
+
class Exception < RuntimeError
|
5
|
+
def message(default=nil)
|
6
|
+
self.class::ErrorMessage
|
7
|
+
end
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
# Base RestClient exception when there's a response available
|
11
|
+
class ExceptionWithResponse < Exception
|
12
|
+
attr_accessor :response
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
def initialize(response=nil)
|
15
|
+
@response = response
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
def http_code
|
19
|
+
@response.code.to_i if @response
|
20
|
+
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
def http_body
|
23
|
+
RestClient::Request.decode(@response['content-encoding'], @response.body) if @response
|
24
|
+
end
|
25
|
+
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
# A redirect was encountered; caught by execute to retry with the new url.
|
28
|
+
class Redirect < Exception
|
29
|
+
ErrorMessage = "Redirect"
|
30
30
|
|
31
|
-
|
32
|
-
def initialize(url)
|
33
|
-
@url = url
|
34
|
-
end
|
35
|
-
end
|
31
|
+
attr_accessor :url
|
36
32
|
|
37
|
-
|
38
|
-
|
39
|
-
|
33
|
+
def initialize(url)
|
34
|
+
@url = url
|
35
|
+
end
|
36
|
+
end
|
40
37
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
38
|
+
class NotModified < ExceptionWithResponse
|
39
|
+
ErrorMessage = 'NotModified'
|
40
|
+
end
|
45
41
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
42
|
+
# Authorization is required to access the resource specified.
|
43
|
+
class Unauthorized < ExceptionWithResponse
|
44
|
+
ErrorMessage = 'Unauthorized'
|
45
|
+
end
|
50
46
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
ErrorMessage = 'Server broke connection'
|
56
|
-
end
|
47
|
+
# No resource was found at the given URL.
|
48
|
+
class ResourceNotFound < ExceptionWithResponse
|
49
|
+
ErrorMessage = 'Resource not found'
|
50
|
+
end
|
57
51
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
52
|
+
# The server broke the connection prior to the request completing. Usually
|
53
|
+
# this means it crashed, or sometimes that your network connection was
|
54
|
+
# severed before it could complete.
|
55
|
+
class ServerBrokeConnection < Exception
|
56
|
+
ErrorMessage = 'Server broke connection'
|
57
|
+
end
|
62
58
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
# format returned by Rails: <errors><error>some message</error></errors>
|
68
|
-
#
|
69
|
-
# You can get the status code by e.http_code, or see anything about the
|
70
|
-
# response via e.response. For example, the entire result body (which is
|
71
|
-
# probably an HTML error page) is e.response.body.
|
72
|
-
class RequestFailed < ExceptionWithResponse
|
73
|
-
def message
|
74
|
-
"HTTP status code #{http_code}"
|
75
|
-
end
|
59
|
+
# The server took too long to respond.
|
60
|
+
class RequestTimeout < Exception
|
61
|
+
ErrorMessage = 'Request timed out'
|
62
|
+
end
|
76
63
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
64
|
+
# The request failed, meaning the remote HTTP server returned a code other
|
65
|
+
# than success, unauthorized, or redirect.
|
66
|
+
#
|
67
|
+
# The exception message attempts to extract the error from the XML, using
|
68
|
+
# format returned by Rails: <errors><error>some message</error></errors>
|
69
|
+
#
|
70
|
+
# You can get the status code by e.http_code, or see anything about the
|
71
|
+
# response via e.response. For example, the entire result body (which is
|
72
|
+
# probably an HTML error page) is e.response.body.
|
73
|
+
class RequestFailed < ExceptionWithResponse
|
74
|
+
def message
|
75
|
+
"HTTP status code #{http_code}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_s
|
79
|
+
message
|
80
|
+
end
|
81
|
+
end
|
81
82
|
end
|
82
83
|
|
83
84
|
# backwards compatibility
|
84
85
|
class RestClient::Request
|
85
|
-
|
86
|
-
|
87
|
-
|
86
|
+
Redirect = RestClient::Redirect
|
87
|
+
Unauthorized = RestClient::Unauthorized
|
88
|
+
RequestFailed = RestClient::RequestFailed
|
88
89
|
end
|
@@ -1,48 +1,48 @@
|
|
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
|
-
|
6
|
+
# HTTP status code, always 200 since RestClient throws exceptions for
|
7
|
+
# other codes.
|
8
|
+
def code
|
9
|
+
@code ||= @net_http_res.code.to_i
|
10
|
+
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
# A hash of the headers, beautified with symbols and underscores.
|
13
|
+
# e.g. "Content-type" will become :content_type.
|
14
|
+
def headers
|
15
|
+
@headers ||= self.class.beautify_headers(@net_http_res.to_hash)
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
# The raw headers.
|
19
|
+
def raw_headers
|
20
|
+
@raw_headers ||= @net_http_res.to_hash
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
23
|
+
# Hash of cookies extracted from response headers
|
24
|
+
def cookies
|
25
|
+
@cookies ||= (self.headers[:set_cookie] || "").split('; ').inject({}) do |out, raw_c|
|
26
|
+
key, val = raw_c.split('=')
|
27
|
+
unless %w(expires domain path secure).member?(key)
|
28
|
+
out[key] = val
|
29
|
+
end
|
30
|
+
out
|
31
|
+
end
|
32
|
+
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
def self.included(receiver)
|
35
|
+
receiver.extend(RestClient::Mixin::Response::ClassMethods)
|
36
|
+
end
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
38
|
+
module ClassMethods
|
39
|
+
def beautify_headers(headers)
|
40
|
+
headers.inject({}) do |out, (key, value)|
|
41
|
+
out[key.gsub(/-/, '_').downcase.to_sym] = value.first
|
42
|
+
out
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
48
|
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: multipart/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: multipart/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
|