dot_net_services 0.0.1
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/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
|