Imported Upstream version 3.6.0

Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
This commit is contained in:
Jo Shields
2014-08-13 10:39:27 +01:00
commit a575963da9
50588 changed files with 8155799 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
2010-07-14 Atsushi Enomoto <atsushi@ximian.com>
* HttpReplyChannel.cs : pass HTTP ContentType header value to
MessageEncoder.ReadMessage().
2010-07-08 Atsushi Enomoto <atsushi@ximian.com>
* HttpRequestContext.cs : remove extra Action handling and old comment.
2010-07-05 Atsushi Enomoto <atsushi@ximian.com>
* HttpRequestContext.cs, HttpChannelListener.cs, HttpReplyChannel.cs:
rename unnecessary "Standalone" (removed).
2010-07-05 Atsushi Enomoto <atsushi@ximian.com>
* HttpStandaloneReplyChannel.cs
HttpStandaloneRequestContext.cs
HttpStandaloneChannelListener.cs
HttpReplyChannel.cs
HttpRequestContext.cs
HttpChannelListener.cs : renamed former 3 files to latter 3 files.
2010-07-05 Atsushi Enomoto <atsushi@ximian.com>
* HttpStandaloneReplyChannel.cs, HttpChannelListenerEntry.cs,
HttpContextInfo.cs, HttpListenerManager.cs,
HttpStandaloneRequestContext.cs, HttpListenerManagerTable.cs:
Fixed wsdl support in the new codebase, so enabled it again, and
made significant changes for ASP.NET integration. Now ASP.NET
implementation switched to the new one.
2010-07-05 Atsushi Enomoto <atsushi@ximian.com>
* HttpStandaloneReplyChannel.cs
HttpContextInfo.cs
HttpListenerManager.cs
HttpStandaloneRequestContext.cs
HttpListenerManagerTable.cs : revert previous changes, regression
on wsdl support.
2010-07-02 Atsushi Enomoto <atsushi@ximian.com>
* HttpStandaloneReplyChannel.cs, HttpStandaloneRequestContext.cs:
add more code that the asp.net stuff will become like.
2010-07-02 Atsushi Enomoto <atsushi@ximian.com>
* HttpStandaloneReplyChannel.cs, HttpListenerManager.cs,
HttpStandaloneRequestContext.cs :
refactoring, to add ASP.NET implementation. (not in use yet)
2010-07-02 Atsushi Enomoto <atsushi@ximian.com>
* HttpContextInfo.cs, HttpListenerManager.cs,
HttpListenerManagerTable.cs : add implementation for ASP.NET, not
in use yet.
2010-06-24 Atsushi Enomoto <atsushi@ximian.com>
* HttpListenerManager.cs : finish pending wait handle when closing
a listener. Fixed the issue on closing host/listener/channels.
2010-06-24 Atsushi Enomoto <atsushi@ximian.com>
* HttpStandaloneReplyChannel.cs : do not try to enter close loop
between request context and this.
2010-06-23 Atsushi Enomoto <atsushi@ximian.com>
* HttpStandaloneChannelListener.cs
HttpStandaloneReplyChannel.cs
HttpStandaloneRequestContext.cs
HttpListenerManagerTable.cs
HttpChannelListenerEntry.cs
HttpContextInfo.cs
HttpListenerManager.cs :
Refresh implementation for HTTP channel listener in new namespace,
which works fine with throttling.
It still has some issue in closing channels or listeners, so it
does not replace existing implementation yet.

View File

@@ -0,0 +1,163 @@
//
// HttpChannelListener.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
//
// Copyright (C) 2010 Novell, Inc. http://www.novell.com
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Security;
using System.Text;
using System.Threading;
namespace System.ServiceModel.Channels.Http
{
internal class HttpChannelListener<TChannel> : InternalChannelListenerBase<TChannel>, IChannelDispatcherBoundListener
where TChannel : class, IChannel
{
HttpListenerManager listener_manager;
public HttpChannelListener (HttpTransportBindingElement source, BindingContext context)
: base (context)
{
if (ServiceHostBase.CurrentServiceHostHack != null)
DispatcherBuilder.ChannelDispatcherSetter = delegate (ChannelDispatcher cd) { this.ChannelDispatcher = cd; };
this.Source = source;
// The null Uri check looks weird, but it seems the listener can be built without it.
// See HttpTransportBindingElementTest.BuildChannelListenerWithoutListenUri().
if (Uri != null && source.Scheme != Uri.Scheme)
throw new ArgumentException (String.Format ("Requested listen uri scheme must be {0}, but was {1}.", source.Scheme, Uri.Scheme));
foreach (BindingElement be in context.Binding.Elements) {
MessageEncodingBindingElement mbe = be as MessageEncodingBindingElement;
if (mbe != null) {
MessageEncoder = CreateEncoder<TChannel> (mbe);
break;
}
}
if (MessageEncoder == null)
MessageEncoder = new TextMessageEncoder (MessageVersion.Default, Encoding.UTF8);
if (context.BindingParameters.Contains (typeof (ServiceCredentials)))
SecurityTokenManager = new ServiceCredentialsSecurityTokenManager ((ServiceCredentials) context.BindingParameters [typeof (ServiceCredentials)]);
}
public ChannelDispatcher ChannelDispatcher { get; set; }
public HttpTransportBindingElement Source { get; private set; }
public HttpListenerManager ListenerManager {
get { return listener_manager; }
}
public ServiceCredentialsSecurityTokenManager SecurityTokenManager { get; private set; }
ManualResetEvent accept_channel_handle = new ManualResetEvent (true);
protected override TChannel OnAcceptChannel (TimeSpan timeout)
{
// HTTP channel could be accepted while there is no incoming request yet. The reply channel waits for the actual request.
// HTTP channel listeners do not accept more than one channel at a time.
DateTime start = DateTime.Now;
TimeSpan waitTimeout;
if (timeout == TimeSpan.MaxValue)
waitTimeout = TimeSpan.FromMilliseconds(int.MaxValue);
else
waitTimeout = timeout - (DateTime.Now - start);
accept_channel_handle.WaitOne (waitTimeout);
accept_channel_handle.Reset ();
TChannel ch = CreateChannel (timeout - (DateTime.Now - start));
ch.Closed += delegate {
accept_channel_handle.Set ();
};
return ch;
}
protected TChannel CreateChannel (TimeSpan timeout)
{
lock (ThisLock) {
return CreateChannelCore (timeout);
}
}
TChannel CreateChannelCore (TimeSpan timeout)
{
if (typeof (TChannel) == typeof (IReplyChannel))
return (TChannel) (object) new HttpReplyChannel ((HttpChannelListener<IReplyChannel>) (object) this);
throw new NotSupportedException (String.Format ("Channel type {0} is not supported", typeof (TChannel)));
}
protected override bool OnWaitForChannel (TimeSpan timeout)
{
throw new NotImplementedException ();
}
protected HttpListenerManager GetOrCreateListenerManager ()
{
var table = HttpListenerManagerTable.GetOrCreate (ChannelDispatcher != null ? ChannelDispatcher.Host : null);
return table.GetOrCreateManager (Uri, Source);
}
protected override void OnOpen (TimeSpan timeout)
{
listener_manager = GetOrCreateListenerManager ();
Properties.Add (listener_manager);
listener_manager.RegisterListener (ChannelDispatcher, Source, timeout);
}
protected override void OnAbort ()
{
listener_manager.UnregisterListener (ChannelDispatcher, TimeSpan.Zero);
}
protected override void OnClose (TimeSpan timeout)
{
if (State == CommunicationState.Closed)
return;
base.OnClose (timeout);
// The channels are kept open when the creator channel listener is closed.
// http://blogs.msdn.com/drnick/archive/2006/03/22/557642.aspx
listener_manager.UnregisterListener (ChannelDispatcher, timeout);
}
// immediately stop accepting channel.
public override bool CancelAsync (TimeSpan timeout)
{
try {
CurrentAsyncResult.AsyncWaitHandle.WaitOne (TimeSpan.Zero);
} catch (TimeoutException) {
}
return true;
}
}
}

View File

@@ -0,0 +1,101 @@
//
// HttpChannelListenerEntry.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
//
// Copyright (C) 2010 Novell, Inc. http://www.novell.com
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Security;
using System.Text;
using System.Threading;
namespace System.ServiceModel.Channels.Http
{
class HttpChannelListenerEntry
{
public HttpChannelListenerEntry (ChannelDispatcher channel, EventWaitHandle waitHandle)
{
ChannelDispatcher = channel;
WaitHandle = waitHandle;
ContextQueue = new Queue<HttpContextInfo> ();
RetrieverLock = new object ();
}
public object RetrieverLock { get; private set; }
public ChannelDispatcher ChannelDispatcher { get; private set; }
public EventWaitHandle WaitHandle { get; private set; }
public Queue<HttpContextInfo> ContextQueue { get; private set; }
internal static int CompareEntries (HttpChannelListenerEntry e1, HttpChannelListenerEntry e2)
{
if (e1.ChannelDispatcher.Endpoints.Count == 0)
return 1;
if (e2.ChannelDispatcher.Endpoints.Count == 0)
return -1;
// select the highest filter priority value in the Endpoints.
int p1 = e1.ChannelDispatcher.Endpoints.OrderByDescending (e => e.FilterPriority).First ().FilterPriority;
int p2 = e2.ChannelDispatcher.Endpoints.OrderByDescending (e => e.FilterPriority).First ().FilterPriority;
// then return the channel dispatcher with higher priority first.
return p2 - p1;
}
const UriComponents cmpflag = UriComponents.Path;
const UriFormat fmtflag = UriFormat.SafeUnescaped;
internal bool FilterHttpContext (HttpContextInfo ctx)
{
if (ChannelDispatcher == null)
return true; // no mex can be involved.
if (ctx.Request.HttpMethod.ToUpper () != "GET")
return !ChannelDispatcher.IsMex; // non-GET request never matches mex channel dispatcher.
var sme = ChannelDispatcher.Host.Extensions.Find<ServiceMetadataExtension> ();
if (sme == null)
return true; // no mex can be involved.
var listener = ChannelDispatcher.Listener;
var mex = sme.Instance;
// now the request is GET, and we have to return true or false based on the matrix below:
// matches wsdl or help| yes | no |
// mex | yes | no | yes | no |
// --------------------+-----+----+-----+----+
// | T | F | F | T |
bool match =
(mex.WsdlUrl != null && Uri.Compare (ctx.Request.Url, mex.WsdlUrl, cmpflag, fmtflag, StringComparison.Ordinal) == 0) ||
(mex.HelpUrl != null && Uri.Compare (ctx.Request.Url, mex.HelpUrl, cmpflag, fmtflag, StringComparison.Ordinal) == 0);
return !(match ^ ChannelDispatcher.IsMex);
}
}
}

View File

@@ -0,0 +1,358 @@
//
// HttpContextInfo.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
//
// Copyright (C) 2010 Novell, Inc. http://www.novell.com
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Net;
using System.Security.Principal;
using System.ServiceModel;
using System.Text;
using System.Threading;
using System.Web;
namespace System.ServiceModel.Channels.Http
{
// Context
abstract class HttpContextInfo
{
public abstract HttpRequestInfo Request { get; }
public abstract HttpResponseInfo Response { get; }
public abstract string User { get; }
public abstract string Password { get; }
public abstract void ReturnUnauthorized ();
public void Abort ()
{
Response.Abort ();
OnContextClosed ();
}
public void Close ()
{
Response.Close ();
OnContextClosed ();
}
protected virtual void OnContextClosed ()
{
}
}
class HttpStandaloneContextInfo : HttpContextInfo
{
public HttpStandaloneContextInfo (HttpListenerContext ctx)
{
this.ctx = ctx;
request = new HttpStandaloneRequestInfo (ctx.Request);
response = new HttpStandaloneResponseInfo (ctx.Response);
}
HttpListenerContext ctx;
HttpStandaloneRequestInfo request;
HttpStandaloneResponseInfo response;
public HttpListenerContext Source {
get { return ctx; }
}
public override HttpRequestInfo Request {
get { return request; }
}
public override HttpResponseInfo Response {
get { return response; }
}
public override string User {
get { return ctx.User != null ? ((HttpListenerBasicIdentity) ctx.User.Identity).Name : null; }
}
public override string Password {
get { return ctx.User != null ? ((HttpListenerBasicIdentity) ctx.User.Identity).Password : null; }
}
public override void ReturnUnauthorized ()
{
ctx.Response.StatusCode = 401;
}
}
class AspNetHttpContextInfo : HttpContextInfo
{
public AspNetHttpContextInfo (SvcHttpHandler handler, HttpContext ctx)
{
this.ctx = ctx;
this.handler = handler;
this.request = new AspNetHttpRequestInfo (ctx.Request);
this.response = new AspNetHttpResponseInfo (ctx.Response);
}
HttpContext ctx;
SvcHttpHandler handler;
AspNetHttpRequestInfo request;
AspNetHttpResponseInfo response;
public HttpContext Source {
get { return ctx; }
}
public override HttpRequestInfo Request {
get { return request; }
}
public override HttpResponseInfo Response {
get { return response; }
}
public override string User {
get { return ctx.User != null ? ((GenericIdentity) ctx.User.Identity).Name : null; }
}
// FIXME: how to acquire this?
public override string Password {
get { return null; }
}
public override void ReturnUnauthorized ()
{
ctx.Response.StatusCode = 401;
}
protected override void OnContextClosed ()
{
handler.EndHttpRequest (ctx);
}
}
// Request
abstract class HttpRequestInfo
{
public abstract long ContentLength64 { get; }
public abstract NameValueCollection QueryString { get; }
public abstract NameValueCollection Headers { get; }
public abstract Uri Url { get; }
public abstract string ContentType { get; }
public abstract string HttpMethod { get; }
public abstract Stream InputStream { get; }
public abstract string ClientIPAddress { get; }
public abstract int ClientPort { get; }
}
class HttpStandaloneRequestInfo : HttpRequestInfo
{
public HttpStandaloneRequestInfo (HttpListenerRequest request)
{
this.req = request;
}
HttpListenerRequest req;
public override long ContentLength64 {
get { return req.ContentLength64; }
}
public override NameValueCollection QueryString {
get { return req.QueryString; }
}
public override NameValueCollection Headers {
get { return req.Headers; }
}
public override Uri Url {
get { return req.Url; }
}
public override string ContentType {
get { return req.ContentType; }
}
public override string HttpMethod {
get { return req.HttpMethod; }
}
public override Stream InputStream {
get { return req.InputStream; }
}
public override string ClientIPAddress {
get { return req.RemoteEndPoint.Address.ToString (); }
}
public override int ClientPort {
get { return req.RemoteEndPoint.Port; }
}
}
class AspNetHttpRequestInfo : HttpRequestInfo
{
public AspNetHttpRequestInfo (HttpRequest request)
{
this.req = request;
}
HttpRequest req;
public override long ContentLength64 {
get { return req.ContentLength; }
}
public override NameValueCollection QueryString {
get { return req.QueryString; }
}
public override NameValueCollection Headers {
get { return req.Headers; }
}
public override Uri Url {
get { return req.Url; }
}
public override string ContentType {
get { return req.ContentType; }
}
public override string HttpMethod {
get { return req.HttpMethod; }
}
public override Stream InputStream {
get { return req.InputStream; }
}
public override string ClientIPAddress {
get { return req.UserHostAddress; }
}
public override int ClientPort {
get { return -1; } // cannot retrieve
}
}
// Response
abstract class HttpResponseInfo
{
public abstract string ContentType { get; set; }
public abstract NameValueCollection Headers { get; }
public abstract Stream OutputStream { get; }
public abstract int StatusCode { get; set; }
public abstract string StatusDescription { get; set; }
public abstract void Abort ();
public abstract void Close ();
public abstract void SetLength (long value);
public virtual bool SuppressContent { get; set; }
}
class HttpStandaloneResponseInfo : HttpResponseInfo
{
public HttpStandaloneResponseInfo (HttpListenerResponse response)
{
this.res = response;
}
HttpListenerResponse res;
public override string ContentType {
get { return res.ContentType; }
set { res.ContentType = value; }
}
public override NameValueCollection Headers {
get { return res.Headers; }
}
public override int StatusCode {
get { return res.StatusCode; }
set { res.StatusCode = value; }
}
public override string StatusDescription {
get { return res.StatusDescription; }
set { res.StatusDescription = value; }
}
public override Stream OutputStream {
get { return res.OutputStream; }
}
public override void Abort ()
{
res.Abort ();
}
public override void Close ()
{
res.Close ();
}
public override void SetLength (long value)
{
res.ContentLength64 = value;
}
}
class AspNetHttpResponseInfo : HttpResponseInfo
{
public AspNetHttpResponseInfo (HttpResponse response)
{
this.res = response;
}
HttpResponse res;
public override bool SuppressContent {
get { return res.SuppressContent; }
set { res.SuppressContent = value; }
}
public override string ContentType {
get { return res.ContentType; }
set { res.ContentType = value; }
}
public override NameValueCollection Headers {
get { return res.Headers; }
}
public override int StatusCode {
get { return res.StatusCode; }
set { res.StatusCode = value; }
}
public override string StatusDescription {
get { return res.StatusDescription; }
set { res.StatusDescription = value; }
}
public override Stream OutputStream {
get { return res.OutputStream; }
}
public override void Abort ()
{
res.End ();
}
public override void Close ()
{
// We must not close the response here, as everything is taking place in the
// HttpApplication's pipeline context and the output is sent to the client
// _after_ we leave this method. Closing the response here will stop any
// output from reaching the client.
}
public override void SetLength (long value)
{
res.AddHeader ("Content-Length", value.ToString (CultureInfo.InvariantCulture));
}
}
}

View File

@@ -0,0 +1,217 @@
//
// HttpListenerManager.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
//
// Copyright (C) 2010 Novell, Inc. http://www.novell.com
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Security;
using System.Text;
using System.Threading;
namespace System.ServiceModel.Channels.Http
{
internal abstract class HttpListenerManager
{
protected HttpListenerManager ()
{
Entries = new List<HttpChannelListenerEntry> ();
}
public List<HttpChannelListenerEntry> Entries { get; private set; }
public abstract void RegisterListener (ChannelDispatcher channel, HttpTransportBindingElement element, TimeSpan timeout);
public abstract void UnregisterListener (ChannelDispatcher channel, TimeSpan timeout);
protected void RegisterListenerCommon (ChannelDispatcher channel, TimeSpan timeout)
{
Entries.Add (new HttpChannelListenerEntry (channel, new AutoResetEvent (false)));
Entries.Sort (HttpChannelListenerEntry.CompareEntries);
}
protected void UnregisterListenerCommon (ChannelDispatcher channel, TimeSpan timeout)
{
var entry = Entries.First (e => e.ChannelDispatcher == channel);
Entries.Remove (entry);
entry.WaitHandle.Set (); // make sure to finish pending requests.
}
public void ProcessNewContext (HttpContextInfo ctxi)
{
var ce = SelectChannel (ctxi);
if (ce == null)
throw new InvalidOperationException ("HttpListenerContext does not match any of the registered channels");
ce.ContextQueue.Enqueue (ctxi);
ce.WaitHandle.Set ();
}
HttpChannelListenerEntry SelectChannel (HttpContextInfo ctx)
{
foreach (var e in Entries)
if (e.FilterHttpContext (ctx))
return e;
return null;
}
public bool TryDequeueRequest (ChannelDispatcher channel, TimeSpan timeout, out HttpContextInfo context)
{
DateTime start = DateTime.Now;
context = null;
var ce = Entries.FirstOrDefault (e => e.ChannelDispatcher == channel);
if (ce == null)
return false;
lock (ce.RetrieverLock) {
var q = ce.ContextQueue;
if (q.Count == 0) {
if (timeout.TotalMilliseconds < 0) return false;
TimeSpan waitTimeout = timeout;
if (timeout == TimeSpan.MaxValue)
waitTimeout = TimeSpan.FromMilliseconds (int.MaxValue);
bool ret = ce.WaitHandle.WaitOne (waitTimeout);
return ret && TryDequeueRequest (channel, waitTimeout - (DateTime.Now - start), out context); // recurse, am lazy :/
}
context = q.Dequeue ();
return true;
}
}
}
internal class HttpStandaloneListenerManager : HttpListenerManager
{
public HttpStandaloneListenerManager (Uri uri, HttpTransportBindingElement element)
{
var l = new HttpListener ();
string uriString = element.HostNameComparisonMode == HostNameComparisonMode.Exact ? uri.ToString () : uri.Scheme + "://*" + uri.GetComponents (UriComponents.Port | UriComponents.Path, UriFormat.SafeUnescaped);
if (!uriString.EndsWith ("/", StringComparison.Ordinal))
uriString += "/"; // HttpListener requires this mess.
l.Prefixes.Add (uriString);
this.listener = l;
}
HttpListener listener;
Thread loop;
// FIXME: use timeout
public override void RegisterListener (ChannelDispatcher channel, HttpTransportBindingElement element, TimeSpan timeout)
{
RegisterListenerCommon (channel, timeout);
if (Entries.Count != 1)
return;
if (element != null) {
var l = listener;
l.AuthenticationSchemeSelectorDelegate = delegate (HttpListenerRequest req) {
return element.AuthenticationScheme;
};
l.Realm = element.Realm;
l.UnsafeConnectionNtlmAuthentication = element.UnsafeConnectionNtlmAuthentication;
}
// Start here. It is shared between channel listeners
// that share the same listen Uri. So there is no other appropriate place.
#if USE_SEPARATE_LOOP // this cannot be enabled because it causes infinite loop when ChannelDispatcher is not involved.
loop = new Thread (new ThreadStart (delegate {
listener.Start ();
try {
while (true)
ProcessNewContext (listener.GetContext ());
} catch (ThreadAbortException) {
Thread.ResetAbort ();
}
listener.Stop ();
}));
loop.Start ();
#else
listener.Start ();
listener.BeginGetContext (GetContextCompleted, null);
#endif
}
// FIXME: use timeout
public override void UnregisterListener (ChannelDispatcher channel, TimeSpan timeout)
{
UnregisterListenerCommon (channel, timeout);
// stop the server if there is no more registered listener.
if (Entries.Count > 0)
return;
#if USE_SEPARATE_LOOP
loop.Abort ();
#else
this.listener.Stop ();
#endif
}
void GetContextCompleted (IAsyncResult result)
{
var ctx = listener.EndGetContext (result);
ProcessNewContext (ctx);
// start another listening
listener.BeginGetContext (GetContextCompleted, null);
}
void ProcessNewContext (HttpListenerContext ctx)
{
if (ctx == null)
return;
ProcessNewContext (new HttpStandaloneContextInfo (ctx));
}
}
internal class AspNetHttpListenerManager : HttpListenerManager
{
public AspNetHttpListenerManager (Uri uri)
{
}
public override void RegisterListener (ChannelDispatcher channel, HttpTransportBindingElement element, TimeSpan timeout)
{
RegisterListenerCommon (channel, timeout);
}
public override void UnregisterListener (ChannelDispatcher channel, TimeSpan timeout)
{
UnregisterListenerCommon (channel, timeout);
}
}
}

View File

@@ -0,0 +1,131 @@
//
// HttpListenerManagerTable.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
//
// Copyright (C) 2010 Novell, Inc. http://www.novell.com
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Security;
using System.Text;
using System.Threading;
namespace System.ServiceModel.Channels.Http
{
// instantiated per service host
internal class HttpListenerManagerTable
{
// static members
static readonly List<HttpListenerManagerTable> instances = new List<HttpListenerManagerTable> ();
public static HttpListenerManagerTable GetOrCreate (object serviceHostKey)
{
var m = instances.FirstOrDefault (p => p.ServiceHostKey == serviceHostKey);
if (m == null) {
m = new HttpListenerManagerTable (serviceHostKey);
instances.Add (m);
}
return m;
}
// instance members
HttpListenerManagerTable (object serviceHostKey)
{
ServiceHostKey = serviceHostKey ?? new object ();
listeners = new Dictionary<Uri, HttpListenerManager> ();
}
Dictionary<Uri,HttpListenerManager> listeners;
public object ServiceHostKey { get; private set; }
public HttpListenerManager GetOrCreateManager (Uri uri, HttpTransportBindingElement element)
{
var m = listeners.FirstOrDefault (p => p.Key.Equals (uri)).Value;
if (m == null) {
// Two special cases
string absolutePath = uri.AbsolutePath;
if (absolutePath.EndsWith ("/js", StringComparison.Ordinal) ||
absolutePath.EndsWith ("/jsdebug", StringComparison.Ordinal))
return CreateListenerManager (uri, element);
// Try without the query, if any
UriBuilder ub = null;
if (!String.IsNullOrEmpty (uri.Query)) {
ub = new UriBuilder (uri);
ub.Query = null;
m = listeners.FirstOrDefault (p => p.Key.Equals (ub.Uri)).Value;
if (m != null)
return m;
}
// Chop off the part following the last / in the absolut path part
// of the Uri - this is the operation being called in, the remaining
// left-hand side of the absolute path should be the service
// endpoint address
if (ub == null) {
ub = new UriBuilder (uri);
ub.Query = null;
}
int lastSlash = absolutePath.LastIndexOf ('/');
if (lastSlash != -1) {
ub.Path = absolutePath.Substring (0, lastSlash);
m = listeners.FirstOrDefault (p => p.Key.Equals (ub.Uri)).Value;
if (m != null)
return m;
}
}
if (m == null)
return CreateListenerManager (uri, element);
return m;
}
HttpListenerManager CreateListenerManager (Uri uri, HttpTransportBindingElement element)
{
HttpListenerManager m;
if (ServiceHostingEnvironment.InAspNet)
m = new AspNetHttpListenerManager (uri);
else
m = new HttpStandaloneListenerManager (uri, element);
listeners [uri] = m;
return m;
}
}
}

View File

@@ -0,0 +1,253 @@
//
// HttpReplyChannel.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
//
// Copyright (C) 2010 Novell, Inc. http://www.novell.com
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.IO;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Security;
using System.Text;
using System.Threading;
namespace System.ServiceModel.Channels.Http
{
internal class HttpReplyChannel : InternalReplyChannelBase
{
HttpChannelListener<IReplyChannel> source;
RequestContext reqctx;
SecurityTokenAuthenticator security_token_authenticator;
SecurityTokenResolver security_token_resolver;
public HttpReplyChannel (HttpChannelListener<IReplyChannel> listener)
: base (listener)
{
this.source = listener;
if (listener.SecurityTokenManager != null) {
var str = new SecurityTokenRequirement () { TokenType = SecurityTokenTypes.UserName };
security_token_authenticator = listener.SecurityTokenManager.CreateSecurityTokenAuthenticator (str, out security_token_resolver);
}
}
internal HttpChannelListener<IReplyChannel> Source {
get { return source; }
}
public MessageEncoder Encoder {
get { return source.MessageEncoder; }
}
internal MessageVersion MessageVersion {
get { return source.MessageEncoder.MessageVersion; }
}
public override RequestContext ReceiveRequest (TimeSpan timeout)
{
RequestContext ctx;
if (!TryReceiveRequest (timeout, out ctx))
throw new TimeoutException ();
return ctx;
}
protected override void OnOpen (TimeSpan timeout)
{
}
protected override void OnAbort ()
{
AbortConnections (TimeSpan.Zero);
base.OnAbort (); // FIXME: remove it. The base is wrong. But it is somehow required to not block some tests.
}
public override bool CancelAsync (TimeSpan timeout)
{
AbortConnections (timeout);
// FIXME: this wait is sort of hack (because it should not be required), but without it some tests are blocked.
// This hack even had better be moved to base.CancelAsync().
if (CurrentAsyncResult != null)
CurrentAsyncResult.AsyncWaitHandle.WaitOne (TimeSpan.FromMilliseconds (300));
return base.CancelAsync (timeout);
}
void AbortConnections (TimeSpan timeout)
{
if (reqctx != null)
reqctx.Close (timeout);
}
bool close_started;
protected override void OnClose (TimeSpan timeout)
{
if (close_started)
return;
close_started = true;
DateTime start = DateTime.Now;
// FIXME: consider timeout
AbortConnections (timeout - (DateTime.Now - start));
base.OnClose (timeout - (DateTime.Now - start));
}
protected string GetHeaderItem (string raw)
{
if (raw == null || raw.Length == 0)
return raw;
switch (raw [0]) {
case '\'':
case '"':
if (raw [raw.Length - 1] == raw [0])
return raw.Substring (1, raw.Length - 2);
// FIXME: is it simply an error?
break;
}
return raw;
}
protected HttpRequestMessageProperty CreateRequestProperty (HttpContextInfo ctxi)
{
var query = ctxi.Request.Url.Query;
var prop = new HttpRequestMessageProperty ();
prop.Method = ctxi.Request.HttpMethod;
prop.QueryString = query.StartsWith ("?") ? query.Substring (1) : query;
// FIXME: prop.SuppressEntityBody
prop.Headers.Add (ctxi.Request.Headers);
return prop;
}
public override bool TryReceiveRequest (TimeSpan timeout, out RequestContext context)
{
context = null;
HttpContextInfo ctxi;
if (!source.ListenerManager.TryDequeueRequest (source.ChannelDispatcher, timeout, out ctxi))
return false;
if (ctxi == null)
return true; // returning true, yet context is null. This happens at closing phase.
if (source.Source.AuthenticationScheme != AuthenticationSchemes.Anonymous) {
if (security_token_authenticator != null)
// FIXME: use return value?
try {
security_token_authenticator.ValidateToken (new UserNameSecurityToken (ctxi.User, ctxi.Password));
} catch (Exception) {
ctxi.ReturnUnauthorized ();
}
else {
ctxi.ReturnUnauthorized ();
}
}
Message msg = null;
if (ctxi.Request.HttpMethod == "POST")
msg = CreatePostMessage (ctxi);
else if (ctxi.Request.HttpMethod == "GET")
msg = Message.CreateMessage (MessageVersion.None, null); // HTTP GET-based request
if (msg == null)
return false;
if (msg.Headers.To == null)
msg.Headers.To = ctxi.Request.Url;
msg.Properties.Add ("Via", LocalAddress.Uri);
msg.Properties.Add (HttpRequestMessageProperty.Name, CreateRequestProperty (ctxi));
Logger.LogMessage (MessageLogSourceKind.TransportReceive, ref msg, source.Source.MaxReceivedMessageSize);
context = new HttpRequestContext (this, ctxi, msg);
reqctx = context;
return true;
}
protected Message CreatePostMessage (HttpContextInfo ctxi)
{
if (ctxi.Response.StatusCode != 200) { // it's already invalid.
ctxi.Close ();
return null;
}
if (!Encoder.IsContentTypeSupported (ctxi.Request.ContentType)) {
ctxi.Response.StatusCode = (int) HttpStatusCode.UnsupportedMediaType;
ctxi.Response.StatusDescription = String.Format (
"Expected content-type '{0}' but got '{1}'", Encoder.ContentType, ctxi.Request.ContentType);
ctxi.Close ();
return null;
}
// FIXME: supply maxSizeOfHeaders.
int maxSizeOfHeaders = 0x10000;
#if false // FIXME: enable it, once duplex callback test gets passed.
Stream stream = ctxi.Request.InputStream;
if (source.Source.TransferMode == TransferMode.Buffered) {
if (ctxi.Request.ContentLength64 <= 0)
throw new ArgumentException ("This HTTP channel is configured to use buffered mode, and thus expects Content-Length sent to the listener");
long size = 0;
var ms = new MemoryStream ();
var buf = new byte [0x1000];
while (size < ctxi.Request.ContentLength64) {
if ((size += stream.Read (buf, 0, 0x1000)) > source.Source.MaxBufferSize)
throw new QuotaExceededException ("Message quota exceeded");
ms.Write (buf, 0, (int) (size - ms.Length));
}
ms.Position = 0;
stream = ms;
}
var msg = Encoder.ReadMessage (
stream, maxSizeOfHeaders, ctxi.Request.ContentType);
#else
var msg = Encoder.ReadMessage (
ctxi.Request.InputStream, maxSizeOfHeaders, ctxi.Request.ContentType);
#endif
if (MessageVersion.Envelope.Equals (EnvelopeVersion.Soap11) ||
MessageVersion.Addressing.Equals (AddressingVersion.None)) {
string action = GetHeaderItem (ctxi.Request.Headers ["SOAPAction"]);
if (action != null) {
if (action.Length > 2 && action [0] == '"' && action [action.Length] == '"')
action = action.Substring (1, action.Length - 2);
msg.Headers.Action = action;
}
}
msg.Properties.Add (RemoteEndpointMessageProperty.Name, new RemoteEndpointMessageProperty (ctxi.Request.ClientIPAddress, ctxi.Request.ClientPort));
return msg;
}
public override bool WaitForRequest (TimeSpan timeout)
{
throw new NotImplementedException ();
}
}
}

View File

@@ -0,0 +1,167 @@
//
// HttpRequestContext.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
//
// Copyright (C) 2010 Novell, Inc. http://www.novell.com
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System.IO;
using System.Net;
using System.Threading;
namespace System.ServiceModel.Channels.Http
{
internal class HttpRequestContext : RequestContext
{
public HttpRequestContext (HttpReplyChannel channel, HttpContextInfo context, Message request)
{
if (channel == null)
throw new ArgumentNullException ("channel");
if (context == null)
throw new ArgumentNullException ("context");
if (request == null)
throw new ArgumentNullException ("request");
this.channel = channel;
this.context = context;
this.request = request;
}
Message request;
HttpReplyChannel channel;
HttpContextInfo context;
public override Message RequestMessage {
get { return request; }
}
public HttpReplyChannel Channel {
get { return channel; }
}
public HttpContextInfo Context {
get { return context; }
}
public override IAsyncResult BeginReply (
Message msg, AsyncCallback callback, object state)
{
return BeginReply (msg,
Channel.DefaultSendTimeout,
callback, state);
}
Action<Message,TimeSpan> reply_delegate;
public override IAsyncResult BeginReply (
Message msg, TimeSpan timeout,
AsyncCallback callback, object state)
{
if (reply_delegate == null)
reply_delegate = new Action<Message,TimeSpan> (Reply);
return reply_delegate.BeginInvoke (msg, timeout, callback, state);
}
public override void EndReply (IAsyncResult result)
{
if (result == null)
throw new ArgumentNullException ("result");
if (reply_delegate == null)
throw new InvalidOperationException ("reply operation has not started");
reply_delegate.EndInvoke (result);
}
public override void Reply (Message msg)
{
Reply (msg, Channel.DefaultSendTimeout);
}
public override void Reply (Message msg, TimeSpan timeout)
{
InternalReply (msg, timeout);
}
public override void Abort ()
{
InternalAbort ();
}
public override void Close ()
{
Close (Channel.DefaultSendTimeout);
}
public override void Close (TimeSpan timeout)
{
InternalClose (timeout);
}
// implementation internals
protected virtual void InternalAbort ()
{
Context.Abort ();
}
protected virtual void InternalClose (TimeSpan timeout)
{
Context.Close ();
}
protected virtual void InternalReply (Message msg, TimeSpan timeout)
{
if (msg == null)
throw new ArgumentNullException ("msg");
Logger.LogMessage (MessageLogSourceKind.TransportSend, ref msg, Channel.Source.Source.MaxReceivedMessageSize);
MemoryStream ms = new MemoryStream ();
Channel.Encoder.WriteMessage (msg, ms);
Context.Response.ContentType = Channel.Encoder.ContentType;
string pname = HttpResponseMessageProperty.Name;
bool suppressEntityBody = false;
if (msg.Properties.ContainsKey (pname)) {
HttpResponseMessageProperty hp = (HttpResponseMessageProperty) msg.Properties [pname];
string contentType = hp.Headers ["Content-Type"];
if (contentType != null)
Context.Response.ContentType = contentType;
Context.Response.Headers.Add (hp.Headers);
if (hp.StatusCode != default (HttpStatusCode))
Context.Response.StatusCode = (int) hp.StatusCode;
Context.Response.StatusDescription = hp.StatusDescription;
if (hp.SuppressEntityBody)
suppressEntityBody = true;
}
if (msg.IsFault)
Context.Response.StatusCode = 500;
if (!suppressEntityBody) {
Context.Response.SetLength (ms.Length);
Context.Response.OutputStream.Write (ms.GetBuffer (), 0, (int) ms.Length);
Context.Response.OutputStream.Flush ();
}
else
Context.Response.SuppressContent = true;
}
}
}