dot_net_services 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +24 -0
- data/README +52 -0
- data/lib/dot_net_services/authentication.rb +168 -0
- data/lib/dot_net_services/error.rb +4 -0
- data/lib/dot_net_services/message_buffer.rb +269 -0
- data/lib/dot_net_services/session.rb +282 -0
- data/lib/dot_net_services.rb +143 -0
- data/lib/net/http/create_mb.rb +14 -0
- data/lib/net/http/retrieve.rb +14 -0
- data/lib/net/http/subscribe.rb +14 -0
- data/lib/net/http/unsubscribe.rb +14 -0
- data/spec/integration/TestService/Service/AnonymousResourceService.cs +9 -0
- data/spec/integration/TestService/Service/App.config +32 -0
- data/spec/integration/TestService/Service/PlainTextService.cs +37 -0
- data/spec/integration/TestService/Service/Program.cs +49 -0
- data/spec/integration/TestService/Service/Properties/AssemblyInfo.cs +33 -0
- data/spec/integration/TestService/Service/ResourceContract.cs +17 -0
- data/spec/integration/TestService/Service/ResourceService.cs +58 -0
- data/spec/integration/TestService/Service/Service.csproj +71 -0
- data/spec/integration/TestService/TestService.sln +33 -0
- data/spec/integration/end_to_end_spec.rb +84 -0
- data/spec/integration/vmb_spec.rb +30 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/unit/dot_net_services/authentication_spec.rb +289 -0
- data/spec/unit/dot_net_services/message_buffer_spec.rb +161 -0
- data/spec/unit/dot_net_services/session_spec.rb +247 -0
- metadata +87 -0
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2008, ThoughtWorks
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
* Redistributions of source code must retain the above copyright
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
10
|
+
documentation and/or other materials provided with the distribution.
|
11
|
+
* Neither the name of the ThoughtWorks nor the
|
12
|
+
names of its contributors may be used to endorse or promote products
|
13
|
+
derived from this software without specific prior written permission.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY ThoughtWorks ''AS IS'' AND ANY
|
16
|
+
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THOUGHTWORKS BE LIABLE FOR ANY
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
= .NET Services for Ruby
|
2
|
+
|
3
|
+
* Project homepage: http://dotnetservicesruby.com
|
4
|
+
* Download: http://rubyforge.org/frs/?group_id=7155
|
5
|
+
* Demo application: http://dotnetservicesruby.com/billboard
|
6
|
+
* Source code: http://rubyforge.org/frs/?group_id=7155
|
7
|
+
* Documentation: http://dotnetservicesruby.com/documentation/index.html
|
8
|
+
|
9
|
+
== What's this?
|
10
|
+
|
11
|
+
.NET Services for Ruby is an open source library that helps Ruby programs communicate with Microsoft's .NET Services
|
12
|
+
using plain HTTP. It was developed by a small team in ThoughtWorks, while Microsoft provided funding, management and
|
13
|
+
technical guidance for the project.
|
14
|
+
|
15
|
+
== Installation
|
16
|
+
|
17
|
+
The library can be installed as a 'dot_net_services' gem, from RubyForge gem repository:
|
18
|
+
|
19
|
+
$ gem install dot_net_services
|
20
|
+
|
21
|
+
or downloaded as an archive from RubyForge [http://rubyforge.org/frs/?group_id=7155].
|
22
|
+
|
23
|
+
<i>NOTE: Version number 0.1.0 tells you that the API will have backwards-incompatible changes in future, so
|
24
|
+
Vendor Everything! [http://errtheblog.com/posts/50-vendor-everything]</i>
|
25
|
+
|
26
|
+
== Documentation
|
27
|
+
|
28
|
+
API: http://dotnetservicesruby.com/documentation/classes/DotNetServices.html
|
29
|
+
|
30
|
+
.NET Services: http://go.microsoft.com/fwlink/?LinkID=129428
|
31
|
+
|
32
|
+
== Demo application
|
33
|
+
|
34
|
+
To provide an example of our interop API in action, we have implemented a small Rails application called BillBoard.
|
35
|
+
We were working on the API while building the app, which helped us study the technology and discover the right
|
36
|
+
abstractions.
|
37
|
+
|
38
|
+
You can see BillBoard in action at http://dotnetservicesruby.com/billboard and download BillBoard source code from
|
39
|
+
RubyForge [http://rubyforge.org/frs/?group_id=7155].
|
40
|
+
|
41
|
+
== Contacts
|
42
|
+
|
43
|
+
Users maillist: http://rubyforge.org/mailman/listinfo/dotnetsrv-ruby-users
|
44
|
+
ThoughtWorks: info-us@thoughtworks.com
|
45
|
+
|
46
|
+
== License
|
47
|
+
|
48
|
+
BSD license. See [LICENSE].
|
49
|
+
|
50
|
+
== Copyright
|
51
|
+
|
52
|
+
(c) ThoughtWorks, Inc 2008
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
|
5
|
+
module DotNetServices
|
6
|
+
# This stores the token and expiration time. The default expiration
|
7
|
+
# time is 1 day.
|
8
|
+
module Authentication # :nodoc:
|
9
|
+
@cache = {}
|
10
|
+
|
11
|
+
# Authentication token.
|
12
|
+
class Token # :nodoc:
|
13
|
+
|
14
|
+
attr_reader :value, :expiry
|
15
|
+
|
16
|
+
# Create a new authentication token; defaults to expire in one day.
|
17
|
+
def initialize(value, expiry = Time.now + 24 *60 * 60)
|
18
|
+
# workaround for a known bug
|
19
|
+
match = value.match(/^([^=]+==).*/)
|
20
|
+
if match
|
21
|
+
@value = match[1]
|
22
|
+
@expiry = expiry
|
23
|
+
else
|
24
|
+
raise AuthenticationError,
|
25
|
+
"Response from access control service doesn't seem to contain a valid authentication token:\n" +
|
26
|
+
value.inspect
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def expired?
|
31
|
+
@expiry < Time.now
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
@value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Standard Username and Password authenticator.
|
40
|
+
class UsernamePassword # :nodoc:
|
41
|
+
|
42
|
+
attr_reader :token, :username, :password
|
43
|
+
|
44
|
+
def initialize(username, password, token = nil)
|
45
|
+
@username, @password = username, password
|
46
|
+
@token = token
|
47
|
+
end
|
48
|
+
|
49
|
+
def authenticate
|
50
|
+
return if @token and not @token.expired?
|
51
|
+
@token = acquire_token
|
52
|
+
end
|
53
|
+
|
54
|
+
# Enhance the request with the identity token provided by the identity service.
|
55
|
+
def enhance(request)
|
56
|
+
authenticate
|
57
|
+
request['X-MS-Identity-Token'] = token.value
|
58
|
+
end
|
59
|
+
|
60
|
+
def ==(other)
|
61
|
+
other.is_a?(Authentication::UsernamePassword) && @username == other.username && @password == other.password
|
62
|
+
end
|
63
|
+
alias :eql? :==
|
64
|
+
|
65
|
+
def hash
|
66
|
+
@hash ||= @username.hash & @password.hash
|
67
|
+
end
|
68
|
+
|
69
|
+
# Retrieve a token from the DotNetServices token issuing service.
|
70
|
+
def acquire_token
|
71
|
+
http = Net::HTTP.new(DotNetServices.identity_host, 443)
|
72
|
+
http.use_ssl = true
|
73
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
74
|
+
|
75
|
+
escaped_username = CGI.escape(@username)
|
76
|
+
escaped_password = CGI.escape(@password)
|
77
|
+
begin
|
78
|
+
response = http.get("/issuetoken.aspx?u=#{escaped_username}&p=#{escaped_password}")
|
79
|
+
rescue => e
|
80
|
+
raise AuthenticationError, "Failed to obtain authentication token. Original error of type #{e.class} " +
|
81
|
+
"was overridden to prevent logging security-sensitive data"
|
82
|
+
end
|
83
|
+
|
84
|
+
unless response.is_a?(Net::HTTPOK)
|
85
|
+
raise AuthenticationError, "Failed to obtain a security token from the identity service. HTTP response was #{response.class.name}"
|
86
|
+
end
|
87
|
+
|
88
|
+
Token.new(response.body)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Certificate authenticator. NOT YET IMPLEMENTED
|
93
|
+
class Certificate # :nodoc:
|
94
|
+
def authenticate
|
95
|
+
raise "not implemented"
|
96
|
+
end
|
97
|
+
|
98
|
+
def enhance(request)
|
99
|
+
raise "not implemented"
|
100
|
+
end
|
101
|
+
|
102
|
+
def hash
|
103
|
+
1
|
104
|
+
end
|
105
|
+
|
106
|
+
def ==(other)
|
107
|
+
other.is_a?(Certificate)
|
108
|
+
end
|
109
|
+
alias :eql? :==
|
110
|
+
end
|
111
|
+
|
112
|
+
# An anonymous authenticator. It is used as a stand-in for
|
113
|
+
# services which do not require authentication.
|
114
|
+
class Anonymous # :nodoc:
|
115
|
+
def authenticate() end
|
116
|
+
def enhance(request) end
|
117
|
+
def ==(another) another.is_a?(Anonymous) end
|
118
|
+
alias :eql? :==
|
119
|
+
def hash() -1 end
|
120
|
+
end
|
121
|
+
|
122
|
+
class << self
|
123
|
+
def setup(auth_data)
|
124
|
+
authenticator = create_authenticator(auth_data)
|
125
|
+
@cache[authenticator] ||= authenticator
|
126
|
+
end
|
127
|
+
|
128
|
+
# Create an authenticator based on the data provided.
|
129
|
+
def create_authenticator(auth_data)
|
130
|
+
if auth_data.nil?
|
131
|
+
Authentication::Anonymous.new
|
132
|
+
elsif !auth_data.is_a? Hash
|
133
|
+
auth_data
|
134
|
+
elsif auth_data.empty?
|
135
|
+
Authentication::Anonymous.new
|
136
|
+
else
|
137
|
+
auth_data_copy = auth_data.dup
|
138
|
+
username = auth_data_copy.delete(:username)
|
139
|
+
password = auth_data_copy.delete(:password)
|
140
|
+
certificate = auth_data_copy.delete(:certificate)
|
141
|
+
|
142
|
+
unless auth_data_copy.empty?
|
143
|
+
raise ArgumentError, "Auth data contains unknown options: #{auth_data.keys.inspect}"
|
144
|
+
end
|
145
|
+
|
146
|
+
if username && !password
|
147
|
+
raise ArgumentError, "Auth data specifies username, but no password."
|
148
|
+
elsif password && !username
|
149
|
+
raise ArgumentError, "Auth data specifies password, but no username."
|
150
|
+
elsif (username || password) && certificate
|
151
|
+
raise ArgumentError, "Cannot determine authentication type from auth data."
|
152
|
+
elsif username && password
|
153
|
+
Authentication::UsernamePassword.new(username, password)
|
154
|
+
elsif certificate
|
155
|
+
Authentication::Certificate.new
|
156
|
+
else
|
157
|
+
raise "Internal error. Unable to setup authenticator from #{auth_data.inspect}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def clear_cache!
|
163
|
+
@cache.clear
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,269 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module DotNetServices
|
4
|
+
|
5
|
+
# The MessageBuffer class is used for creating Volatile Message Buffer (VMB) endpoints on the .NET Services bus, and
|
6
|
+
# retrieving messages from them.
|
7
|
+
#
|
8
|
+
# A VMB is a temporary message buffer that can be used for asynchronous communications between senders and receivers
|
9
|
+
# of messages. A process may request creation of a VMB on any URL within a solution namespace. Any HTTP requests
|
10
|
+
# (other than GETs) to that URL are stored in the buffer, until some process retrieves them by sending an HTTP
|
11
|
+
# X-RETRIEVE request to the management endpoint.
|
12
|
+
#
|
13
|
+
# VMB can exist by itself, not tied to the lifecycle of a process that originally created it. Further information
|
14
|
+
# about VMBs can be found in .NET Services portal [http://go.microsoft.com/fwlink/?LinkID=129428].
|
15
|
+
#
|
16
|
+
# == Usage examples
|
17
|
+
#
|
18
|
+
# MessageBuffer instance represents a VMB endpoint. Typical usage looks as follows:
|
19
|
+
#
|
20
|
+
# error_handler = lambda { |error| logger.error(error) }
|
21
|
+
#
|
22
|
+
# buffer = DotNetServices::MessageBuffer.open_and_poll(
|
23
|
+
# "MySolution/MyVMB",
|
24
|
+
# {:username => 'MySolution', :password => 'my_password'},
|
25
|
+
# error_handler) do
|
26
|
+
# |message|
|
27
|
+
# ... process the message
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# This invocation does all of the following:
|
31
|
+
#
|
32
|
+
# * Creates a MessageBuffer instance associated with a specified endpoint (/MySolution/MyVMB)
|
33
|
+
# * obains a security token from trhe Identity service (see Session for details on that).
|
34
|
+
# * Creates a VMB on the .NET Services bus if it doesn't exist yet
|
35
|
+
# * immediately starts polling it
|
36
|
+
# * if it retrieves a message, it passes it to the block. The message is an instance of Net::HTTP::Request that
|
37
|
+
# looks exactly the same as what the sender originally sent to the bus.
|
38
|
+
# * if an error occurs while polling, the error is passed to the error_handler block. Polling then continues.
|
39
|
+
#
|
40
|
+
# == Guidelines
|
41
|
+
#
|
42
|
+
# In cases where an application (which in this case means a process) needs to process multiple types of messages,
|
43
|
+
# Microsoft recommends to create a single VMB per application, route all message types through the same VMB, and
|
44
|
+
# route messages to appropriate processors within the application itself.
|
45
|
+
#
|
46
|
+
# In the current version of the API, we provide no explicit support for clustering message processors. If you use
|
47
|
+
# MessageBuffer#open_and_poll(), it's recommended that you only run one cipy of a message processor. If clustering
|
48
|
+
# is required (for high availability reasons), you can use lower-level MessageBuffer methods to do it.
|
49
|
+
#
|
50
|
+
# .NET Services VMBs provide pub-sub and multicast functionality which can be used even over plain HTTP. Which is
|
51
|
+
# amazing. However, we don't support this functionality in the current version of our library.
|
52
|
+
class MessageBuffer
|
53
|
+
|
54
|
+
attr_reader :session
|
55
|
+
|
56
|
+
class << self
|
57
|
+
|
58
|
+
# Creates a MessageBuffer instance, acquires a security token, registers a VMB if necessary.
|
59
|
+
# Unlike Session#open, +auth_data+ is mandatory here.
|
60
|
+
# If given a block, passes the MessageBuffer instance to the block, and closes it at the end of the block
|
61
|
+
# returns the result of the block (if called with a block), or the buffer instance opened
|
62
|
+
def open(name, auth_data, &block)
|
63
|
+
buffer = MessageBuffer.new(name, auth_data).open
|
64
|
+
if block_given?
|
65
|
+
begin
|
66
|
+
return yield(buffer)
|
67
|
+
ensure
|
68
|
+
# TODO gracefully close the buffer
|
69
|
+
end
|
70
|
+
else
|
71
|
+
return buffer
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Invoke MessageBuffer#open to create a MessageBuffer instance, subscribe the buffer to receive messages
|
76
|
+
# posted to the +subscription_endpoint+, then poll it until the containing thread or process is terminated.
|
77
|
+
#
|
78
|
+
# Whenever a message is retrieved from the buffer, this method passes it to the block. When an error occurs
|
79
|
+
# (while polling, or raised by the block), it is passed to +error_handler+, which should be a lambda, or an
|
80
|
+
# object with handle(error) public method. If no +error_handler+ is provided, the error is printed out
|
81
|
+
# to STDERR and then ignored.
|
82
|
+
def open_and_poll(name, subscription_endpoint, auth_data, error_handler=nil, &block)
|
83
|
+
MessageBuffer.new(name, auth_data).open_and_poll(subscription_endpoint, error_handler, &block)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Initializes a new MessageBuffer instance.
|
88
|
+
# Does not create a VMB on the .NET Services bus (use #open for that)
|
89
|
+
def initialize(name, auth_data)
|
90
|
+
@name = name
|
91
|
+
@session = Session.new(name, auth_data)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Register the message buffer (by sending X-CREATEMB to the management endpoint).
|
95
|
+
#
|
96
|
+
# Returns the buffer. Raises an exception if the creation is not successful.
|
97
|
+
def register
|
98
|
+
createmb_response = @session.createmb
|
99
|
+
|
100
|
+
unless createmb_response.is_a?(Net::HTTPCreated)
|
101
|
+
# the buffer may already be there
|
102
|
+
unless @session.get_from_relay.is_a?(Net::HTTPSuccess)
|
103
|
+
raise "Creating VMB failed. Service responded with #{createmb_response.class.name}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
# Initiates a VMB session by:
|
110
|
+
# * acquiring a security token
|
111
|
+
# * checks if there is already a VMB at the endpoint
|
112
|
+
# * creating a VMB if necessary
|
113
|
+
def open
|
114
|
+
register unless @session.get_from_relay.is_a?(Net::HTTPSuccess)
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
# Open the buffer (see #open), subscribe the buffer to receive messages
|
119
|
+
# posted to the +subscription_endpoint+, then poll it until the containing thread or
|
120
|
+
# process is terminated.
|
121
|
+
#
|
122
|
+
# Whenever a message is retrieved from the buffer, this method passes it to the block. When an error occurs
|
123
|
+
# (while polling, or raised by the block), it is passed to +error_handler+, which should be a lambda, or an
|
124
|
+
# object with handle(error) public method. If no +error_handler+ is provided, the error is printed out
|
125
|
+
# to STDERR and then ignored.
|
126
|
+
def open_and_poll(subscription_endpoint, error_handler=nil, &block)
|
127
|
+
raise "MessageBuffer#open_and_poll requires a block" unless block_given?
|
128
|
+
|
129
|
+
@subscription_endpoint = subscription_endpoint
|
130
|
+
|
131
|
+
open
|
132
|
+
subscribe(@subscription_endpoint)
|
133
|
+
|
134
|
+
begin
|
135
|
+
loop do
|
136
|
+
begin
|
137
|
+
message = poll
|
138
|
+
block.call(message) if message
|
139
|
+
rescue Object => error
|
140
|
+
if error_handler
|
141
|
+
if error_handler.is_a?(Proc)
|
142
|
+
error_handler.call(error)
|
143
|
+
else
|
144
|
+
error_handler.handle(error)
|
145
|
+
end
|
146
|
+
else
|
147
|
+
STDERR.puts
|
148
|
+
STDERR.puts "Message Buffer Error"
|
149
|
+
STDERR.puts error.message
|
150
|
+
STDERR.puts error.backtrace.map { |line| " #{line}"}
|
151
|
+
STDERR.puts
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
ensure
|
156
|
+
@subscription_endpoint = nil
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Poll VMB endpoint for new messages; return the message if one was retrieved, or nil if it wasn't.
|
161
|
+
#
|
162
|
+
# Raises an exception if the response from the bus contains an error code (which usually means that the VMB is
|
163
|
+
# not registered, but may mean other things, for example if the bus itself is not accessible for some reason).
|
164
|
+
#
|
165
|
+
# +timeout+ parameter regulates how long a polling request will be held by the bus if there are no messages.
|
166
|
+
#
|
167
|
+
# An HTTP server that holds HTTP connections because it has nothing to respond with is somewhat uncommon, so a
|
168
|
+
# more detailed explanation is due.
|
169
|
+
#
|
170
|
+
# Normally, any HTTP interaction begins by opening of a TCP socket from the client to the server. The client
|
171
|
+
# then writes the HTTP request into that socket and waits until the server responds back and closes the socket.
|
172
|
+
# If the server doesn't respond for a long time (a minute, usually), the client drops the socket
|
173
|
+
# and declares timeout.
|
174
|
+
#
|
175
|
+
# Many times, when you poll a VMB endpoint, it will have no messages. If the bus simply came back immediately with
|
176
|
+
# "No Content" response, this would cause a VMB subscriber needs to constantly poll the buffer, abusing the bus
|
177
|
+
# infrastructure.
|
178
|
+
#
|
179
|
+
# .NET Services gets around this problem by making the client connection hang for some time, either until there
|
180
|
+
# is a message, or a certain number of seconds has passed, and there is still no message. That number of seconds is
|
181
|
+
# what the +timeout+ parameter specifies. Microsoft suggested that 10-20 seconds is reasonable for
|
182
|
+
# production purposes. #open_and_poll() loop uses 15.
|
183
|
+
def poll(timeout=nil)
|
184
|
+
response = @session.retrieve(:encoding => 'asreply', :timeout => timeout)
|
185
|
+
case response
|
186
|
+
when Net::HTTPNoContent
|
187
|
+
return nil
|
188
|
+
when Net::HTTPNotFound
|
189
|
+
register
|
190
|
+
subscribe(@subscription_endpoint) if @subscription_endpoint
|
191
|
+
return nil
|
192
|
+
when Net::HTTPSuccess
|
193
|
+
return response
|
194
|
+
else
|
195
|
+
raise "Retrieving messages failed. Response was #{response.class.name}" unless response.is_a?(Net::HTTPSuccess)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Delete the message buffer. This removes the buffer entirely
|
200
|
+
# from the bus, not just the local reference to it.
|
201
|
+
def delete
|
202
|
+
response = @session.delete
|
203
|
+
|
204
|
+
unless response.is_a?(Net::HTTPNoContent)
|
205
|
+
raise "Deleting VMB failed. Response was #{response.class.name}"
|
206
|
+
end
|
207
|
+
self
|
208
|
+
end
|
209
|
+
|
210
|
+
# Queries the VMB management endpoint for the VMB expiry time. With every #poll (HTTP X-RETRIEVE to the VMB management
|
211
|
+
# endpoint) the bus sets the expiry time of this VMB to 30 minutes later. If 30 minutes pass and there are no
|
212
|
+
# further polls, the bus automatically delets the buffer.
|
213
|
+
def expires
|
214
|
+
response = @session.get_from_relay
|
215
|
+
|
216
|
+
unless response.is_a?(Net::HTTPOK)
|
217
|
+
raise "Querying expiry status of VMB failed. Response was #{response.class}"
|
218
|
+
end
|
219
|
+
|
220
|
+
expires_header = response["expires"]
|
221
|
+
raise "Querying expiry status of VMB failed. Response doesn't have expires: header" unless expires_header
|
222
|
+
DateTime.parse(expires_header)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Performs an empty POST to the VMB management endpoint, which extends the VMB expiry time without retrieving any
|
226
|
+
# messages.
|
227
|
+
def keep_alive
|
228
|
+
# if we don't pass a linefeed as a body to the VMB management endpoint, it responds with HTTP 411 Length Required
|
229
|
+
response = @session.post_to_relay "\n"
|
230
|
+
case response
|
231
|
+
when Net::HTTPSuccess
|
232
|
+
self
|
233
|
+
else
|
234
|
+
raise "POST to the VMB management endpoint failed. Response was #{response.class}"
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Subscribe to an endpoint.
|
239
|
+
#
|
240
|
+
# HTTP messages sent to the +endpoint+ will be routed to this message buffer
|
241
|
+
def subscribe(target_path)
|
242
|
+
subscription_endpoint_url = DotNetServices.root_url + "/" + target_path
|
243
|
+
subscribe_response = @session.subscribe(:target => subscription_endpoint_url)
|
244
|
+
case subscribe_response
|
245
|
+
when Net::HTTPSuccess
|
246
|
+
return self
|
247
|
+
when Net::HTTPConflict
|
248
|
+
unsubscribe(target_path)
|
249
|
+
resubscribe_response = @session.subscribe(:target => subscription_endpoint_url)
|
250
|
+
unless resubscribe_response.is_a?(Net::HTTPSuccess)
|
251
|
+
raise "Second X-SUBSCRIBE to VMB management endpoint failed. Response was #{resubscribe_response.class}"
|
252
|
+
end
|
253
|
+
else
|
254
|
+
raise "X-SUBSCRIBE to VMB management endpoint failed. Response was #{subscribe_response.class}"
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Unsubscribe from an endpoint.
|
259
|
+
def unsubscribe(target_path)
|
260
|
+
subscription_endpoint_url = DotNetServices.root_url + "/" + target_path
|
261
|
+
unsubscribe_response = @session.unsubscribe(:target => subscription_endpoint_url)
|
262
|
+
unless unsubscribe_response.is_a?(Net::HTTPSuccess)
|
263
|
+
raise "X-UNSUBSCRIBE to VMB management endpoint failed. Response was #{subscribe_response.class}"
|
264
|
+
end
|
265
|
+
self
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
end
|