2010-01-22 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Channels / HttpListenerManager.cs
1 //
2 // HttpListenerManager.cs
3 //
4 // Author:
5 //      Vladimir Krasnov <vladimirk@mainsoft.com>
6 //
7 // Copyright (C) 2005-2006 Mainsoft, Inc.  http://www.mainsoft.com
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 using System;
29 using System.Collections.Generic;
30 using System.Collections.Specialized;
31 using System.ServiceModel.Description;
32 using System.Text;
33 using System.Threading;
34 using System.Net;
35 using System.Web;
36
37 namespace System.ServiceModel.Channels
38 {
39         abstract class HttpContextInfo
40         {
41                 public abstract NameValueCollection QueryString { get; }
42                 public abstract Uri RequestUrl { get; }
43                 public abstract string HttpMethod { get; }
44                 public abstract void Abort ();
45         }
46
47         class HttpListenerContextInfo : HttpContextInfo
48         {
49                 public HttpListenerContextInfo (HttpListenerContext ctx)
50                 {
51                         this.ctx = ctx;
52                 }
53                 
54                 HttpListenerContext ctx;
55
56                 public HttpListenerContext Source {
57                         get { return ctx; }
58                 }
59
60                 public override NameValueCollection QueryString {
61                         get { return ctx.Request.QueryString; }
62                 }
63                 public override Uri RequestUrl {
64                         get { return ctx.Request.Url; }
65                 }
66                 public override string HttpMethod {
67                         get { return ctx.Request.HttpMethod; }
68                 }
69                 public override void Abort ()
70                 {
71                         ctx.Response.Abort ();
72                 }
73         }
74
75         class AspNetHttpContextInfo : HttpContextInfo
76         {
77                 public AspNetHttpContextInfo (HttpContext ctx)
78                 {
79                         this.ctx = ctx;
80                 }
81                 
82                 HttpContext ctx;
83
84                 public HttpContext Source {
85                         get { return ctx; }
86                 }
87
88                 public override NameValueCollection QueryString {
89                         get { return ctx.Request.QueryString; }
90                 }
91                 public override Uri RequestUrl {
92                         get { return ctx.Request.Url; }
93                 }
94                 public override string HttpMethod {
95                         get { return ctx.Request.HttpMethod; }
96                 }
97
98                 public override void Abort ()
99                 {
100                         ctx.Response.Close ();
101                 }
102         }
103
104         internal class HttpSimpleListenerManager : HttpListenerManager
105         {
106                 static Dictionary<Uri, HttpListener> opened_listeners;
107                 HttpListener http_listener;
108
109                 static HttpSimpleListenerManager ()
110                 {
111                         opened_listeners = new Dictionary<Uri, HttpListener> ();
112                 }
113
114                 public HttpSimpleListenerManager (IChannelListener channelListener, HttpTransportBindingElement source)
115                         : base (channelListener, source)
116                 {
117                 }
118
119                 protected override void OnRegister (IChannelListener channelListener, TimeSpan timeout)
120                 {
121                         lock (opened_listeners) {
122                                 if (!opened_listeners.ContainsKey (channelListener.Uri)) {
123                                         HttpListener listener = new HttpListener ();
124                                         listener.AuthenticationSchemes = Source.AuthenticationScheme;
125                                         listener.Realm = Source.Realm;
126                                         listener.UnsafeConnectionNtlmAuthentication = Source.UnsafeConnectionNtlmAuthentication;
127
128                                         string uriString = channelListener.Uri.ToString ();
129                                         if (!uriString.EndsWith ("/", StringComparison.Ordinal))
130                                                 uriString += "/";
131                                         listener.Prefixes.Add (uriString);
132                                         listener.Start ();
133
134                                         opened_listeners [channelListener.Uri] = listener;
135                                 }
136
137                                 http_listener = opened_listeners [channelListener.Uri];
138                         }
139                 }
140
141                 protected override void OnUnregister (IChannelListener listener, bool abort)
142                 {
143                         lock (opened_listeners) {
144                                 if (http_listener == null)
145                                         return;
146                                 if (http_listener.IsListening) {
147                                         if (abort)
148                                                 http_listener.Abort ();
149                                         else
150                                                 http_listener.Close ();
151                                 }
152                                 ((IDisposable) http_listener).Dispose ();
153
154                                 opened_listeners.Remove (listener.Uri);
155                         }
156
157                         http_listener = null;
158                 }
159
160                 public override void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceivedCallback)
161                 {
162                         http_listener.BeginGetContext (delegate (IAsyncResult result) {
163                                 var hctx = http_listener.EndGetContext (result);
164                                 contextReceivedCallback (new HttpListenerContextInfo (hctx));
165                         }, null);
166                 }
167         }
168
169         internal class AspNetListenerManager : HttpListenerManager
170         {
171                 SvcHttpHandler http_handler;
172
173                 public AspNetListenerManager (IChannelListener channelListener, HttpTransportBindingElement source)
174                         : base (channelListener, source)
175                 {
176                         http_handler = SvcHttpHandlerFactory.GetHandlerForListener (channelListener);
177                 }
178
179                 public SvcHttpHandler Source {
180                         get { return http_handler; }
181                 }
182
183                 protected override void OnRegister (IChannelListener channelListener, TimeSpan timeout)
184                 {
185                         http_handler.RegisterListener (channelListener);
186                 }
187
188                 protected override void OnUnregister (IChannelListener listener, bool abort)
189                 {
190                         http_handler.UnregisterListener (listener);
191                 }
192
193                 Func<IChannelListener,HttpContext> wait_delegate;
194
195                 public override void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceivedCallback)
196                 {
197                         if (wait_delegate == null)
198                                 wait_delegate = new Func<IChannelListener,HttpContext> (http_handler.WaitForRequest);
199                         wait_delegate.BeginInvoke (listener, delegate (IAsyncResult result) {
200                                 var ctx = wait_delegate.EndInvoke (result);
201                                 contextReceivedCallback (ctx != null ? new AspNetHttpContextInfo (ctx) : null);
202                                 }, null);
203                 }
204         }
205
206         internal abstract class HttpListenerManager
207         {
208                 static Dictionary<Uri, List<IChannelListener>> registered_channels;
209                 IChannelListener channel_listener;
210                 MetadataPublishingInfo mex_info;
211                 HttpGetWsdl wsdl_instance;
212                 AutoResetEvent wait_http_ctx = new AutoResetEvent (false);
213                 List<HttpContextInfo> pending = new List<HttpContextInfo> ();
214
215                 public MetadataPublishingInfo MexInfo { get { return mex_info; } }
216                 public HttpTransportBindingElement Source { get; private set; }
217
218                 static HttpListenerManager ()
219                 {
220                         registered_channels = new Dictionary<Uri, List<IChannelListener>> ();
221                 }
222
223                 protected HttpListenerManager (IChannelListener channelListener, HttpTransportBindingElement source)
224                 {
225                         this.channel_listener = channelListener;
226                         // FIXME: this cast should not be required, but current JIT somehow causes an internal error.
227                         mex_info = ((IChannelListener) channelListener).GetProperty<MetadataPublishingInfo> ();
228                         wsdl_instance = mex_info != null ? mex_info.Instance : null;
229                         Source = source;
230                 }
231
232                 public void Open (TimeSpan timeout)
233                 {
234                         if (!registered_channels.ContainsKey (channel_listener.Uri))
235                                 registered_channels [channel_listener.Uri] = new List<IChannelListener> ();
236                         OnRegister (channel_listener, timeout);
237                         registered_channels [channel_listener.Uri].Add (channel_listener);
238
239                         // make sure to fill wsdl_instance among other 
240                         // listeners. It is somewhat hacky way, but 
241                         // otherwise there is no assured way to do it.
242                         var wsdl = wsdl_instance;
243                         if (wsdl == null)
244                                 foreach (var l in registered_channels [channel_listener.Uri])
245                                         if ((wsdl = l.GetProperty<HttpListenerManager> ().wsdl_instance) != null)
246                                                 break;
247                         if (wsdl != null) {
248                                 foreach (var l in registered_channels [channel_listener.Uri])
249                                         l.GetProperty<HttpListenerManager> ().wsdl_instance = wsdl;
250                         }
251                 }
252
253                 public void Stop (bool abort)
254                 {
255                         List<IChannelListener> channelsList = registered_channels [channel_listener.Uri];
256                         channelsList.Remove (channel_listener);
257
258                         try {
259                                 foreach (var ctx in pending)
260                                         ctx.Abort ();
261                         } catch (Exception ex) {
262                                 // FIXME: log it
263                                 Console.WriteLine ("error during HTTP channel listener shutdown: " + ex);
264                         }
265
266                         if (channelsList.Count == 0)
267                                 OnUnregister (channel_listener, abort);
268                 }
269
270                 protected abstract void OnRegister (IChannelListener listener, TimeSpan timeout);
271                 protected abstract void OnUnregister (IChannelListener listener, bool abort);
272
273                 public void CancelGetHttpContextAsync ()
274                 {
275                         wait_http_ctx.Set ();
276                 }
277
278                 // Do not directly handle retrieved HttpListenerContexts when
279                 // the listener received ones.
280                 // Instead, iterate every listeners to find the most-likely-
281                 // matching one and immediately handle the listener context.
282                 // If the listener is not requesting a context right now, then
283                 // store it in *each* listener's queue.
284
285                 public void GetHttpContextAsync (TimeSpan timeout, Action<HttpContextInfo> callback)
286                 {
287                         lock (pending) {
288                                 foreach (var pctx in pending) {
289                                         if (FilterHttpContext (pctx)) {
290                                                 callback (pctx);
291                                                 return;
292                                         }
293                                 }
294                         }
295                         KickContextReceiver (channel_listener, DispatchHttpListenerContext);
296                         wait_http_ctx.WaitOne (timeout);
297                         lock (pending) {
298                                 HttpContextInfo ctx = pending.Count > 0 ? pending [0] : null;
299                                 if (ctx != null)
300                                         pending.Remove (ctx);
301                                 callback (ctx);
302                         }
303                 }
304
305                 public abstract void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceiverCallback);
306
307                 void DispatchHttpListenerContext (HttpContextInfo ctx)
308                 {
309                         if (wsdl_instance == null) {
310                                 pending.Add (ctx);
311                                 wait_http_ctx.Set ();
312                                 return;
313                         }
314                         foreach (var l in registered_channels [channel_listener.Uri]) {
315                                 var lm = l.GetProperty<HttpListenerManager> ();
316                                 if (lm.FilterHttpContext (ctx)) {
317                                         lm.pending.Add (ctx);
318                                         lm.wait_http_ctx.Set ();
319                                         return;
320                                 }
321                         }
322                         pending.Add (ctx);
323                         wait_http_ctx.Set ();
324                 }
325
326                 const UriComponents cmpflag = UriComponents.HttpRequestUrl ^ UriComponents.Query;
327                 const UriFormat fmtflag = UriFormat.SafeUnescaped;
328
329                 internal bool FilterHttpContext (HttpContextInfo ctx)
330                 {
331                         if (ctx.HttpMethod.ToUpper () != "GET")
332                                 return mex_info == null;
333
334                         if (wsdl_instance == null)
335                                 return true;
336                         if (channel_listener.State != CommunicationState.Opened)
337                                 return true;
338
339                         if (wsdl_instance.WsdlUrl != null && Uri.Compare (ctx.RequestUrl, wsdl_instance.WsdlUrl, cmpflag, fmtflag, StringComparison.Ordinal) == 0) {
340                                 if (mex_info == null)
341                                         return false; // Do not handle this at normal dispatcher.
342                                 if (ctx.QueryString [null] == "wsdl")
343                                         return mex_info.SupportsMex; // wsdl dispatcher should handle this.
344                                 if (!wsdl_instance.HelpUrl.Equals (wsdl_instance.WsdlUrl))
345                                         return true; // in case help URL is not equivalent to WSDL URL, it anyways returns WSDL regardless of ?wsdl existence.
346                         }
347                         if (wsdl_instance.HelpUrl != null && Uri.Compare (ctx.RequestUrl, wsdl_instance.HelpUrl, cmpflag, fmtflag, StringComparison.Ordinal) == 0) {
348                                 // Do not handle this at normal dispatcher.
349                                 // Do return true otherwise, even if it is with "?wsdl".
350                                 // (It must be handled above if applicable.)
351                                 return mex_info != null;
352                         }
353
354                         return mex_info == null;
355                 }
356         }
357 }