701cb4a502a1c35d427980021eb625f7b2ed9a40
[mono.git] / mcs / class / RabbitMQ.Client / src / client / api / ConnectionFactory.cs
1 // This source code is dual-licensed under the Apache License, version
2 // 2.0, and the Mozilla Public License, version 1.1.
3 //
4 // The APL v2.0:
5 //
6 //---------------------------------------------------------------------------
7 //   Copyright (C) 2007, 2008 LShift Ltd., Cohesive Financial
8 //   Technologies LLC., and Rabbit Technologies Ltd.
9 //
10 //   Licensed under the Apache License, Version 2.0 (the "License");
11 //   you may not use this file except in compliance with the License.
12 //   You may obtain a copy of the License at
13 //
14 //       http://www.apache.org/licenses/LICENSE-2.0
15 //
16 //   Unless required by applicable law or agreed to in writing, software
17 //   distributed under the License is distributed on an "AS IS" BASIS,
18 //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 //   See the License for the specific language governing permissions and
20 //   limitations under the License.
21 //---------------------------------------------------------------------------
22 //
23 // The MPL v1.1:
24 //
25 //---------------------------------------------------------------------------
26 //   The contents of this file are subject to the Mozilla Public License
27 //   Version 1.1 (the "License"); you may not use this file except in
28 //   compliance with the License. You may obtain a copy of the License at
29 //   http://www.rabbitmq.com/mpl.html
30 //
31 //   Software distributed under the License is distributed on an "AS IS"
32 //   basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
33 //   License for the specific language governing rights and limitations
34 //   under the License.
35 //
36 //   The Original Code is The RabbitMQ .NET Client.
37 //
38 //   The Initial Developers of the Original Code are LShift Ltd.,
39 //   Cohesive Financial Technologies LLC., and Rabbit Technologies Ltd.
40 //
41 //   Portions created by LShift Ltd., Cohesive Financial Technologies
42 //   LLC., and Rabbit Technologies Ltd. are Copyright (C) 2007, 2008
43 //   LShift Ltd., Cohesive Financial Technologies LLC., and Rabbit
44 //   Technologies Ltd.;
45 //
46 //   All Rights Reserved.
47 //
48 //   Contributor(s): ______________________________________.
49 //
50 //---------------------------------------------------------------------------
51 using System;
52 using System.IO;
53 using System.Net;
54 using System.Net.Sockets;
55 using System.Collections;
56
57 using RabbitMQ.Client.Impl;
58 using RabbitMQ.Client.Exceptions;
59
60 namespace RabbitMQ.Client
61 {
62     ///<summary>Main entry point to the RabbitMQ .NET AMQP client
63     ///API. Constructs IConnection instances.</summary>
64     ///<remarks>
65     ///<para>
66     /// A simple example of connecting to a broker:
67     ///</para>
68     ///<example><code>
69     ///     ConnectionFactory factory = new ConnectionFactory();
70     ///     //
71     ///     // The next three lines are optional:
72     ///     factory.Parameters.UserName = ConnectionParameters.DefaultUser;
73     ///     factory.Parameters.Password = ConnectionParameters.DefaultPass;
74     ///     factory.Parameters.VirtualHost = ConnectionParameters.DefaultVHost;
75     ///     //
76     ///     IProtocol protocol = Protocols.DefaultProtocol;
77     ///     IConnection conn = factory.CreateConnection(protocol, hostName, portNumber);
78     ///     //
79     ///     IModel ch = conn.CreateModel();
80     ///     ushort ticket = ch.AccessRequest("/data");
81     ///     //
82     ///     // ... use ch's IModel methods ...
83     ///     //
84     ///     ch.Close(200, "Closing the channel");
85     ///     conn.Close(200, "Closing the connection");
86     ///</code></example>
87     ///<para>
88     /// Please see also the API overview and tutorial in the User Guide.
89     ///</para>
90     ///<para>
91     /// Some of the static methods described below take, as a
92     /// convenience, a System.Uri instance representing an AMQP server
93     /// address. The use of Uri here is not standardised - Uri is
94     /// simply a convenient container for internet-address-like
95     /// components. In particular, the Uri "Scheme" property is
96     /// ignored: only the "Host" and "Port" properties are extracted.
97     ///</para>
98     ///</remarks>
99     public class ConnectionFactory
100     {
101         private ConnectionParameters m_parameters = new ConnectionParameters();
102         ///<summary>Retrieve the parameters this factory uses to
103         ///construct IConnection instances.</summary>
104         public ConnectionParameters Parameters
105         {
106             get
107             {
108                 return m_parameters;
109             }
110         }
111
112         ///<summary>Constructs a ConnectionFactory with default values
113         ///for Parameters.</summary>
114         public ConnectionFactory()
115         {
116         }
117
118         protected virtual IConnection FollowRedirectChain
119             (int maxRedirects,
120              IDictionary connectionAttempts,
121              IDictionary connectionErrors,
122              ref AmqpTcpEndpoint[] mostRecentKnownHosts,
123              AmqpTcpEndpoint endpoint)
124         {
125             AmqpTcpEndpoint candidate = endpoint;
126             try {
127                 while (true) {
128                     int attemptCount =
129                         connectionAttempts.Contains(candidate)
130                         ? (int) connectionAttempts[candidate]
131                         : 0;
132                     connectionAttempts[candidate] = attemptCount + 1;
133                     bool insist = attemptCount >= maxRedirects;
134
135                     try {
136                         IProtocol p = candidate.Protocol;
137                         IFrameHandler fh = p.CreateFrameHandler(candidate);
138                         // At this point, we may be able to create
139                         // and fully open a successful connection,
140                         // in which case we're done, and the
141                         // connection should be returned.
142                         return p.CreateConnection(m_parameters, insist, fh);
143                     } catch (RedirectException re) {
144                         if (insist) {
145                             // We've been redirected, but we insisted that
146                             // we shouldn't be redirected! Well-behaved
147                             // brokers should never do this.
148                             string message = string.Format("Server {0} ignored 'insist' flag, redirecting us to {1}",
149                                                            candidate,
150                                                            re.Host);
151                             throw new ProtocolViolationException(message);
152                         } else {
153                             // We've been redirected. Follow this new link
154                             // in the chain, by setting
155                             // mostRecentKnownHosts (in case the chain
156                             // runs out), and updating candidate for the
157                             // next time round the loop.
158                             connectionErrors[candidate] = re;
159                             mostRecentKnownHosts = re.KnownHosts;
160                             candidate = re.Host;
161                         }
162                     }
163                 }
164             } catch (Exception e) {
165                 connectionErrors[candidate] = e;
166                 return null;
167             }
168         }
169
170         protected virtual IConnection CreateConnection(int maxRedirects,
171                                                        IDictionary connectionAttempts,
172                                                        IDictionary connectionErrors,
173                                                        params AmqpTcpEndpoint[] endpoints)
174         {
175             foreach (AmqpTcpEndpoint endpoint in endpoints)
176             {
177                 AmqpTcpEndpoint[] mostRecentKnownHosts = new AmqpTcpEndpoint[0];
178                 // ^^ holds a list of known-hosts that came back with
179                 // a connection.redirect. If, once we reach the end of
180                 // a chain of redirects, we still haven't managed to
181                 // get a usable connection, we recurse on
182                 // mostRecentKnownHosts, trying each of those in
183                 // turn. Finally, if neither the initial
184                 // chain-of-redirects for the current endpoint, nor
185                 // the chains-of-redirects for each of the
186                 // mostRecentKnownHosts gives us a usable connection,
187                 // we give up on this particular endpoint, and
188                 // continue with the foreach loop, trying the
189                 // remainder of the array we were given.
190                 IConnection conn = FollowRedirectChain(maxRedirects,
191                                                        connectionAttempts,
192                                                        connectionErrors,
193                                                        ref mostRecentKnownHosts,
194                                                        endpoint);
195                 if (conn != null) {
196                     return conn;
197                 }
198
199                 // Connection to this endpoint failed at some point
200                 // down the redirection chain - either the first
201                 // entry, or one of the re.Host values from subsequent
202                 // RedirectExceptions. We recurse into
203                 // mostRecentKnownHosts, to see if one of those is
204                 // suitable.
205                 if (mostRecentKnownHosts.Length > 0) {
206                     // Only bother recursing if we know of some
207                     // hosts. If we were to recurse with no endpoints
208                     // in the array, we'd stomp on
209                     // mostRecentException, which makes debugging
210                     // connectivity problems needlessly more
211                     // difficult.
212                     conn = CreateConnection(maxRedirects,
213                                             connectionAttempts,
214                                             connectionErrors,
215                                             mostRecentKnownHosts);
216                     if (conn != null) {
217                         return conn;
218                     }
219                 }
220             }
221             return null;
222         }
223
224         ///<summary>Create a connection to the first available
225         ///endpoint in the list provided. Up to a maximum of
226         ///maxRedirects broker-originated redirects are permitted for
227         ///each endpoint tried.</summary>
228         public virtual IConnection CreateConnection(int maxRedirects,
229                                                     params AmqpTcpEndpoint[] endpoints)
230         {
231             IDictionary connectionAttempts = new Hashtable();
232             IDictionary connectionErrors = new Hashtable();
233             IConnection conn = CreateConnection(maxRedirects,
234                                                 connectionAttempts,
235                                                 connectionErrors,
236                                                 endpoints);
237             if (conn != null) {
238                 return conn;
239             }
240             throw new BrokerUnreachableException(connectionAttempts, connectionErrors);
241         }
242
243         ///<summary>Create a connection to the first available
244         ///endpoint in the list provided. No broker-originated
245         ///redirects are permitted.</summary>
246         public virtual IConnection CreateConnection(params AmqpTcpEndpoint[] endpoints)
247         {
248             return CreateConnection(0, endpoints);
249         }
250
251         ///<summary>Create a connection to the endpoint specified.</summary>
252         ///<exception cref="ArgumentException"/>
253         public IConnection CreateConnection(IProtocol version,
254                                             string hostName,
255                                             int portNumber)
256         {
257             return CreateConnection(new AmqpTcpEndpoint[] {
258                                         new AmqpTcpEndpoint(version, hostName, portNumber)
259                                     });
260         }
261
262         ///<summary>Create a connection to the endpoint specified. The
263         ///port used is the default for the protocol.</summary>
264         ///<exception cref="ArgumentException"/>
265         public IConnection CreateConnection(IProtocol version, string hostName)
266         {
267             return CreateConnection(version, hostName, -1);
268         }
269
270         ///<summary>Create a connection to the endpoint specified.</summary>
271         ///<remarks>
272         /// Please see the class overview documentation for
273         /// information about the Uri format in use.
274         ///</remarks>
275         ///<exception cref="ArgumentException"/>
276         public IConnection CreateConnection(IProtocol version, Uri uri)
277         {
278             return CreateConnection(version, uri.Host, uri.Port);
279         }
280
281         ///<summary>Create a connection to the endpoint specified,
282         ///with the IProtocol from
283         ///Protocols.FromEnvironment().</summary>
284         ///<remarks>
285         /// Please see the class overview documentation for
286         /// information about the Uri format in use.
287         ///</remarks>
288         public IConnection CreateConnection(Uri uri)
289         {
290             return CreateConnection(Protocols.FromEnvironment(), uri.Host, uri.Port);
291         }
292
293         ///<summary>Create a connection to the host (and optional
294         ///port) specified, with the IProtocol from
295         ///Protocols.FromEnvironment(). The format of the address
296         ///string is the same as that accepted by
297         ///AmqpTcpEndpoint.Parse().</summary>
298         public IConnection CreateConnection(string address) {
299             return CreateConnection(AmqpTcpEndpoint.Parse(Protocols.FromEnvironment(), address));
300         }
301     }
302 }