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)
115 : base (channelListener)
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 ();
125 string uriString = channelListener.Uri.ToString ();
126 if (!uriString.EndsWith ("/", StringComparison.Ordinal))
128 listener.Prefixes.Add (uriString);
131 opened_listeners [channelListener.Uri] = listener;
134 http_listener = opened_listeners [channelListener.Uri];
138 protected override void OnUnregister (IChannelListener listener, bool abort)
140 lock (opened_listeners) {
141 if (http_listener == null)
143 if (http_listener.IsListening) {
145 http_listener.Abort ();
147 http_listener.Close ();
149 ((IDisposable) http_listener).Dispose ();
151 opened_listeners.Remove (listener.Uri);
154 http_listener = null;
157 public override void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceivedCallback)
159 http_listener.BeginGetContext (delegate (IAsyncResult result) {
160 var hctx = http_listener.EndGetContext (result);
161 contextReceivedCallback (new HttpListenerContextInfo (hctx));
166 internal class AspNetListenerManager : HttpListenerManager
168 SvcHttpHandler http_handler;
170 public AspNetListenerManager (IChannelListener channelListener)
171 : base (channelListener)
173 http_handler = SvcHttpHandlerFactory.GetHandlerForListener (channelListener);
176 public SvcHttpHandler Source {
177 get { return http_handler; }
180 protected override void OnRegister (IChannelListener channelListener, TimeSpan timeout)
182 http_handler.RegisterListener (channelListener);
185 protected override void OnUnregister (IChannelListener listener, bool abort)
187 http_handler.UnregisterListener (listener);
190 Func<IChannelListener,HttpContext> wait_delegate;
192 public override void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceivedCallback)
194 if (wait_delegate == null)
195 wait_delegate = new Func<IChannelListener,HttpContext> (http_handler.WaitForRequest);
196 wait_delegate.BeginInvoke (listener, delegate (IAsyncResult result) {
197 var ctx = wait_delegate.EndInvoke (result);
198 contextReceivedCallback (ctx != null ? new AspNetHttpContextInfo (ctx) : null);
203 internal abstract class HttpListenerManager
205 static Dictionary<Uri, List<IChannelListener>> registered_channels;
206 IChannelListener channel_listener;
207 MetadataPublishingInfo mex_info;
208 HttpGetWsdl wsdl_instance;
209 AutoResetEvent wait_http_ctx = new AutoResetEvent (false);
210 List<HttpContextInfo> pending = new List<HttpContextInfo> ();
212 public MetadataPublishingInfo MexInfo { get { return mex_info; } }
214 static HttpListenerManager ()
216 registered_channels = new Dictionary<Uri, List<IChannelListener>> ();
219 protected HttpListenerManager (IChannelListener channelListener)
221 this.channel_listener = channelListener;
222 // FIXME: this cast should not be required, but current JIT somehow causes an internal error.
223 mex_info = ((IChannelListener) channelListener).GetProperty<MetadataPublishingInfo> ();
224 wsdl_instance = mex_info != null ? mex_info.Instance : null;
227 public void Open (TimeSpan timeout)
229 if (!registered_channels.ContainsKey (channel_listener.Uri))
230 registered_channels [channel_listener.Uri] = new List<IChannelListener> ();
231 OnRegister (channel_listener, timeout);
232 registered_channels [channel_listener.Uri].Add (channel_listener);
234 // make sure to fill wsdl_instance among other
235 // listeners. It is somewhat hacky way, but
236 // otherwise there is no assured way to do it.
237 var wsdl = wsdl_instance;
239 foreach (var l in registered_channels [channel_listener.Uri])
240 if ((wsdl = l.GetProperty<HttpListenerManager> ().wsdl_instance) != null)
243 foreach (var l in registered_channels [channel_listener.Uri])
244 l.GetProperty<HttpListenerManager> ().wsdl_instance = wsdl;
248 public void Stop (bool abort)
250 List<IChannelListener> channelsList = registered_channels [channel_listener.Uri];
251 channelsList.Remove (channel_listener);
254 foreach (var ctx in pending)
256 } catch (Exception ex) {
258 Console.WriteLine ("error during HTTP channel listener shutdown: " + ex);
261 if (channelsList.Count == 0)
262 OnUnregister (channel_listener, abort);
265 protected abstract void OnRegister (IChannelListener listener, TimeSpan timeout);
266 protected abstract void OnUnregister (IChannelListener listener, bool abort);
268 // Do not directly handle retrieved HttpListenerContexts when
269 // the listener received ones.
270 // Instead, iterate every listeners to find the most-likely-
271 // matching one and immediately handle the listener context.
272 // If the listener is not requesting a context right now, then
273 // store it in *each* listener's queue.
275 public void GetHttpContextAsync (TimeSpan timeout, Action<HttpContextInfo> callback)
278 foreach (var pctx in pending) {
279 if (FilterHttpContext (pctx)) {
285 KickContextReceiver (channel_listener, DispatchHttpListenerContext);
286 wait_http_ctx.WaitOne (timeout);
288 HttpContextInfo ctx = pending.Count > 0 ? pending [0] : null;
290 pending.Remove (ctx);
295 public abstract void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceiverCallback);
297 void DispatchHttpListenerContext (HttpContextInfo ctx)
299 if (wsdl_instance == null) {
301 wait_http_ctx.Set ();
304 foreach (var l in registered_channels [channel_listener.Uri]) {
305 var lm = l.GetProperty<HttpListenerManager> ();
306 if (lm.FilterHttpContext (ctx)) {
307 lm.pending.Add (ctx);
308 lm.wait_http_ctx.Set ();
313 wait_http_ctx.Set ();
316 const UriComponents cmpflag = UriComponents.HttpRequestUrl ^ UriComponents.Query;
317 const UriFormat fmtflag = UriFormat.SafeUnescaped;
319 internal bool FilterHttpContext (HttpContextInfo ctx)
321 if (ctx.HttpMethod.ToUpper () != "GET")
322 return mex_info == null;
324 if (wsdl_instance == null)
326 if (channel_listener.State != CommunicationState.Opened)
329 if (wsdl_instance.WsdlUrl != null && Uri.Compare (ctx.RequestUrl, wsdl_instance.WsdlUrl, cmpflag, fmtflag, StringComparison.Ordinal) == 0) {
330 if (mex_info == null)
331 return false; // Do not handle this at normal dispatcher.
332 if (ctx.QueryString [null] == "wsdl")
333 return mex_info.SupportsMex; // wsdl dispatcher should handle this.
334 if (!wsdl_instance.HelpUrl.Equals (wsdl_instance.WsdlUrl))
335 return true; // in case help URL is not equivalent to WSDL URL, it anyways returns WSDL regardless of ?wsdl existence.
337 if (wsdl_instance.HelpUrl != null && Uri.Compare (ctx.RequestUrl, wsdl_instance.HelpUrl, cmpflag, fmtflag, StringComparison.Ordinal) == 0) {
338 // Do not handle this at normal dispatcher.
339 // Do return true otherwise, even if it is with "?wsdl".
340 // (It must be handled above if applicable.)
341 return mex_info != null;
344 return mex_info == null;