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.Dispatcher;
37 using System.ServiceModel.Security;
38 using System.Security.Principal;
40 using System.Threading;
44 namespace System.ServiceModel.Channels
46 abstract class HttpContextInfo
48 public abstract NameValueCollection QueryString { get; }
49 public abstract Uri RequestUrl { get; }
50 public abstract string HttpMethod { get; }
51 public abstract void Abort ();
53 public abstract string User { get; }
54 public abstract string Password { get; }
55 public abstract void ReturnUnauthorized ();
58 class HttpListenerContextInfo : HttpContextInfo
60 public HttpListenerContextInfo (HttpListenerContext ctx)
65 HttpListenerContext ctx;
67 public HttpListenerContext Source {
71 public override NameValueCollection QueryString {
72 get { return ctx.Request.QueryString; }
74 public override Uri RequestUrl {
75 get { return ctx.Request.Url; }
77 public override string HttpMethod {
78 get { return ctx.Request.HttpMethod; }
80 public override void Abort ()
82 ctx.Response.Abort ();
85 public override string User {
86 get { return ctx.User != null ? ((HttpListenerBasicIdentity) ctx.User.Identity).Name : null; }
89 public override string Password {
90 get { return ctx.User != null ? ((HttpListenerBasicIdentity) ctx.User.Identity).Password : null; }
93 public override void ReturnUnauthorized ()
95 ctx.Response.StatusCode = 401;
99 class AspNetHttpContextInfo : HttpContextInfo
101 public AspNetHttpContextInfo (HttpContext ctx)
108 public HttpContext Source {
112 public override NameValueCollection QueryString {
113 get { return ctx.Request.QueryString; }
115 public override Uri RequestUrl {
116 get { return ctx.Request.Url; }
118 public override string HttpMethod {
119 get { return ctx.Request.HttpMethod; }
122 public override void Abort ()
124 ctx.Response.Close ();
127 public override string User {
128 get { return ctx.User != null ? ((GenericIdentity) ctx.User.Identity).Name : null; }
131 // FIXME: how to acquire this?
132 public override string Password {
136 public override void ReturnUnauthorized ()
138 ctx.Response.StatusCode = 401;
142 internal class HttpSimpleListenerManager : HttpListenerManager
144 static Dictionary<object,Dictionary<Uri,HttpListener>> http_listeners_table = new Dictionary<object,Dictionary<Uri,HttpListener>> ();
146 Dictionary<Uri, HttpListener> opened_listeners;
147 HttpListener http_listener;
149 public HttpSimpleListenerManager (IChannelListener channelListener, HttpTransportBindingElement source, ServiceCredentialsSecurityTokenManager securityTokenManager, ChannelDispatcher dispatcher)
150 : base (channelListener, source, securityTokenManager, dispatcher)
152 object key = dispatcher != null ? dispatcher.Host : new object (); // so that HttpChannelListener without ServiceHost is always assigned a new table.
153 if (!http_listeners_table.TryGetValue (key, out opened_listeners)) {
154 opened_listeners = new Dictionary<Uri, HttpListener> ();
155 http_listeners_table [key] = opened_listeners;
159 protected override void OnRegister (IChannelListener channelListener, TimeSpan timeout)
161 lock (opened_listeners) {
162 if (!opened_listeners.ContainsKey (channelListener.Uri)) {
163 HttpListener listener = new HttpListener ();
164 listener.AuthenticationSchemeSelectorDelegate = delegate (HttpListenerRequest req) {
165 return Source.AuthenticationScheme;
167 listener.Realm = Source.Realm;
168 listener.UnsafeConnectionNtlmAuthentication = Source.UnsafeConnectionNtlmAuthentication;
170 string uriString = channelListener.Uri.ToString ();
171 if (!uriString.EndsWith ("/", StringComparison.Ordinal))
173 listener.Prefixes.Add (uriString);
176 opened_listeners [channelListener.Uri] = listener;
179 http_listener = opened_listeners [channelListener.Uri];
183 protected override void OnUnregister (IChannelListener listener, bool abort)
185 lock (opened_listeners) {
186 if (http_listener == null)
188 if (http_listener.IsListening) {
190 http_listener.Abort ();
192 http_listener.Close ();
194 ((IDisposable) http_listener).Dispose ();
196 opened_listeners.Remove (listener.Uri);
199 http_listener = null;
202 protected override void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceivedCallback)
204 http_listener.BeginGetContext (delegate (IAsyncResult result) {
205 var hctx = http_listener.EndGetContext (result);
206 contextReceivedCallback (new HttpListenerContextInfo (hctx));
211 internal class AspNetListenerManager : HttpListenerManager
213 SvcHttpHandler http_handler;
215 public AspNetListenerManager (IChannelListener channelListener, HttpTransportBindingElement source, ServiceCredentialsSecurityTokenManager securityTokenManager, ChannelDispatcher dispatcher)
216 : base (channelListener, source, securityTokenManager, dispatcher)
218 http_handler = SvcHttpHandler.Current;
221 internal SvcHttpHandler HttpHandler { get { return http_handler; } }
223 protected override void OnRegister (IChannelListener channelListener, TimeSpan timeout)
225 http_handler.RegisterListener (channelListener);
228 protected override void OnUnregister (IChannelListener listener, bool abort)
230 http_handler.UnregisterListener (listener);
233 Func<IChannelListener,HttpContext> wait_delegate;
235 protected override void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceivedCallback)
237 if (wait_delegate == null)
238 wait_delegate = new Func<IChannelListener,HttpContext> (http_handler.WaitForRequest);
239 wait_delegate.BeginInvoke (listener, delegate (IAsyncResult result) {
240 var ctx = wait_delegate.EndInvoke (result);
241 contextReceivedCallback (ctx != null ? new AspNetHttpContextInfo (ctx) : null);
246 internal abstract class HttpListenerManager
248 static Dictionary<Uri, List<IChannelListener>> registered_channels;
249 IChannelListener channel_listener;
250 MetadataPublishingInfo mex_info;
251 HttpGetWsdl wsdl_instance;
252 AutoResetEvent wait_http_ctx = new AutoResetEvent (false);
253 List<HttpContextInfo> pending = new List<HttpContextInfo> ();
255 public MetadataPublishingInfo MexInfo { get { return mex_info; } }
256 public HttpTransportBindingElement Source { get; private set; }
257 public ChannelDispatcher Dispatcher { get; private set; }
259 SecurityTokenAuthenticator security_token_authenticator;
260 SecurityTokenResolver security_token_resolver;
262 static HttpListenerManager ()
264 registered_channels = new Dictionary<Uri, List<IChannelListener>> ();
267 protected HttpListenerManager (IChannelListener channelListener, HttpTransportBindingElement source, ServiceCredentialsSecurityTokenManager securityTokenManager, ChannelDispatcher dispatcher)
269 this.Dispatcher = dispatcher;
270 this.channel_listener = channelListener;
271 mex_info = Dispatcher.Listener.GetProperty<MetadataPublishingInfo> ();
272 wsdl_instance = mex_info != null ? mex_info.Instance : null;
275 if (securityTokenManager != null) {
276 var str = new SecurityTokenRequirement () { TokenType = SecurityTokenTypes.UserName };
277 security_token_authenticator = securityTokenManager.CreateSecurityTokenAuthenticator (str, out security_token_resolver);
281 public void Open (TimeSpan timeout)
283 if (!registered_channels.ContainsKey (channel_listener.Uri))
284 registered_channels [channel_listener.Uri] = new List<IChannelListener> ();
285 OnRegister (channel_listener, timeout);
286 registered_channels [channel_listener.Uri].Add (channel_listener);
288 // make sure to fill wsdl_instance among other
289 // listeners. It is somewhat hacky way, but
290 // otherwise there is no assured way to do it.
291 var wsdl = wsdl_instance;
293 foreach (var l in registered_channels [channel_listener.Uri])
294 if ((wsdl = l.GetProperty<HttpListenerManager> ().wsdl_instance) != null)
297 foreach (var l in registered_channels [channel_listener.Uri])
298 l.GetProperty<HttpListenerManager> ().wsdl_instance = wsdl;
302 public void Stop (bool abort)
304 List<IChannelListener> channelsList = registered_channels [channel_listener.Uri];
305 channelsList.Remove (channel_listener);
308 foreach (var ctx in pending)
310 } catch (Exception ex) {
312 Console.WriteLine ("error during HTTP channel listener shutdown: " + ex);
315 if (channelsList.Count == 0)
316 OnUnregister (channel_listener, abort);
319 protected abstract void OnRegister (IChannelListener listener, TimeSpan timeout);
320 protected abstract void OnUnregister (IChannelListener listener, bool abort);
322 public void CancelGetHttpContextAsync ()
324 wait_http_ctx.Set ();
327 // Do not directly handle retrieved HttpListenerContexts when
328 // the listener received ones.
329 // Instead, iterate every listeners to find the most-likely-
330 // matching one and immediately handle the listener context.
331 // If the listener is not requesting a context right now, then
332 // store it in *each* listener's queue.
334 public void GetHttpContextAsync (TimeSpan timeout, Action<HttpContextInfo> callback)
337 foreach (var pctx in pending) {
338 if (FilterHttpContext (pctx)) {
339 pending.Remove (pctx);
345 KickContextReceiver (channel_listener, DispatchHttpListenerContext);
346 wait_http_ctx.WaitOne (timeout);
348 HttpContextInfo ctx = pending.Count > 0 ? pending [0] : null;
350 pending.Remove (ctx);
355 protected abstract void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceiverCallback);
357 void DispatchHttpListenerContext (HttpContextInfo ctx)
359 if (wsdl_instance == null) {
360 AddListenerContext (ctx);
363 foreach (var l in registered_channels [channel_listener.Uri]) {
364 var lm = l.GetProperty<HttpListenerManager> ();
365 if (lm.FilterHttpContext (ctx)) {
366 lm.AddListenerContext (ctx);
370 AddListenerContext (ctx);
373 void AddListenerContext (HttpContextInfo ctx)
375 if (Source.AuthenticationScheme != AuthenticationSchemes.Anonymous) {
376 if (security_token_authenticator != null)
377 // FIXME: use return value?
379 security_token_authenticator.ValidateToken (new UserNameSecurityToken (ctx.User, ctx.Password));
380 } catch (Exception) {
381 ctx.ReturnUnauthorized ();
384 ctx.ReturnUnauthorized ();
390 // FIXME: this should not be required, but it somehow saves some failures wrt concurrent calls.
392 wait_http_ctx.Set ();
396 const UriComponents cmpflag = UriComponents.HttpRequestUrl ^ UriComponents.Query;
397 const UriFormat fmtflag = UriFormat.SafeUnescaped;
399 internal bool FilterHttpContext (HttpContextInfo ctx)
401 if (ctx.HttpMethod.ToUpper () != "GET")
402 return mex_info == null;
404 if (wsdl_instance == null)
406 if (channel_listener.State != CommunicationState.Opened)
409 if (wsdl_instance.WsdlUrl != null && Uri.Compare (ctx.RequestUrl, wsdl_instance.WsdlUrl, cmpflag, fmtflag, StringComparison.Ordinal) == 0) {
410 if (mex_info == null)
411 return false; // Do not handle this at normal dispatcher.
412 if (String.Compare (ctx.QueryString [null], "wsdl", StringComparison.OrdinalIgnoreCase) == 0)
413 return mex_info.SupportsMex; // wsdl dispatcher should handle this.
414 if (!wsdl_instance.HelpUrl.Equals (wsdl_instance.WsdlUrl))
415 return true; // in case help URL is not equivalent to WSDL URL, it anyways returns WSDL regardless of ?wsdl existence.
417 if (wsdl_instance.HelpUrl != null && Uri.Compare (ctx.RequestUrl, wsdl_instance.HelpUrl, cmpflag, fmtflag, StringComparison.Ordinal) == 0) {
418 // Do not handle this at normal dispatcher.
419 // Do return true otherwise, even if it is with "?wsdl".
420 // (It must be handled above if applicable.)
421 return mex_info != null;
424 return mex_info == null;