2 // HttpListenerManager.cs
5 // Vladimir Krasnov <vladimirk@mainsoft.com>
6 // Atsushi Enomoto <atsushi@ximian.com>
8 // Copyright (C) 2005-2006 Mainsoft, Inc. http://www.mainsoft.com
9 // Copyright (C) 2009-2010 Novell, Inc. http://www.novell.com
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Collections.Generic;
32 using System.Collections.Specialized;
33 using System.IdentityModel.Selectors;
34 using System.IdentityModel.Tokens;
35 using System.ServiceModel.Description;
36 using System.ServiceModel.Security;
38 using System.Threading;
42 namespace System.ServiceModel.Channels
44 abstract class HttpContextInfo
46 public abstract NameValueCollection QueryString { get; }
47 public abstract Uri RequestUrl { get; }
48 public abstract string HttpMethod { get; }
49 public abstract void Abort ();
51 public abstract string User { get; }
52 public abstract string Password { get; }
53 public abstract void ReturnUnauthorized ();
56 class HttpListenerContextInfo : HttpContextInfo
58 public HttpListenerContextInfo (HttpListenerContext ctx)
63 HttpListenerContext ctx;
65 public HttpListenerContext Source {
69 public override NameValueCollection QueryString {
70 get { return ctx.Request.QueryString; }
72 public override Uri RequestUrl {
73 get { return ctx.Request.Url; }
75 public override string HttpMethod {
76 get { return ctx.Request.HttpMethod; }
78 public override void Abort ()
80 ctx.Response.Abort ();
83 public override string User {
84 get { return ctx.User != null ? ((HttpListenerBasicIdentity) ctx.User.Identity).Name : null; }
87 public override string Password {
88 get { return ctx.User != null ? ((HttpListenerBasicIdentity) ctx.User.Identity).Password : null; }
91 public override void ReturnUnauthorized ()
93 ctx.Response.StatusCode = 401;
97 class AspNetHttpContextInfo : HttpContextInfo
99 public AspNetHttpContextInfo (HttpContext ctx)
106 public HttpContext Source {
110 public override NameValueCollection QueryString {
111 get { return ctx.Request.QueryString; }
113 public override Uri RequestUrl {
114 get { return ctx.Request.Url; }
116 public override string HttpMethod {
117 get { return ctx.Request.HttpMethod; }
120 public override void Abort ()
122 ctx.Response.Close ();
125 public override string User {
126 get { return ctx.User != null ? ((HttpListenerBasicIdentity) ctx.User.Identity).Name : null; }
129 // FIXME: how to acquire this?
130 public override string Password {
131 get { throw new NotImplementedException (); }
134 public override void ReturnUnauthorized ()
136 ctx.Response.StatusCode = 401;
140 internal class HttpSimpleListenerManager : HttpListenerManager
142 static Dictionary<Uri, HttpListener> opened_listeners;
143 HttpListener http_listener;
145 static HttpSimpleListenerManager ()
147 opened_listeners = new Dictionary<Uri, HttpListener> ();
150 public HttpSimpleListenerManager (IChannelListener channelListener, HttpTransportBindingElement source, ServiceCredentialsSecurityTokenManager securityTokenManager)
151 : base (channelListener, source, securityTokenManager)
155 protected override void OnRegister (IChannelListener channelListener, TimeSpan timeout)
157 lock (opened_listeners) {
158 if (!opened_listeners.ContainsKey (channelListener.Uri)) {
159 HttpListener listener = new HttpListener ();
160 listener.AuthenticationSchemeSelectorDelegate = delegate (HttpListenerRequest req) {
161 return Source.AuthenticationScheme;
163 listener.Realm = Source.Realm;
164 listener.UnsafeConnectionNtlmAuthentication = Source.UnsafeConnectionNtlmAuthentication;
166 string uriString = channelListener.Uri.ToString ();
167 if (!uriString.EndsWith ("/", StringComparison.Ordinal))
169 listener.Prefixes.Add (uriString);
172 opened_listeners [channelListener.Uri] = listener;
175 http_listener = opened_listeners [channelListener.Uri];
179 protected override void OnUnregister (IChannelListener listener, bool abort)
181 lock (opened_listeners) {
182 if (http_listener == null)
184 if (http_listener.IsListening) {
186 http_listener.Abort ();
188 http_listener.Close ();
190 ((IDisposable) http_listener).Dispose ();
192 opened_listeners.Remove (listener.Uri);
195 http_listener = null;
198 protected override void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceivedCallback)
200 http_listener.BeginGetContext (delegate (IAsyncResult result) {
201 var hctx = http_listener.EndGetContext (result);
202 contextReceivedCallback (new HttpListenerContextInfo (hctx));
207 internal class AspNetListenerManager : HttpListenerManager
209 SvcHttpHandler http_handler;
211 public AspNetListenerManager (IChannelListener channelListener, HttpTransportBindingElement source, ServiceCredentialsSecurityTokenManager securityTokenManager)
212 : base (channelListener, source, securityTokenManager)
214 http_handler = SvcHttpHandlerFactory.GetHandlerForListener (channelListener);
217 public SvcHttpHandler Source {
218 get { return http_handler; }
221 protected override void OnRegister (IChannelListener channelListener, TimeSpan timeout)
223 http_handler.RegisterListener (channelListener);
226 protected override void OnUnregister (IChannelListener listener, bool abort)
228 http_handler.UnregisterListener (listener);
231 Func<IChannelListener,HttpContext> wait_delegate;
233 protected override void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceivedCallback)
235 if (wait_delegate == null)
236 wait_delegate = new Func<IChannelListener,HttpContext> (http_handler.WaitForRequest);
237 wait_delegate.BeginInvoke (listener, delegate (IAsyncResult result) {
238 var ctx = wait_delegate.EndInvoke (result);
239 contextReceivedCallback (ctx != null ? new AspNetHttpContextInfo (ctx) : null);
244 internal abstract class HttpListenerManager
246 static Dictionary<Uri, List<IChannelListener>> registered_channels;
247 IChannelListener channel_listener;
248 MetadataPublishingInfo mex_info;
249 HttpGetWsdl wsdl_instance;
250 AutoResetEvent wait_http_ctx = new AutoResetEvent (false);
251 List<HttpContextInfo> pending = new List<HttpContextInfo> ();
253 public MetadataPublishingInfo MexInfo { get { return mex_info; } }
254 public HttpTransportBindingElement Source { get; private set; }
256 SecurityTokenAuthenticator security_token_authenticator;
257 SecurityTokenResolver security_token_resolver;
259 static HttpListenerManager ()
261 registered_channels = new Dictionary<Uri, List<IChannelListener>> ();
264 protected HttpListenerManager (IChannelListener channelListener, HttpTransportBindingElement source, ServiceCredentialsSecurityTokenManager securityTokenManager)
266 this.channel_listener = channelListener;
267 // FIXME: this cast should not be required, but current JIT somehow causes an internal error.
268 mex_info = ((IChannelListener) channelListener).GetProperty<MetadataPublishingInfo> ();
269 wsdl_instance = mex_info != null ? mex_info.Instance : null;
272 if (securityTokenManager != null) {
273 var str = new SecurityTokenRequirement () { TokenType = SecurityTokenTypes.UserName };
274 security_token_authenticator = securityTokenManager.CreateSecurityTokenAuthenticator (str, out security_token_resolver);
278 public void Open (TimeSpan timeout)
280 if (!registered_channels.ContainsKey (channel_listener.Uri))
281 registered_channels [channel_listener.Uri] = new List<IChannelListener> ();
282 OnRegister (channel_listener, timeout);
283 registered_channels [channel_listener.Uri].Add (channel_listener);
285 // make sure to fill wsdl_instance among other
286 // listeners. It is somewhat hacky way, but
287 // otherwise there is no assured way to do it.
288 var wsdl = wsdl_instance;
290 foreach (var l in registered_channels [channel_listener.Uri])
291 if ((wsdl = l.GetProperty<HttpListenerManager> ().wsdl_instance) != null)
294 foreach (var l in registered_channels [channel_listener.Uri])
295 l.GetProperty<HttpListenerManager> ().wsdl_instance = wsdl;
299 public void Stop (bool abort)
301 List<IChannelListener> channelsList = registered_channels [channel_listener.Uri];
302 channelsList.Remove (channel_listener);
305 foreach (var ctx in pending)
307 } catch (Exception ex) {
309 Console.WriteLine ("error during HTTP channel listener shutdown: " + ex);
312 if (channelsList.Count == 0)
313 OnUnregister (channel_listener, abort);
316 protected abstract void OnRegister (IChannelListener listener, TimeSpan timeout);
317 protected abstract void OnUnregister (IChannelListener listener, bool abort);
319 public void CancelGetHttpContextAsync ()
321 wait_http_ctx.Set ();
324 // Do not directly handle retrieved HttpListenerContexts when
325 // the listener received ones.
326 // Instead, iterate every listeners to find the most-likely-
327 // matching one and immediately handle the listener context.
328 // If the listener is not requesting a context right now, then
329 // store it in *each* listener's queue.
331 public void GetHttpContextAsync (TimeSpan timeout, Action<HttpContextInfo> callback)
334 foreach (var pctx in pending) {
335 if (FilterHttpContext (pctx)) {
341 KickContextReceiver (channel_listener, DispatchHttpListenerContext);
342 wait_http_ctx.WaitOne (timeout);
344 HttpContextInfo ctx = pending.Count > 0 ? pending [0] : null;
346 pending.Remove (ctx);
351 protected abstract void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceiverCallback);
353 void DispatchHttpListenerContext (HttpContextInfo ctx)
355 if (wsdl_instance == null) {
356 AddListenerContext (ctx);
359 foreach (var l in registered_channels [channel_listener.Uri]) {
360 var lm = l.GetProperty<HttpListenerManager> ();
361 if (lm.FilterHttpContext (ctx)) {
362 lm.AddListenerContext (ctx);
366 AddListenerContext (ctx);
369 void AddListenerContext (HttpContextInfo ctx)
371 if (Source.AuthenticationScheme != AuthenticationSchemes.Anonymous) {
372 if (security_token_authenticator != null)
373 // FIXME: use return value?
375 security_token_authenticator.ValidateToken (new UserNameSecurityToken (ctx.User, ctx.Password));
376 } catch (Exception) {
377 ctx.ReturnUnauthorized ();
380 ctx.ReturnUnauthorized ();
384 lock (registered_channels) {
386 // FIXME: this should not be required, but it somehow saves some failures wrt concurrent calls.
388 wait_http_ctx.Set ();
392 const UriComponents cmpflag = UriComponents.HttpRequestUrl ^ UriComponents.Query;
393 const UriFormat fmtflag = UriFormat.SafeUnescaped;
395 internal bool FilterHttpContext (HttpContextInfo ctx)
397 if (ctx.HttpMethod.ToUpper () != "GET")
398 return mex_info == null;
400 if (wsdl_instance == null)
402 if (channel_listener.State != CommunicationState.Opened)
405 if (wsdl_instance.WsdlUrl != null && Uri.Compare (ctx.RequestUrl, wsdl_instance.WsdlUrl, cmpflag, fmtflag, StringComparison.Ordinal) == 0) {
406 if (mex_info == null)
407 return false; // Do not handle this at normal dispatcher.
408 if (ctx.QueryString [null] == "wsdl")
409 return mex_info.SupportsMex; // wsdl dispatcher should handle this.
410 if (!wsdl_instance.HelpUrl.Equals (wsdl_instance.WsdlUrl))
411 return true; // in case help URL is not equivalent to WSDL URL, it anyways returns WSDL regardless of ?wsdl existence.
413 if (wsdl_instance.HelpUrl != null && Uri.Compare (ctx.RequestUrl, wsdl_instance.HelpUrl, cmpflag, fmtflag, StringComparison.Ordinal) == 0) {
414 // Do not handle this at normal dispatcher.
415 // Do return true otherwise, even if it is with "?wsdl".
416 // (It must be handled above if applicable.)
417 return mex_info != null;
420 return mex_info == null;