xpflow 0.1b
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/bin/xpflow +96 -0
- data/lib/colorado.rb +198 -0
- data/lib/json/add/core.rb +243 -0
- data/lib/json/add/rails.rb +8 -0
- data/lib/json/common.rb +423 -0
- data/lib/json/editor.rb +1369 -0
- data/lib/json/ext.rb +28 -0
- data/lib/json/pure/generator.rb +442 -0
- data/lib/json/pure/parser.rb +320 -0
- data/lib/json/pure.rb +15 -0
- data/lib/json/version.rb +8 -0
- data/lib/json.rb +62 -0
- data/lib/mime/types.rb +881 -0
- data/lib/mime-types.rb +3 -0
- data/lib/restclient/abstract_response.rb +106 -0
- data/lib/restclient/exceptions.rb +193 -0
- data/lib/restclient/net_http_ext.rb +55 -0
- data/lib/restclient/payload.rb +235 -0
- data/lib/restclient/raw_response.rb +34 -0
- data/lib/restclient/request.rb +316 -0
- data/lib/restclient/resource.rb +169 -0
- data/lib/restclient/response.rb +24 -0
- data/lib/restclient.rb +174 -0
- data/lib/xpflow/bash.rb +341 -0
- data/lib/xpflow/bundle.rb +113 -0
- data/lib/xpflow/cmdline.rb +249 -0
- data/lib/xpflow/collection.rb +122 -0
- data/lib/xpflow/concurrency.rb +79 -0
- data/lib/xpflow/data.rb +393 -0
- data/lib/xpflow/dsl.rb +816 -0
- data/lib/xpflow/engine.rb +574 -0
- data/lib/xpflow/ensemble.rb +135 -0
- data/lib/xpflow/events.rb +56 -0
- data/lib/xpflow/experiment.rb +65 -0
- data/lib/xpflow/exts/facter.rb +30 -0
- data/lib/xpflow/exts/g5k.rb +931 -0
- data/lib/xpflow/exts/g5k_use.rb +50 -0
- data/lib/xpflow/exts/gui.rb +140 -0
- data/lib/xpflow/exts/model.rb +155 -0
- data/lib/xpflow/graph.rb +1603 -0
- data/lib/xpflow/graph_xpflow.rb +251 -0
- data/lib/xpflow/import.rb +196 -0
- data/lib/xpflow/library.rb +349 -0
- data/lib/xpflow/logging.rb +153 -0
- data/lib/xpflow/manager.rb +147 -0
- data/lib/xpflow/nodes.rb +1250 -0
- data/lib/xpflow/runs.rb +773 -0
- data/lib/xpflow/runtime.rb +125 -0
- data/lib/xpflow/scope.rb +168 -0
- data/lib/xpflow/ssh.rb +186 -0
- data/lib/xpflow/stat.rb +50 -0
- data/lib/xpflow/stdlib.rb +381 -0
- data/lib/xpflow/structs.rb +369 -0
- data/lib/xpflow/taktuk.rb +193 -0
- data/lib/xpflow/templates/ssh-config.basic +14 -0
- data/lib/xpflow/templates/ssh-config.inria +18 -0
- data/lib/xpflow/templates/ssh-config.proxy +13 -0
- data/lib/xpflow/templates/taktuk +6590 -0
- data/lib/xpflow/templates/utils/batch +4 -0
- data/lib/xpflow/templates/utils/bootstrap +12 -0
- data/lib/xpflow/templates/utils/hostname +3 -0
- data/lib/xpflow/templates/utils/ping +3 -0
- data/lib/xpflow/templates/utils/rsync +12 -0
- data/lib/xpflow/templates/utils/scp +17 -0
- data/lib/xpflow/templates/utils/scp_many +8 -0
- data/lib/xpflow/templates/utils/ssh +3 -0
- data/lib/xpflow/templates/utils/ssh-interactive +4 -0
- data/lib/xpflow/templates/utils/taktuk +19 -0
- data/lib/xpflow/threads.rb +187 -0
- data/lib/xpflow/utils.rb +569 -0
- data/lib/xpflow/visual.rb +230 -0
- data/lib/xpflow/with_g5k.rb +7 -0
- data/lib/xpflow.rb +349 -0
- metadata +135 -0
data/lib/mime-types.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module RestClient
|
4
|
+
|
5
|
+
module AbstractResponse
|
6
|
+
|
7
|
+
attr_reader :net_http_res, :args
|
8
|
+
|
9
|
+
# HTTP status code
|
10
|
+
def code
|
11
|
+
@code ||= @net_http_res.code.to_i
|
12
|
+
end
|
13
|
+
|
14
|
+
# A hash of the headers, beautified with symbols and underscores.
|
15
|
+
# e.g. "Content-type" will become :content_type.
|
16
|
+
def headers
|
17
|
+
@headers ||= AbstractResponse.beautify_headers(@net_http_res.to_hash)
|
18
|
+
end
|
19
|
+
|
20
|
+
# The raw headers.
|
21
|
+
def raw_headers
|
22
|
+
@raw_headers ||= @net_http_res.to_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
# Hash of cookies extracted from response headers
|
26
|
+
def cookies
|
27
|
+
@cookies ||= (self.headers[:set_cookie] || {}).inject({}) do |out, cookie_content|
|
28
|
+
out.merge parse_cookie(cookie_content)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return the default behavior corresponding to the response code:
|
33
|
+
# the response itself for code in 200..206, redirection for 301, 302 and 307 in get and head cases, redirection for 303 and an exception in other cases
|
34
|
+
def return! request = nil, result = nil, & block
|
35
|
+
if (200..207).include? code
|
36
|
+
self
|
37
|
+
elsif [301, 302, 307].include? code
|
38
|
+
unless [:get, :head].include? args[:method]
|
39
|
+
raise Exceptions::EXCEPTIONS_MAP[code].new(self, code)
|
40
|
+
else
|
41
|
+
follow_redirection(request, result, & block)
|
42
|
+
end
|
43
|
+
elsif code == 303
|
44
|
+
args[:method] = :get
|
45
|
+
args.delete :payload
|
46
|
+
follow_redirection(request, result, & block)
|
47
|
+
elsif Exceptions::EXCEPTIONS_MAP[code]
|
48
|
+
raise Exceptions::EXCEPTIONS_MAP[code].new(self, code)
|
49
|
+
else
|
50
|
+
raise RequestFailed.new(self, code)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_i
|
55
|
+
code
|
56
|
+
end
|
57
|
+
|
58
|
+
def description
|
59
|
+
"#{code} #{STATUSES[code]} | #{(headers[:content_type] || '').gsub(/;.*$/, '')} #{size} bytes\n"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Follow a redirection
|
63
|
+
def follow_redirection request = nil, result = nil, & block
|
64
|
+
url = headers[:location]
|
65
|
+
if url !~ /^http/
|
66
|
+
url = URI.parse(args[:url]).merge(url).to_s
|
67
|
+
end
|
68
|
+
args[:url] = url
|
69
|
+
if request
|
70
|
+
if request.max_redirects == 0
|
71
|
+
raise MaxRedirectsReached
|
72
|
+
end
|
73
|
+
args[:password] = request.password
|
74
|
+
args[:user] = request.user
|
75
|
+
args[:headers] = request.headers
|
76
|
+
args[:max_redirects] = request.max_redirects - 1
|
77
|
+
# pass any cookie set in the result
|
78
|
+
if result && result['set-cookie']
|
79
|
+
args[:headers][:cookies] = (args[:headers][:cookies] || {}).merge(parse_cookie(result['set-cookie']))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
Request.execute args, &block
|
83
|
+
end
|
84
|
+
|
85
|
+
def AbstractResponse.beautify_headers(headers)
|
86
|
+
headers.inject({}) do |out, (key, value)|
|
87
|
+
out[key.gsub(/-/, '_').downcase.to_sym] = %w{ set-cookie }.include?(key.downcase) ? value : value.first
|
88
|
+
out
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
# Parse a cookie value and return its content in an Hash
|
95
|
+
def parse_cookie cookie_content
|
96
|
+
out = {}
|
97
|
+
CGI::Cookie::parse(cookie_content).each do |key, cookie|
|
98
|
+
unless ['expires', 'path'].include? key
|
99
|
+
out[CGI::escape(key)] = cookie.value[0] ? (CGI::escape(cookie.value[0]) || '') : ''
|
100
|
+
end
|
101
|
+
end
|
102
|
+
out
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module RestClient
|
2
|
+
|
3
|
+
STATUSES = {100 => 'Continue',
|
4
|
+
101 => 'Switching Protocols',
|
5
|
+
102 => 'Processing', #WebDAV
|
6
|
+
|
7
|
+
200 => 'OK',
|
8
|
+
201 => 'Created',
|
9
|
+
202 => 'Accepted',
|
10
|
+
203 => 'Non-Authoritative Information', # http/1.1
|
11
|
+
204 => 'No Content',
|
12
|
+
205 => 'Reset Content',
|
13
|
+
206 => 'Partial Content',
|
14
|
+
207 => 'Multi-Status', #WebDAV
|
15
|
+
|
16
|
+
300 => 'Multiple Choices',
|
17
|
+
301 => 'Moved Permanently',
|
18
|
+
302 => 'Found',
|
19
|
+
303 => 'See Other', # http/1.1
|
20
|
+
304 => 'Not Modified',
|
21
|
+
305 => 'Use Proxy', # http/1.1
|
22
|
+
306 => 'Switch Proxy', # no longer used
|
23
|
+
307 => 'Temporary Redirect', # http/1.1
|
24
|
+
|
25
|
+
400 => 'Bad Request',
|
26
|
+
401 => 'Unauthorized',
|
27
|
+
402 => 'Payment Required',
|
28
|
+
403 => 'Forbidden',
|
29
|
+
404 => 'Resource Not Found',
|
30
|
+
405 => 'Method Not Allowed',
|
31
|
+
406 => 'Not Acceptable',
|
32
|
+
407 => 'Proxy Authentication Required',
|
33
|
+
408 => 'Request Timeout',
|
34
|
+
409 => 'Conflict',
|
35
|
+
410 => 'Gone',
|
36
|
+
411 => 'Length Required',
|
37
|
+
412 => 'Precondition Failed',
|
38
|
+
413 => 'Request Entity Too Large',
|
39
|
+
414 => 'Request-URI Too Long',
|
40
|
+
415 => 'Unsupported Media Type',
|
41
|
+
416 => 'Requested Range Not Satisfiable',
|
42
|
+
417 => 'Expectation Failed',
|
43
|
+
418 => 'I\'m A Teapot',
|
44
|
+
421 => 'Too Many Connections From This IP',
|
45
|
+
422 => 'Unprocessable Entity', #WebDAV
|
46
|
+
423 => 'Locked', #WebDAV
|
47
|
+
424 => 'Failed Dependency', #WebDAV
|
48
|
+
425 => 'Unordered Collection', #WebDAV
|
49
|
+
426 => 'Upgrade Required',
|
50
|
+
449 => 'Retry With', #Microsoft
|
51
|
+
450 => 'Blocked By Windows Parental Controls', #Microsoft
|
52
|
+
|
53
|
+
500 => 'Internal Server Error',
|
54
|
+
501 => 'Not Implemented',
|
55
|
+
502 => 'Bad Gateway',
|
56
|
+
503 => 'Service Unavailable',
|
57
|
+
504 => 'Gateway Timeout',
|
58
|
+
505 => 'HTTP Version Not Supported',
|
59
|
+
506 => 'Variant Also Negotiates',
|
60
|
+
507 => 'Insufficient Storage', #WebDAV
|
61
|
+
509 => 'Bandwidth Limit Exceeded', #Apache
|
62
|
+
510 => 'Not Extended'}
|
63
|
+
|
64
|
+
# Compatibility : make the Response act like a Net::HTTPResponse when needed
|
65
|
+
module ResponseForException
|
66
|
+
def method_missing symbol, *args
|
67
|
+
if net_http_res.respond_to? symbol
|
68
|
+
warn "[warning] The response contained in an RestClient::Exception is now a RestClient::Response instead of a Net::HTTPResponse, please update your code"
|
69
|
+
net_http_res.send symbol, *args
|
70
|
+
else
|
71
|
+
super
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# This is the base RestClient exception class. Rescue it if you want to
|
77
|
+
# catch any exception that your request might raise
|
78
|
+
# You can get the status code by e.http_code, or see anything about the
|
79
|
+
# response via e.response.
|
80
|
+
# For example, the entire result body (which is
|
81
|
+
# probably an HTML error page) is e.response.
|
82
|
+
class Exception < RuntimeError
|
83
|
+
attr_accessor :response
|
84
|
+
attr_writer :message
|
85
|
+
|
86
|
+
def initialize response = nil, initial_response_code = nil
|
87
|
+
@response = response
|
88
|
+
@initial_response_code = initial_response_code
|
89
|
+
|
90
|
+
# compatibility: this make the exception behave like a Net::HTTPResponse
|
91
|
+
response.extend ResponseForException if response
|
92
|
+
end
|
93
|
+
|
94
|
+
def http_code
|
95
|
+
# return integer for compatibility
|
96
|
+
if @response
|
97
|
+
@response.code.to_i
|
98
|
+
else
|
99
|
+
@initial_response_code
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def http_body
|
104
|
+
@response.body if @response
|
105
|
+
end
|
106
|
+
|
107
|
+
def inspect
|
108
|
+
"#{message}: #{http_body}"
|
109
|
+
end
|
110
|
+
|
111
|
+
def to_s
|
112
|
+
inspect
|
113
|
+
end
|
114
|
+
|
115
|
+
def message
|
116
|
+
@message || self.class.name
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
# Compatibility
|
122
|
+
class ExceptionWithResponse < Exception
|
123
|
+
end
|
124
|
+
|
125
|
+
# The request failed with an error code not managed by the code
|
126
|
+
class RequestFailed < ExceptionWithResponse
|
127
|
+
|
128
|
+
def message
|
129
|
+
"HTTP status code #{http_code}"
|
130
|
+
end
|
131
|
+
|
132
|
+
def to_s
|
133
|
+
message
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# We will a create an exception for each status code, see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
138
|
+
module Exceptions
|
139
|
+
# Map http status codes to the corresponding exception class
|
140
|
+
EXCEPTIONS_MAP = {}
|
141
|
+
end
|
142
|
+
|
143
|
+
STATUSES.each_pair do |code, message|
|
144
|
+
|
145
|
+
# Compatibility
|
146
|
+
superclass = ([304, 401, 404].include? code) ? ExceptionWithResponse : RequestFailed
|
147
|
+
klass = Class.new(superclass) do
|
148
|
+
send(:define_method, :message) {"#{http_code ? "#{http_code} " : ''}#{message}"}
|
149
|
+
end
|
150
|
+
klass_constant = const_set message.delete(' \-\''), klass
|
151
|
+
Exceptions::EXCEPTIONS_MAP[code] = klass_constant
|
152
|
+
end
|
153
|
+
|
154
|
+
# A redirect was encountered; caught by execute to retry with the new url.
|
155
|
+
class Redirect < Exception
|
156
|
+
|
157
|
+
message = 'Redirect'
|
158
|
+
|
159
|
+
attr_accessor :url
|
160
|
+
|
161
|
+
def initialize(url)
|
162
|
+
@url = url
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class MaxRedirectsReached < Exception
|
167
|
+
message = 'Maximum number of redirect reached'
|
168
|
+
end
|
169
|
+
|
170
|
+
# The server broke the connection prior to the request completing. Usually
|
171
|
+
# this means it crashed, or sometimes that your network connection was
|
172
|
+
# severed before it could complete.
|
173
|
+
class ServerBrokeConnection < Exception
|
174
|
+
def initialize(message = 'Server broke connection')
|
175
|
+
super nil, nil
|
176
|
+
self.message = message
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
class SSLCertificateNotVerified < Exception
|
181
|
+
def initialize(message)
|
182
|
+
super nil, nil
|
183
|
+
self.message = message
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# backwards compatibility
|
189
|
+
class RestClient::Request
|
190
|
+
Redirect = RestClient::Redirect
|
191
|
+
Unauthorized = RestClient::Unauthorized
|
192
|
+
RequestFailed = RestClient::RequestFailed
|
193
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Net
|
2
|
+
class HTTP
|
3
|
+
|
4
|
+
# Adding the patch method if it doesn't exist (rest-client issue: https://github.com/archiloque/rest-client/issues/79)
|
5
|
+
if !defined?(Net::HTTP::Patch)
|
6
|
+
# Code taken from this commit: https://github.com/ruby/ruby/commit/ab70e53ac3b5102d4ecbe8f38d4f76afad29d37d#lib/net/http.rb
|
7
|
+
class Protocol
|
8
|
+
# Sends a PATCH request to the +path+ and gets a response,
|
9
|
+
# as an HTTPResponse object.
|
10
|
+
def patch(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+
|
11
|
+
send_entity(path, data, initheader, dest, Patch, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Executes a request which uses a representation
|
15
|
+
# and returns its body.
|
16
|
+
def send_entity(path, data, initheader, dest, type, &block)
|
17
|
+
res = nil
|
18
|
+
request(type.new(path, initheader), data) {|r|
|
19
|
+
r.read_body dest, &block
|
20
|
+
res = r
|
21
|
+
}
|
22
|
+
unless @newimpl
|
23
|
+
res.value
|
24
|
+
return res, res.body
|
25
|
+
end
|
26
|
+
res
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Patch < HTTPRequest
|
31
|
+
METHOD = 'PATCH'
|
32
|
+
REQUEST_HAS_BODY = true
|
33
|
+
RESPONSE_HAS_BODY = true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Replace the request method in Net::HTTP to sniff the body type
|
39
|
+
# and set the stream if appropriate
|
40
|
+
#
|
41
|
+
# Taken from:
|
42
|
+
# http://www.missiondata.com/blog/ruby/29/streaming-data-to-s3-with-ruby/
|
43
|
+
|
44
|
+
alias __request__ request
|
45
|
+
|
46
|
+
def request(req, body=nil, &block)
|
47
|
+
if body != nil && body.respond_to?(:read)
|
48
|
+
req.body_stream = body
|
49
|
+
return __request__(req, nil, &block)
|
50
|
+
else
|
51
|
+
return __request__(req, body, &block)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'stringio'
|
3
|
+
require 'mime/types'
|
4
|
+
|
5
|
+
module RestClient
|
6
|
+
module Payload
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def generate(params)
|
10
|
+
if params.is_a?(String)
|
11
|
+
Base.new(params)
|
12
|
+
elsif params.respond_to?(:read)
|
13
|
+
Streamed.new(params)
|
14
|
+
elsif params
|
15
|
+
if params.delete(:multipart) == true || has_file?(params)
|
16
|
+
Multipart.new(params)
|
17
|
+
else
|
18
|
+
UrlEncoded.new(params)
|
19
|
+
end
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def has_file?(params)
|
26
|
+
params.any? do |_, v|
|
27
|
+
case v
|
28
|
+
when Hash
|
29
|
+
has_file?(v)
|
30
|
+
when Array
|
31
|
+
has_file_array?(v)
|
32
|
+
else
|
33
|
+
v.respond_to?(:path) && v.respond_to?(:read)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_file_array?(params)
|
39
|
+
params.any? do |v|
|
40
|
+
case v
|
41
|
+
when Hash
|
42
|
+
has_file?(v)
|
43
|
+
when Array
|
44
|
+
has_file_array?(v)
|
45
|
+
else
|
46
|
+
v.respond_to?(:path) && v.respond_to?(:read)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Base
|
52
|
+
def initialize(params)
|
53
|
+
build_stream(params)
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_stream(params)
|
57
|
+
@stream = StringIO.new(params)
|
58
|
+
@stream.seek(0)
|
59
|
+
end
|
60
|
+
|
61
|
+
def read(bytes=nil)
|
62
|
+
@stream.read(bytes)
|
63
|
+
end
|
64
|
+
|
65
|
+
alias :to_s :read
|
66
|
+
|
67
|
+
# Flatten parameters by converting hashes of hashes to flat hashes
|
68
|
+
# {keys1 => {keys2 => value}} will be transformed into [keys1[key2], value]
|
69
|
+
def flatten_params(params, parent_key = nil)
|
70
|
+
result = []
|
71
|
+
params.each do |key, value|
|
72
|
+
calculated_key = parent_key ? "#{parent_key}[#{handle_key(key)}]" : handle_key(key)
|
73
|
+
if value.is_a? Hash
|
74
|
+
result += flatten_params(value, calculated_key)
|
75
|
+
elsif value.is_a? Array
|
76
|
+
result += flatten_params_array(value, calculated_key)
|
77
|
+
else
|
78
|
+
result << [calculated_key, value]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
result
|
82
|
+
end
|
83
|
+
|
84
|
+
def flatten_params_array value, calculated_key
|
85
|
+
result = []
|
86
|
+
value.each do |elem|
|
87
|
+
if elem.is_a? Hash
|
88
|
+
result += flatten_params(elem, calculated_key)
|
89
|
+
elsif elem.is_a? Array
|
90
|
+
result += flatten_params_array(elem, calculated_key)
|
91
|
+
else
|
92
|
+
result << ["#{calculated_key}[]", elem]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
result
|
96
|
+
end
|
97
|
+
|
98
|
+
def headers
|
99
|
+
{'Content-Length' => size.to_s}
|
100
|
+
end
|
101
|
+
|
102
|
+
def size
|
103
|
+
@stream.size
|
104
|
+
end
|
105
|
+
|
106
|
+
alias :length :size
|
107
|
+
|
108
|
+
def close
|
109
|
+
@stream.close unless @stream.closed?
|
110
|
+
end
|
111
|
+
|
112
|
+
def inspect
|
113
|
+
result = to_s.inspect
|
114
|
+
@stream.seek(0)
|
115
|
+
result
|
116
|
+
end
|
117
|
+
|
118
|
+
def short_inspect
|
119
|
+
(size > 500 ? "#{size} byte(s) length" : inspect)
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
class Streamed < Base
|
125
|
+
def build_stream(params = nil)
|
126
|
+
@stream = params
|
127
|
+
end
|
128
|
+
|
129
|
+
def size
|
130
|
+
if @stream.respond_to?(:size)
|
131
|
+
@stream.size
|
132
|
+
elsif @stream.is_a?(IO)
|
133
|
+
@stream.stat.size
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
alias :length :size
|
138
|
+
end
|
139
|
+
|
140
|
+
class UrlEncoded < Base
|
141
|
+
def build_stream(params = nil)
|
142
|
+
@stream = StringIO.new(flatten_params(params).collect do |entry|
|
143
|
+
"#{entry[0]}=#{handle_key(entry[1])}"
|
144
|
+
end.join("&"))
|
145
|
+
@stream.seek(0)
|
146
|
+
end
|
147
|
+
|
148
|
+
# for UrlEncoded escape the keys
|
149
|
+
def handle_key key
|
150
|
+
URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
151
|
+
end
|
152
|
+
|
153
|
+
def headers
|
154
|
+
super.merge({'Content-Type' => 'application/x-www-form-urlencoded'})
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class Multipart < Base
|
159
|
+
EOL = "\r\n"
|
160
|
+
|
161
|
+
def build_stream(params)
|
162
|
+
b = "--#{boundary}"
|
163
|
+
|
164
|
+
@stream = Tempfile.new("RESTClient.Stream.#{rand(1000)}")
|
165
|
+
@stream.binmode
|
166
|
+
@stream.write(b + EOL)
|
167
|
+
|
168
|
+
if params.is_a? Hash
|
169
|
+
x = flatten_params(params)
|
170
|
+
else
|
171
|
+
x = params
|
172
|
+
end
|
173
|
+
|
174
|
+
last_index = x.length - 1
|
175
|
+
x.each_with_index do |a, index|
|
176
|
+
k, v = * a
|
177
|
+
if v.respond_to?(:read) && v.respond_to?(:path)
|
178
|
+
create_file_field(@stream, k, v)
|
179
|
+
else
|
180
|
+
create_regular_field(@stream, k, v)
|
181
|
+
end
|
182
|
+
@stream.write(EOL + b)
|
183
|
+
@stream.write(EOL) unless last_index == index
|
184
|
+
end
|
185
|
+
@stream.write('--')
|
186
|
+
@stream.write(EOL)
|
187
|
+
@stream.seek(0)
|
188
|
+
end
|
189
|
+
|
190
|
+
def create_regular_field(s, k, v)
|
191
|
+
s.write("Content-Disposition: form-data; name=\"#{k}\"")
|
192
|
+
s.write(EOL)
|
193
|
+
s.write(EOL)
|
194
|
+
s.write(v)
|
195
|
+
end
|
196
|
+
|
197
|
+
def create_file_field(s, k, v)
|
198
|
+
begin
|
199
|
+
s.write("Content-Disposition: form-data;")
|
200
|
+
s.write(" name=\"#{k}\";") unless (k.nil? || k=='')
|
201
|
+
s.write(" filename=\"#{v.respond_to?(:original_filename) ? v.original_filename : File.basename(v.path)}\"#{EOL}")
|
202
|
+
s.write("Content-Type: #{v.respond_to?(:content_type) ? v.content_type : mime_for(v.path)}#{EOL}")
|
203
|
+
s.write(EOL)
|
204
|
+
while data = v.read(8124)
|
205
|
+
s.write(data)
|
206
|
+
end
|
207
|
+
ensure
|
208
|
+
v.close if v.respond_to?(:close)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def mime_for(path)
|
213
|
+
mime = MIME::Types.type_for path
|
214
|
+
mime.empty? ? 'text/plain' : mime[0].content_type
|
215
|
+
end
|
216
|
+
|
217
|
+
def boundary
|
218
|
+
@boundary ||= rand(1_000_000).to_s
|
219
|
+
end
|
220
|
+
|
221
|
+
# for Multipart do not escape the keys
|
222
|
+
def handle_key key
|
223
|
+
key
|
224
|
+
end
|
225
|
+
|
226
|
+
def headers
|
227
|
+
super.merge({'Content-Type' => %Q{multipart/form-data; boundary=#{boundary}}})
|
228
|
+
end
|
229
|
+
|
230
|
+
def close
|
231
|
+
@stream.close!
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module RestClient
|
2
|
+
# The response from RestClient 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 occassion you want to fetch the
|
5
|
+
# headers you can:
|
6
|
+
#
|
7
|
+
# RestClient.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
|
17
|
+
|
18
|
+
def initialize tempfile, net_http_res, args
|
19
|
+
@net_http_res = net_http_res
|
20
|
+
@args = args
|
21
|
+
@file = tempfile
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
@file.open
|
26
|
+
@file.read
|
27
|
+
end
|
28
|
+
|
29
|
+
def size
|
30
|
+
File.size file
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|