2 // HttpListenerManager.cs
5 // Vladimir Krasnov <vladimirk@mainsoft.com>
7 // Copyright (C) 2005-2006 Mainsoft, Inc. http://www.mainsoft.com
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
29 using System.Collections.Generic;
30 using System.Collections.Specialized;
31 using System.ServiceModel.Description;
33 using System.Threading;
37 namespace System.ServiceModel.Channels
39 abstract class HttpContextInfo
41 public abstract NameValueCollection QueryString { get; }
42 public abstract Uri RequestUrl { get; }
43 public abstract string HttpMethod { get; }
44 public abstract void Abort ();
47 class HttpListenerContextInfo : HttpContextInfo
49 public HttpListenerContextInfo (HttpListenerContext ctx)
54 HttpListenerContext ctx;
56 public HttpListenerContext Source {
60 public override NameValueCollection QueryString {
61 get { return ctx.Request.QueryString; }
63 public override Uri RequestUrl {
64 get { return ctx.Request.Url; }
66 public override string HttpMethod {
67 get { return ctx.Request.HttpMethod; }
69 public override void Abort ()
71 ctx.Response.Abort ();
75 class AspNetHttpContextInfo : HttpContextInfo
77 public AspNetHttpContextInfo (HttpContext ctx)
84 public HttpContext Source {
88 public override NameValueCollection QueryString {
89 get { return ctx.Request.QueryString; }
91 public override Uri RequestUrl {
92 get { return ctx.Request.Url; }
94 public override string HttpMethod {
95 get { return ctx.Request.HttpMethod; }
98 public override void Abort ()
100 ctx.Response.Close ();
104 internal class HttpSimpleListenerManager : HttpListenerManager
106 static Dictionary<Uri, HttpListener> opened_listeners;
107 HttpListener http_listener;
109 static HttpSimpleListenerManager ()
111 opened_listeners = new Dictionary<Uri, HttpListener> ();
114 public HttpSimpleListenerManager (IChannelListener channelListener, HttpTransportBindingElement source)
115 : base (channelListener, source)
119 protected override void OnRegister (IChannelListener channelListener, TimeSpan timeout)
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;
128 string uriString = channelListener.Uri.ToString ();
129 if (!uriString.EndsWith ("/", StringComparison.Ordinal))
131 listener.Prefixes.Add (uriString);
134 opened_listeners [channelListener.Uri] = listener;
137 http_listener = opened_listeners [channelListener.Uri];
141 protected override void OnUnregister (IChannelListener listener, bool abort)
143 lock (opened_listeners) {
144 if (http_listener == null)
146 if (http_listener.IsListening) {
148 http_listener.Abort ();
150 http_listener.Close ();
152 ((IDisposable) http_listener).Dispose ();
154 opened_listeners.Remove (listener.Uri);
157 http_listener = null;
160 public override void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceivedCallback)
162 http_listener.BeginGetContext (delegate (IAsyncResult result) {
163 var hctx = http_listener.EndGetContext (result);
164 contextReceivedCallback (new HttpListenerContextInfo (hctx));
169 internal class AspNetListenerManager : HttpListenerManager
171 SvcHttpHandler http_handler;
173 public AspNetListenerManager (IChannelListener channelListener, HttpTransportBindingElement source)
174 : base (channelListener, source)
176 http_handler = SvcHttpHandlerFactory.GetHandlerForListener (channelListener);
179 public SvcHttpHandler Source {
180 get { return http_handler; }
183 protected override void OnRegister (IChannelListener channelListener, TimeSpan timeout)
185 http_handler.RegisterListener (channelListener);
188 protected override void OnUnregister (IChannelListener listener, bool abort)
190 http_handler.UnregisterListener (listener);
193 Func<IChannelListener,HttpContext> wait_delegate;
195 public override void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceivedCallback)
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);
206 internal abstract class HttpListenerManager
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> ();
215 public MetadataPublishingInfo MexInfo { get { return mex_info; } }
216 public HttpTransportBindingElement Source { get; private set; }
218 static HttpListenerManager ()
220 registered_channels = new Dictionary<Uri, List<IChannelListener>> ();
223 protected HttpListenerManager (IChannelListener channelListener, HttpTransportBindingElement source)
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;
232 public void Open (TimeSpan timeout)
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);
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;
244 foreach (var l in registered_channels [channel_listener.Uri])
245 if ((wsdl = l.GetProperty<HttpListenerManager> ().wsdl_instance) != null)
248 foreach (var l in registered_channels [channel_listener.Uri])
249 l.GetProperty<HttpListenerManager> ().wsdl_instance = wsdl;
253 public void Stop (bool abort)
255 List<IChannelListener> channelsList = registered_channels [channel_listener.Uri];
256 channelsList.Remove (channel_listener);
259 foreach (var ctx in pending)
261 } catch (Exception ex) {
263 Console.WriteLine ("error during HTTP channel listener shutdown: " + ex);
266 if (channelsList.Count == 0)
267 OnUnregister (channel_listener, abort);
270 protected abstract void OnRegister (IChannelListener listener, TimeSpan timeout);
271 protected abstract void OnUnregister (IChannelListener listener, bool abort);
273 public void CancelGetHttpContextAsync ()
275 wait_http_ctx.Set ();
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.
285 public void GetHttpContextAsync (TimeSpan timeout, Action<HttpContextInfo> callback)
288 foreach (var pctx in pending) {
289 if (FilterHttpContext (pctx)) {
295 KickContextReceiver (channel_listener, DispatchHttpListenerContext);
296 wait_http_ctx.WaitOne (timeout);
298 HttpContextInfo ctx = pending.Count > 0 ? pending [0] : null;
300 pending.Remove (ctx);
305 public abstract void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceiverCallback);
307 void DispatchHttpListenerContext (HttpContextInfo ctx)
309 if (wsdl_instance == null) {
311 wait_http_ctx.Set ();
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 ();
323 wait_http_ctx.Set ();
326 const UriComponents cmpflag = UriComponents.HttpRequestUrl ^ UriComponents.Query;
327 const UriFormat fmtflag = UriFormat.SafeUnescaped;
329 internal bool FilterHttpContext (HttpContextInfo ctx)
331 if (ctx.HttpMethod.ToUpper () != "GET")
332 return mex_info == null;
334 if (wsdl_instance == null)
336 if (channel_listener.State != CommunicationState.Opened)
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.
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;
354 return mex_info == null;