t2-server 0.9.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.rdoc +79 -0
- data/LICENCE.rdoc +1 -1
- data/README.rdoc +94 -26
- data/Rakefile +6 -5
- data/bin/t2-delete-runs +25 -23
- data/bin/t2-get-output +11 -13
- data/bin/t2-run-workflow +91 -29
- data/bin/t2-server-info +29 -12
- data/extras/t2-server-stress +184 -0
- data/lib/t2-server-cli.rb +48 -23
- data/lib/t2-server.rb +2 -1
- data/lib/t2-server/admin.rb +7 -4
- data/lib/t2-server/exceptions.rb +23 -4
- data/lib/t2-server/interaction.rb +241 -0
- data/lib/t2-server/net/connection.rb +90 -60
- data/lib/t2-server/net/credentials.rb +25 -9
- data/lib/t2-server/net/parameters.rb +21 -6
- data/lib/t2-server/port.rb +229 -140
- data/lib/t2-server/run-cache.rb +99 -0
- data/lib/t2-server/run.rb +349 -332
- data/lib/t2-server/server.rb +115 -164
- data/lib/t2-server/util.rb +11 -9
- data/lib/t2-server/xml/libxml.rb +3 -2
- data/lib/t2-server/xml/nokogiri.rb +4 -3
- data/lib/t2-server/xml/rexml.rb +3 -2
- data/lib/t2-server/xml/xml.rb +47 -36
- data/lib/{t2server.rb → t2-server/xml/xpath_cache.rb} +29 -7
- data/t2-server.gemspec +16 -5
- data/test/tc_misc.rb +61 -0
- data/test/tc_perms.rb +17 -1
- data/test/tc_run.rb +164 -34
- data/test/tc_secure.rb +11 -2
- data/test/tc_server.rb +23 -2
- data/test/ts_t2server.rb +10 -8
- data/test/workflows/missing_outputs.t2flow +440 -0
- data/version.yml +3 -3
- metadata +42 -4
data/lib/t2-server-cli.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2010-
|
1
|
+
# Copyright (c) 2010-2013 The University of Manchester, UK.
|
2
2
|
#
|
3
3
|
# All rights reserved.
|
4
4
|
#
|
@@ -50,27 +50,8 @@ module T2Server
|
|
50
50
|
end
|
51
51
|
|
52
52
|
# SSL options
|
53
|
-
opt
|
54
|
-
|
55
|
-
"optional password is not provided it will be asked for on the " +
|
56
|
-
"command line. Must be in PEM format.") do |val|
|
57
|
-
cert, cpass = val.chomp.split(":", 2)
|
58
|
-
conn_params[:client_certificate] = cert
|
59
|
-
conn_params[:client_password] = cpass if cpass
|
60
|
-
end
|
61
|
-
opt.on("--cacert=CERT_FILE", "Use the specified certificate file to " +
|
62
|
-
"verify the peer. Must be in PEM format.") do |val|
|
63
|
-
conn_params[:ca_file] = val.chomp
|
64
|
-
end
|
65
|
-
opt.on("--capath=CERTS_PATH", "Use the specified certificate " +
|
66
|
-
"directory to verify the peer. Certificates must be in PEM " +
|
67
|
-
"format") do |val|
|
68
|
-
conn_params[:ca_path] = val.chomp
|
69
|
-
end
|
70
|
-
opt.on("-k", "--insecure", "Allow insecure connections: no peer " +
|
71
|
-
"verification.") do
|
72
|
-
conn_params[:verify_peer] = false
|
73
|
-
end
|
53
|
+
ssl_auth_opts(opt, conn_params)
|
54
|
+
ssl_transport_opts(opt, conn_params)
|
74
55
|
|
75
56
|
# common options
|
76
57
|
opt.on_tail("-u", "--username=USERNAME", "The username to use for " +
|
@@ -100,7 +81,7 @@ module T2Server
|
|
100
81
|
|
101
82
|
# separate the creds if they are supplied in the uri
|
102
83
|
def parse_address(address, creds)
|
103
|
-
if address == nil
|
84
|
+
if address == nil || address == ""
|
104
85
|
puts @opts
|
105
86
|
exit 1
|
106
87
|
end
|
@@ -112,5 +93,49 @@ module T2Server
|
|
112
93
|
def opts
|
113
94
|
@opts
|
114
95
|
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
# The SSL authentication and peer verification options.
|
100
|
+
def ssl_auth_opts(opt, conn_params)
|
101
|
+
opt.on("-E CERT_FILE:PASSWORD", "--cert=CERT_FILE:PASSWORD", "Use " +
|
102
|
+
"the specified certificate file for client authentication. If the " +
|
103
|
+
"optional password is not provided it will be asked for on the " +
|
104
|
+
"command line. Must be in PEM format.") do |val|
|
105
|
+
cert, cpass = val.chomp.split(":", 2)
|
106
|
+
conn_params[:client_certificate] = cert
|
107
|
+
conn_params[:client_password] = cpass if cpass
|
108
|
+
end
|
109
|
+
opt.on("--cacert=CERT_FILE", "Use the specified certificate file to " +
|
110
|
+
"verify the peer. Must be in PEM format.") do |val|
|
111
|
+
conn_params[:ca_file] = val.chomp
|
112
|
+
end
|
113
|
+
opt.on("--capath=CERTS_PATH", "Use the specified certificate " +
|
114
|
+
"directory to verify the peer. Certificates must be in PEM " +
|
115
|
+
"format") do |val|
|
116
|
+
conn_params[:ca_path] = val.chomp
|
117
|
+
end
|
118
|
+
opt.on("-k", "--insecure", "Allow insecure connections: no peer " +
|
119
|
+
"verification.") do
|
120
|
+
conn_params[:verify_peer] = false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# The SSL transport options.
|
125
|
+
def ssl_transport_opts(opt, conn_params)
|
126
|
+
opt.on("-1", "--tlsv1", "Use TLS version 1 when negotiating with " +
|
127
|
+
"the remote Taverna Server server.") do
|
128
|
+
conn_params[:ssl_version] = :TLSv1
|
129
|
+
end
|
130
|
+
opt.on("-2", "--sslv2", "Use SSL version 2 when negotiating with " +
|
131
|
+
"the remote Taverna Server server.") do
|
132
|
+
conn_params[:ssl_version] = :SSLv23
|
133
|
+
end
|
134
|
+
opt.on("-3", "--sslv3", "Use SSL version 3 when negotiating with " +
|
135
|
+
"the remote Taverna Server server.") do
|
136
|
+
conn_params[:ssl_version] = :SSLv3
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
115
140
|
end
|
116
141
|
end
|
data/lib/t2-server.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2010-
|
1
|
+
# Copyright (c) 2010-2013 The University of Manchester, UK.
|
2
2
|
#
|
3
3
|
# All rights reserved.
|
4
4
|
#
|
@@ -38,6 +38,7 @@ require 't2-server/net/credentials'
|
|
38
38
|
require 't2-server/net/connection'
|
39
39
|
require 't2-server/net/parameters'
|
40
40
|
require 't2-server/port'
|
41
|
+
require 't2-server/interaction'
|
41
42
|
require 't2-server/server'
|
42
43
|
require 't2-server/run'
|
43
44
|
require 't2-server/admin'
|
data/lib/t2-server/admin.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2010-
|
1
|
+
# Copyright (c) 2010-2013 The University of Manchester, UK.
|
2
2
|
#
|
3
3
|
# All rights reserved.
|
4
4
|
#
|
@@ -30,7 +30,11 @@
|
|
30
30
|
#
|
31
31
|
# Author: Robert Haines
|
32
32
|
|
33
|
+
# :stopdoc:
|
34
|
+
# This comment is needed to stop the above licence from being included in the
|
35
|
+
# documentation multiple times. Sigh.
|
33
36
|
module T2Server
|
37
|
+
# :startdoc:
|
34
38
|
|
35
39
|
# This call provides access to the administrative interface of a Taverna
|
36
40
|
# Server instance.
|
@@ -56,7 +60,6 @@ module T2Server
|
|
56
60
|
admin_description = xml_document(@server.read(@uri, "application/xml",
|
57
61
|
@credentials))
|
58
62
|
@resources = get_resources(admin_description)
|
59
|
-
#@resources.each {|key, value| puts "#{key}: #{value}"}
|
60
63
|
|
61
64
|
yield(self) if block_given?
|
62
65
|
end
|
@@ -117,7 +120,7 @@ module T2Server
|
|
117
120
|
# :startdoc:
|
118
121
|
|
119
122
|
# :call-seq:
|
120
|
-
# value ->
|
123
|
+
# value -> string
|
121
124
|
# value=
|
122
125
|
#
|
123
126
|
# Get or set the value held by this resource. This call always queries
|
@@ -129,7 +132,7 @@ module T2Server
|
|
129
132
|
end
|
130
133
|
|
131
134
|
# :call-seq:
|
132
|
-
# writable? ->
|
135
|
+
# writable? -> true or false
|
133
136
|
#
|
134
137
|
# Is this resource writable?
|
135
138
|
def writable?
|
data/lib/t2-server/exceptions.rb
CHANGED
@@ -83,12 +83,31 @@ module T2Server
|
|
83
83
|
# not necessarily indicate a problem with the server.
|
84
84
|
class UnexpectedServerResponse < T2ServerError
|
85
85
|
|
86
|
-
#
|
86
|
+
# The method that was called to produce this error.
|
87
|
+
attr_reader :method
|
88
|
+
|
89
|
+
# The path of the URI that returned this error.
|
90
|
+
attr_reader :path
|
91
|
+
|
92
|
+
# The HTTP error code of this error.
|
93
|
+
attr_reader :code
|
94
|
+
|
95
|
+
# The response body of this error. If the server did not supply one then
|
96
|
+
# this will be "<none>".
|
97
|
+
attr_reader :body
|
98
|
+
|
99
|
+
# Create a new UnexpectedServerResponse with details of which HTTP method
|
100
|
+
# was called, the path that it was called on and the specified unexpected
|
87
101
|
# response. The response to be passed in is that which was returned by a
|
88
102
|
# call to Net::HTTP#request.
|
89
|
-
def initialize(response)
|
90
|
-
|
91
|
-
|
103
|
+
def initialize(method, path, response)
|
104
|
+
@method = method
|
105
|
+
@path = path
|
106
|
+
@code = response.code
|
107
|
+
@body = response.body.empty? ? "<none>" : "#{response.body}"
|
108
|
+
message = "Unexpected server response:\n Method: #{@method}\n Path: "\
|
109
|
+
"#{@path}\n Code: #{@code}\n Body: #{@body}"
|
110
|
+
super message
|
92
111
|
end
|
93
112
|
end
|
94
113
|
|
@@ -0,0 +1,241 @@
|
|
1
|
+
# Copyright (c) 2010-2013 The University of Manchester, UK.
|
2
|
+
#
|
3
|
+
# All rights reserved.
|
4
|
+
#
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
7
|
+
#
|
8
|
+
# * Redistributions of source code must retain the above copyright notice,
|
9
|
+
# this list of conditions and the following disclaimer.
|
10
|
+
#
|
11
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
13
|
+
# and/or other materials provided with the distribution.
|
14
|
+
#
|
15
|
+
# * Neither the names of The University of Manchester nor the names of its
|
16
|
+
# contributors may be used to endorse or promote products derived from this
|
17
|
+
# software without specific prior written permission.
|
18
|
+
#
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
30
|
+
#
|
31
|
+
# Author: Robert Haines
|
32
|
+
|
33
|
+
require 'rubygems'
|
34
|
+
require 'atom'
|
35
|
+
require 'uri'
|
36
|
+
|
37
|
+
module T2Server
|
38
|
+
|
39
|
+
# The Interaction module provides access to Taverna workflow notifications
|
40
|
+
# as supplied by the Interaction Service. These are read from an Atom feed
|
41
|
+
# and returned as Notification objects. For more information about the
|
42
|
+
# Interaction Service, see
|
43
|
+
# http://dev.mygrid.org.uk/wiki/display/taverna/Interaction+service
|
44
|
+
module Interaction
|
45
|
+
|
46
|
+
# :stopdoc:
|
47
|
+
FEED_NS = "http://ns.taverna.org.uk/2012/interaction"
|
48
|
+
|
49
|
+
class Feed
|
50
|
+
def initialize(run)
|
51
|
+
@run = run
|
52
|
+
@cache = {:requests => {}, :replies => {}}
|
53
|
+
end
|
54
|
+
|
55
|
+
# Get all new notification requests since they were last checked.
|
56
|
+
#
|
57
|
+
# Here we really only want new unanswered notifications, but polling
|
58
|
+
# returns all requests new to *us*, even those that have been replied to
|
59
|
+
# elsewhere. Filter out answered requests here.
|
60
|
+
def new_requests
|
61
|
+
poll(:requests).select { |i| !i.has_reply? }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get all notifications, or all of a particular type.
|
65
|
+
def notifications(type = :all)
|
66
|
+
poll
|
67
|
+
|
68
|
+
case type
|
69
|
+
when :requests, :replies
|
70
|
+
@cache[type].values
|
71
|
+
else
|
72
|
+
@cache[:requests].values + @cache[:replies].values
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def entries(&block)
|
79
|
+
feed = Atom::Feed.load_feed(@run.read_notification_feed)
|
80
|
+
feed.each_entry(:paginate => true, &block)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Poll for all notification types and update the caches.
|
84
|
+
#
|
85
|
+
# Returns any new notifications, [] otherwise. If you are only
|
86
|
+
# interested in knowing about new notifications of a specific type you
|
87
|
+
# can use the type parameter to specify this. Use :requests, :replies or
|
88
|
+
# :all (default).
|
89
|
+
def poll(type = :all)
|
90
|
+
updates = []
|
91
|
+
requests = @cache[:requests]
|
92
|
+
replies = @cache[:replies]
|
93
|
+
|
94
|
+
entries do |entry|
|
95
|
+
# It's worth noting what happens here.
|
96
|
+
#
|
97
|
+
# This connection to a run's notification feed may not be the only
|
98
|
+
# one, or it might be a reconnection. As a result we might see a
|
99
|
+
# reply before we see the original request; atom feeds are last in
|
100
|
+
# first out.
|
101
|
+
#
|
102
|
+
# So if we see a reply, we should check to see if we have the
|
103
|
+
# request before setting the request to "replied". And if we see a
|
104
|
+
# request we should check to see if we have a reply for it already;
|
105
|
+
# we may have seen the reply the previous time through the loop.
|
106
|
+
note = Notification.new(entry, @run)
|
107
|
+
if note.is_reply?
|
108
|
+
next if replies.has_key? note.reply_to
|
109
|
+
requests[note.reply_to].has_reply unless requests[note.reply_to].nil?
|
110
|
+
replies[note.reply_to] = note
|
111
|
+
updates << note if type == :replies || type == :all
|
112
|
+
else
|
113
|
+
next if requests.has_key? note.id
|
114
|
+
note.has_reply unless replies[note.id].nil?
|
115
|
+
requests[note.id] = note
|
116
|
+
updates << note if type == :requests || type == :all
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
updates
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
# :startdoc:
|
125
|
+
|
126
|
+
# This class represents a Taverna notification.
|
127
|
+
class Notification
|
128
|
+
|
129
|
+
# The identifier of this notification.
|
130
|
+
attr_reader :id
|
131
|
+
|
132
|
+
# If this notification is a reply then this is the identifier of the
|
133
|
+
# notification that it is a reply to.
|
134
|
+
attr_reader :reply_to
|
135
|
+
|
136
|
+
# The URI of the notification page to show.
|
137
|
+
attr_reader :uri
|
138
|
+
|
139
|
+
# The serial number of a notification. This identifies a notification
|
140
|
+
# within a workflow.
|
141
|
+
attr_reader :serial
|
142
|
+
|
143
|
+
# :stopdoc:
|
144
|
+
def initialize(entry, run)
|
145
|
+
@run = run
|
146
|
+
reply_to = entry[FEED_NS, "in-reply-to"]
|
147
|
+
if reply_to.empty?
|
148
|
+
@is_reply = false
|
149
|
+
@has_reply = false
|
150
|
+
@id = entry[FEED_NS, "id"][0]
|
151
|
+
@is_notification = entry[FEED_NS, "progress"].empty? ? false : true
|
152
|
+
@uri = get_link(entry.links)
|
153
|
+
@serial = "#{entry[FEED_NS, 'path'][0]}-#{entry[FEED_NS, 'count'][0]}"
|
154
|
+
else
|
155
|
+
@is_reply = true
|
156
|
+
@is_notification = false
|
157
|
+
@reply_to = reply_to[0]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
# :startdoc:
|
161
|
+
|
162
|
+
# :call-seq:
|
163
|
+
# is_reply? -> true or false
|
164
|
+
#
|
165
|
+
# Is this notification a reply to another notification?
|
166
|
+
def is_reply?
|
167
|
+
@is_reply
|
168
|
+
end
|
169
|
+
|
170
|
+
# :call-seq:
|
171
|
+
# is_notification? -> true or false
|
172
|
+
#
|
173
|
+
# Is this notification a pure notification only? There is no user
|
174
|
+
# response to a pure notification, it is for information only.
|
175
|
+
def is_notification?
|
176
|
+
@is_notification
|
177
|
+
end
|
178
|
+
|
179
|
+
# :stopdoc:
|
180
|
+
def has_reply
|
181
|
+
@has_reply = true
|
182
|
+
end
|
183
|
+
# :startdoc:
|
184
|
+
|
185
|
+
# :call-seq:
|
186
|
+
# has_reply? -> true or false
|
187
|
+
#
|
188
|
+
# Does this notification have a reply? This only makes sense for
|
189
|
+
# notifications that are not replies or pure notifications.
|
190
|
+
def has_reply?
|
191
|
+
@has_reply
|
192
|
+
end
|
193
|
+
|
194
|
+
# :call-seq:
|
195
|
+
# input_data -> data
|
196
|
+
#
|
197
|
+
# Get the input data associated with this notification. Returns an empty
|
198
|
+
# string if this notification is a reply.
|
199
|
+
def input_data
|
200
|
+
return "" if is_reply?
|
201
|
+
|
202
|
+
data_name = "interaction#{@id}InputData.json"
|
203
|
+
@run.read_interaction_data(data_name)
|
204
|
+
rescue AttributeNotFoundError
|
205
|
+
# It does not matter if the file doesn't exist.
|
206
|
+
""
|
207
|
+
end
|
208
|
+
|
209
|
+
# :call-seq:
|
210
|
+
# reply(status, data)
|
211
|
+
#
|
212
|
+
# Given a status and some data this method uploads the data and
|
213
|
+
# publishes an interaction reply on the run's notification feed.
|
214
|
+
def reply(status, data)
|
215
|
+
data_name = "interaction#{@id}OutputData.json"
|
216
|
+
|
217
|
+
notification = Atom::Entry.new do |entry|
|
218
|
+
entry.title = "A reply to #{@id}"
|
219
|
+
entry.id = "#{@id}reply"
|
220
|
+
entry.content = ""
|
221
|
+
entry[FEED_NS, "run-id"] << @run.id
|
222
|
+
entry[FEED_NS, "in-reply-to"] << @id
|
223
|
+
entry[FEED_NS, "result-status"] << status
|
224
|
+
end.to_xml
|
225
|
+
|
226
|
+
@run.write_interaction_data(data_name, data)
|
227
|
+
@run.write_notification(notification)
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
|
232
|
+
def get_link(links)
|
233
|
+
links.each do |l|
|
234
|
+
return URI.parse(l.to_s) if l.rel == "presentation"
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2010-
|
1
|
+
# Copyright (c) 2010-2013 The University of Manchester, UK.
|
2
2
|
#
|
3
3
|
# All rights reserved.
|
4
4
|
#
|
@@ -59,7 +59,7 @@ module T2Server
|
|
59
59
|
end
|
60
60
|
|
61
61
|
# if we're given params they must be of the right type
|
62
|
-
if !params.nil?
|
62
|
+
if !params.nil? && !params.is_a?(ConnectionParameters)
|
63
63
|
raise ArgumentError, "Parameters must be ConnectionParameters", caller
|
64
64
|
end
|
65
65
|
|
@@ -93,23 +93,27 @@ module T2Server
|
|
93
93
|
@uri = uri
|
94
94
|
@params = params || DefaultConnectionParameters.new
|
95
95
|
|
96
|
-
#
|
96
|
+
# Open a persistent HTTP connection.
|
97
97
|
@http = Net::HTTP::Persistent.new("Taverna_Server_Ruby_Client")
|
98
|
+
|
99
|
+
# Set timeouts if specified.
|
100
|
+
@http.open_timeout = @params[:open_timeout] if @params[:open_timeout]
|
101
|
+
@http.read_timeout = @params[:read_timeout] if @params[:read_timeout]
|
98
102
|
end
|
99
103
|
|
100
104
|
# :call-seq:
|
101
|
-
# GET(uri, type, range, credentials) ->
|
105
|
+
# GET(uri, type, range, credentials) -> string
|
102
106
|
#
|
103
107
|
# HTTP GET a resource at _uri_ of _type_ from the server. If successful
|
104
108
|
# the body of the response is returned. A portion of the data can be
|
105
109
|
# retrieved by specifying a byte range, start..end, with the _range_
|
106
110
|
# parameter.
|
107
|
-
def GET(uri, type, range, credentials)
|
111
|
+
def GET(uri, type, range, credentials, &block)
|
108
112
|
get = Net::HTTP::Get.new(uri.path)
|
109
113
|
get["Accept"] = type
|
110
114
|
get["Range"] = "bytes=#{range.min}-#{range.max}" unless range.nil?
|
111
115
|
|
112
|
-
response = submit(get, uri, credentials)
|
116
|
+
response = submit(get, uri, credentials, &block)
|
113
117
|
|
114
118
|
case response
|
115
119
|
when Net::HTTPOK, Net::HTTPPartialContent
|
@@ -119,36 +123,35 @@ module T2Server
|
|
119
123
|
when Net::HTTPMovedTemporarily
|
120
124
|
new_conn = redirect(response["location"])
|
121
125
|
raise ConnectionRedirectError.new(new_conn)
|
122
|
-
when Net::HTTPNotFound
|
123
|
-
raise AttributeNotFoundError.new(uri.path)
|
124
|
-
when Net::HTTPForbidden
|
125
|
-
raise AccessForbiddenError.new("attribute #{uri.path}")
|
126
|
-
when Net::HTTPUnauthorized
|
127
|
-
raise AuthorizationError.new(credentials)
|
128
126
|
else
|
129
|
-
|
127
|
+
report_error("GET", uri.path, response, credentials)
|
130
128
|
end
|
131
129
|
end
|
132
130
|
|
133
131
|
# :call-seq:
|
134
|
-
# PUT(uri, value, type, credentials) ->
|
132
|
+
# PUT(uri, value, type, credentials) -> true or false
|
135
133
|
# PUT(uri, value, type, credentials) -> URI
|
134
|
+
# PUT(uri, stream, type, credentials) -> URI
|
135
|
+
#
|
136
|
+
# Upload data via HTTP PUT. Data may be specified as a value or as a
|
137
|
+
# stream. The stream can be any object that has a read(length) method;
|
138
|
+
# instances of File or IO, for example.
|
136
139
|
#
|
137
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
|
141
|
-
def PUT(uri, value, type, credentials)
|
140
|
+
# If successful _true_ or a URI to the uploaded resource is returned
|
141
|
+
# depending on whether the operation has altered a parameter (true) or
|
142
|
+
# uploaded new data (URI).
|
143
|
+
def PUT(uri, data, type, credentials)
|
142
144
|
put = Net::HTTP::Put.new(uri.path)
|
143
145
|
put.content_type = type
|
144
|
-
|
146
|
+
|
147
|
+
set_upload_body(put, data)
|
145
148
|
|
146
149
|
response = submit(put, uri, credentials)
|
147
150
|
|
148
151
|
case response
|
149
|
-
when Net::HTTPOK
|
150
|
-
# We've set a parameter so we get 200
|
151
|
-
# true to indicate success.
|
152
|
+
when Net::HTTPOK, Net::HTTPAccepted
|
153
|
+
# We've either set a parameter or started a run so we get 200 or 202
|
154
|
+
# back from the server, respectively. Return true to indicate success.
|
152
155
|
true
|
153
156
|
when Net::HTTPCreated
|
154
157
|
# We've uploaded data so we get 201 back from the server. Return the
|
@@ -158,26 +161,27 @@ module T2Server
|
|
158
161
|
# We've modified data so we get 204 back from the server. Return the
|
159
162
|
# uri of the modified resource.
|
160
163
|
uri
|
161
|
-
when Net::
|
162
|
-
raise
|
163
|
-
when Net::HTTPForbidden
|
164
|
-
raise AccessForbiddenError.new("attribute #{uri.path}")
|
165
|
-
when Net::HTTPUnauthorized
|
166
|
-
raise AuthorizationError.new(credentials)
|
164
|
+
when Net::HTTPServiceUnavailable
|
165
|
+
raise ServerAtCapacityError.new
|
167
166
|
else
|
168
|
-
|
167
|
+
report_error("PUT", uri.path, response, credentials)
|
169
168
|
end
|
170
169
|
end
|
171
170
|
|
172
171
|
# :call-seq:
|
173
|
-
# POST(uri, value, type, credentials)
|
172
|
+
# POST(uri, value, type, credentials) -> URI
|
173
|
+
# POST(uri, stream, type, credentials) -> URI
|
174
|
+
#
|
175
|
+
# Upload data via HTTP POST. Data may be specified as a value or as a
|
176
|
+
# stream. The stream can be any object that has a read(length) method;
|
177
|
+
# instances of File or IO, for example.
|
174
178
|
#
|
175
|
-
#
|
176
|
-
|
177
|
-
def POST(uri, value, type, credentials)
|
179
|
+
# If successful the URI of the uploaded resource is returned.
|
180
|
+
def POST(uri, data, type, credentials)
|
178
181
|
post = Net::HTTP::Post.new(uri.path)
|
179
182
|
post.content_type = type
|
180
|
-
|
183
|
+
|
184
|
+
set_upload_body(post, data)
|
181
185
|
|
182
186
|
response = submit(post, uri, credentials)
|
183
187
|
|
@@ -185,23 +189,15 @@ module T2Server
|
|
185
189
|
when Net::HTTPCreated
|
186
190
|
# return the URI of the newly created item
|
187
191
|
URI.parse(response['location'])
|
188
|
-
when Net::
|
189
|
-
raise
|
190
|
-
when Net::HTTPForbidden
|
191
|
-
if response.body.chomp.include? "server load exceeded"
|
192
|
-
raise ServerAtCapacityError.new
|
193
|
-
else
|
194
|
-
raise AccessForbiddenError.new("attribute #{uri.path}")
|
195
|
-
end
|
196
|
-
when Net::HTTPUnauthorized
|
197
|
-
raise AuthorizationError.new(credentials)
|
192
|
+
when Net::HTTPServiceUnavailable
|
193
|
+
raise ServerAtCapacityError.new
|
198
194
|
else
|
199
|
-
|
195
|
+
report_error("POST", uri.path, response, credentials)
|
200
196
|
end
|
201
197
|
end
|
202
198
|
|
203
199
|
# :call-seq:
|
204
|
-
# DELETE(uri, credentials) ->
|
200
|
+
# DELETE(uri, credentials) -> true or false
|
205
201
|
#
|
206
202
|
# Perform an HTTP DELETE on a _uri_ on the server. If successful true
|
207
203
|
# is returned.
|
@@ -214,19 +210,13 @@ module T2Server
|
|
214
210
|
when Net::HTTPNoContent
|
215
211
|
# Success, carry on...
|
216
212
|
true
|
217
|
-
when Net::HTTPNotFound
|
218
|
-
false
|
219
|
-
when Net::HTTPForbidden
|
220
|
-
raise AccessForbiddenError.new(uri)
|
221
|
-
when Net::HTTPUnauthorized
|
222
|
-
raise AuthorizationError.new(credentials)
|
223
213
|
else
|
224
|
-
|
214
|
+
report_error("DELETE", uri.path, response, credentials)
|
225
215
|
end
|
226
216
|
end
|
227
217
|
|
228
218
|
# :call-seq:
|
229
|
-
# OPTIONS(uri, credentials) ->
|
219
|
+
# OPTIONS(uri, credentials) -> hash
|
230
220
|
#
|
231
221
|
# Perform the HTTP OPTIONS command on the given _uri_ and return a hash
|
232
222
|
# of the headers returned.
|
@@ -238,23 +228,59 @@ module T2Server
|
|
238
228
|
case response
|
239
229
|
when Net::HTTPOK
|
240
230
|
response.to_hash
|
231
|
+
else
|
232
|
+
report_error("OPTIONS", uri.path, response, credentials)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
|
238
|
+
# If one of the expected responses for a HTTP method is not received then
|
239
|
+
# handle the error condition here.
|
240
|
+
def report_error(method, path, response, credentials)
|
241
|
+
case response
|
242
|
+
when Net::HTTPNotFound
|
243
|
+
raise AttributeNotFoundError.new(path)
|
241
244
|
when Net::HTTPForbidden
|
242
|
-
raise AccessForbiddenError.new("resource #{
|
245
|
+
raise AccessForbiddenError.new("resource #{path}")
|
243
246
|
when Net::HTTPUnauthorized
|
244
247
|
raise AuthorizationError.new(credentials)
|
245
248
|
else
|
246
|
-
raise UnexpectedServerResponse.new(response)
|
249
|
+
raise UnexpectedServerResponse.new(method, path, response)
|
247
250
|
end
|
248
251
|
end
|
249
252
|
|
250
|
-
|
253
|
+
# If we have a stream then we need to set body_stream and then either
|
254
|
+
# supply a content length or set the transfer encoding to "chunked". A
|
255
|
+
# file object can supply its size, a bare IO object cannot. If we have a
|
256
|
+
# simple value we can set body directly.
|
257
|
+
def set_upload_body(request, data)
|
258
|
+
if data.respond_to? :read
|
259
|
+
request.body_stream = data
|
260
|
+
if data.respond_to? :size
|
261
|
+
request.content_length = data.size
|
262
|
+
else
|
263
|
+
request["Transfer-encoding"] = "chunked"
|
264
|
+
end
|
265
|
+
else
|
266
|
+
request.body = data
|
267
|
+
end
|
268
|
+
end
|
251
269
|
|
252
|
-
|
270
|
+
# If a block is passed in here then the response is returned in chunks
|
271
|
+
# (streamed). If no block is passed in the whole response is read into
|
272
|
+
# memory and returned.
|
273
|
+
def submit(request, uri, credentials, &block)
|
253
274
|
|
254
275
|
credentials.authenticate(request) unless credentials.nil?
|
255
276
|
|
277
|
+
response = nil
|
256
278
|
begin
|
257
|
-
@http.request(uri, request)
|
279
|
+
@http.request(uri, request) do |r|
|
280
|
+
r.read_body(&block)
|
281
|
+
response = r
|
282
|
+
end
|
283
|
+
response
|
258
284
|
rescue InternalHTTPError => e
|
259
285
|
raise ConnectionError.new(e)
|
260
286
|
end
|
@@ -276,6 +302,10 @@ module T2Server
|
|
276
302
|
def initialize(uri, params = nil)
|
277
303
|
super(uri, params)
|
278
304
|
|
305
|
+
if OpenSSL::SSL::SSLContext::METHODS.include? @params[:ssl_version]
|
306
|
+
@http.ssl_version = @params[:ssl_version]
|
307
|
+
end
|
308
|
+
|
279
309
|
# Peer verification
|
280
310
|
if @params[:verify_peer]
|
281
311
|
if @params[:ca_file]
|