New test.
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Channels / HttpReplyChannel.cs
index 6492f9ea16f9bd23ee602ec5bcbe073eb9294adf..41968d02a1fd7b07e9b7c21c26e8f1920f72af41 100644 (file)
@@ -27,6 +27,7 @@
 //
 using System;
 using System.Collections.Generic;
+using System.Collections.Specialized;
 using System.IO;
 using System.Net;
 using System.ServiceModel;
@@ -39,7 +40,7 @@ namespace System.ServiceModel.Channels
        {
                HttpSimpleChannelListener<IReplyChannel> source;
                List<HttpListenerContext> waiting = new List<HttpListenerContext> ();
-               EndpointAddress local_address;
+               RequestContext reqctx;
 
                public HttpSimpleReplyChannel (HttpSimpleChannelListener<IReplyChannel> listener)
                        : base (listener)
@@ -47,6 +48,53 @@ namespace System.ServiceModel.Channels
                        this.source = listener;
                }
 
+               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 SignalAsyncWait ()
+               {
+                       if (wait == null)
+                               return;
+                       var wait_ = wait;
+                       wait = null;
+                       wait_.Set ();
+               }
+
+               void AbortConnections (TimeSpan timeout)
+               {
+                       // FIXME: use timeout
+                       lock (waiting)
+                               foreach (var ctx in waiting)
+                                       ctx.Response.Close ();
+                       if (wait != null)
+                               source.ListenerManager.CancelGetHttpContextAsync ();
+               }
+
+               protected override void OnClose (TimeSpan timeout)
+               {
+                       DateTime start = DateTime.Now;
+                       if (reqctx != null)
+                               reqctx.Close (timeout);
+
+                       // FIXME: consider timeout
+                       AbortConnections (timeout - (DateTime.Now - start));
+
+                       base.OnClose (timeout - (DateTime.Now - start));
+               }
+
                public override bool TryReceiveRequest (TimeSpan timeout, out RequestContext context)
                {
                        context = null;
@@ -59,15 +107,25 @@ namespace System.ServiceModel.Channels
                                        waiting.RemoveAt (0);
                                }
                        }
-                       if (ctx == null) 
+                       if (ctx == null)
                                // Though as long as this instance is used
                                // synchronously, it should not happen.
                                return false;
+                       if (ctx.Response.StatusCode != 200) {
+                               ctx.Response.Close ();
+                               return false;
+                       }
 
                        // FIXME: supply maxSizeOfHeaders.
                        int maxSizeOfHeaders = 0x10000;
 
                        Message msg = null;
+
+                       // FIXME: our HttpConnection (under HttpListener) 
+                       // somehow breaks when the underlying connection is
+                       // reused. Remove it when it gets fixed.
+                       ctx.Response.KeepAlive = false;
+
                        if (ctx.Request.HttpMethod == "POST") {
                                if (!Encoder.IsContentTypeSupported (ctx.Request.ContentType)) {
                                        ctx.Response.StatusCode = (int) HttpStatusCode.UnsupportedMediaType;
@@ -80,26 +138,22 @@ namespace System.ServiceModel.Channels
 
                                msg = Encoder.ReadMessage (
                                        ctx.Request.InputStream, maxSizeOfHeaders);
-                               if (source.MessageEncoder.MessageVersion.Envelope == EnvelopeVersion.Soap11 ||
-                                   source.MessageEncoder.MessageVersion.Addressing == AddressingVersion.None) {
+
+                               if (MessageVersion.Envelope.Equals (EnvelopeVersion.Soap11) ||
+                                   MessageVersion.Addressing.Equals (AddressingVersion.None)) {
                                        string action = GetHeaderItem (ctx.Request.Headers ["SOAPAction"]);
-                                       if (action != null)
+                                       if (action != null) {
+                                               if (action.Length > 2 && action [0] == '"' && action [action.Length] == '"')
+                                                       action = action.Substring (1, action.Length - 2);
                                                msg.Headers.Action = action;
+                                       }
                                }
                        } else if (ctx.Request.HttpMethod == "GET") {
-                               msg = Message.CreateMessage (source.MessageEncoder.MessageVersion, null);
+                               msg = Message.CreateMessage (MessageVersion, null);
                        }
                        msg.Headers.To = ctx.Request.Url;
-                       
-                       HttpRequestMessageProperty prop =
-                               new HttpRequestMessageProperty ();
-                       prop.Method = ctx.Request.HttpMethod;
-                       prop.QueryString = ctx.Request.Url.Query;
-                       if (prop.QueryString.StartsWith ("?"))
-                               prop.QueryString = prop.QueryString.Substring (1);
-                       // FIXME: prop.SuppressEntityBody
-                       prop.Headers.Add (ctx.Request.Headers);
-                       msg.Properties.Add (HttpRequestMessageProperty.Name, prop);
+                       msg.Properties.Add ("Via", LocalAddress.Uri);
+                       msg.Properties.Add (HttpRequestMessageProperty.Name, CreateRequestProperty (ctx.Request.HttpMethod, ctx.Request.Url.Query, ctx.Request.Headers));
 /*
 MessageBuffer buf = msg.CreateBufferedCopy (0x10000);
 msg = buf.CreateMessage ();
@@ -109,41 +163,49 @@ buf.CreateMessage ().WriteMessage (w);
 w.Close ();
 */
                        context = new HttpRequestContext (this, msg, ctx);
+                       reqctx = context;
                        return true;
                }
 
+               AutoResetEvent wait;
+
                public override bool WaitForRequest (TimeSpan timeout)
                {
-                       AutoResetEvent wait = new AutoResetEvent (false);
+                       if (wait != null)
+                               throw new InvalidOperationException ("Another wait operation is in progress");
                        try {
-                               source.Http.BeginGetContext (HttpContextReceived, wait);
+                               wait = new AutoResetEvent (false);
+                               source.ListenerManager.GetHttpContextAsync (timeout, HttpContextAcquired);
+                               if (wait != null) // in case callback is done before WaitOne() here.
+                                       return wait.WaitOne (timeout, false);
+                               return waiting.Count > 0;
                        } catch (HttpListenerException e) {
-                               if (e.ErrorCode == 0x80004005) // invalid handle. Happens during shutdown.
+                               // FIXME: does this make sense? I doubt.
+                               if ((uint) e.ErrorCode == 0x80004005) // invalid handle. Happens during shutdown.
                                        while (true) Thread.Sleep (1000); // thread is about to be terminated.
                                throw;
-                       } catch (ObjectDisposedException) { return false; }
-                       // FIXME: we might want to take other approaches.
-                       if (timeout.Ticks > int.MaxValue)
-                               timeout = TimeSpan.FromDays (20);
-                       return wait.WaitOne (timeout, false);
+                       } catch (ObjectDisposedException) {
+                               return false;
+                       } finally {
+                               wait = null;
+                       }
                }
 
-               void HttpContextReceived (IAsyncResult result)
+               void HttpContextAcquired (HttpContextInfo ctx)
                {
-                       if (State == CommunicationState.Closing || State == CommunicationState.Closed)
-                               return;
-
-                       waiting.Add (source.Http.EndGetContext (result));
-                       AutoResetEvent wait = (AutoResetEvent) result.AsyncState;
-                       wait.Set ();
+                       if (wait == null)
+                               throw new InvalidOperationException ("WaitForRequest operation has not started");
+                       var sctx = (HttpListenerContextInfo) ctx;
+                       if (State == CommunicationState.Opened && ctx != null)
+                               lock (waiting)
+                                       waiting.Add (sctx.Source);
+                       SignalAsyncWait ();
                }
        }
 
-       internal abstract class HttpReplyChannel : ReplyChannelBase
+       internal abstract class HttpReplyChannel : InternalReplyChannelBase
        {
                HttpChannelListenerBase<IReplyChannel> source;
-               List<HttpListenerContext> waiting = new List<HttpListenerContext> ();
-               EndpointAddress local_address;
 
                public HttpReplyChannel (HttpChannelListenerBase<IReplyChannel> listener)
                        : base (listener)
@@ -155,9 +217,8 @@ w.Close ();
                        get { return source.MessageEncoder; }
                }
 
-               // FIXME: where is it set?
-               public override EndpointAddress LocalAddress {
-                       get { return local_address; }
+               internal MessageVersion MessageVersion {
+                       get { return source.MessageEncoder.MessageVersion; }
                }
 
                public override RequestContext ReceiveRequest (TimeSpan timeout)
@@ -167,19 +228,6 @@ w.Close ();
                        return ctx;
                }
 
-               protected override void OnAbort ()
-               {
-                       foreach (HttpListenerContext ctx in waiting)
-                               ctx.Request.InputStream.Close ();
-               }
-
-               protected override void OnClose (TimeSpan timeout)
-               {
-                       // FIXME: consider timeout
-                       foreach (HttpListenerContext ctx in waiting)
-                               ctx.Request.InputStream.Close ();
-               }
-
                protected override void OnOpen (TimeSpan timeout)
                {
                }
@@ -198,5 +246,15 @@ w.Close ();
                        }
                        return raw;
                }
+
+               protected HttpRequestMessageProperty CreateRequestProperty (string method, string query, NameValueCollection headers)
+               {
+                       var prop = new HttpRequestMessageProperty ();
+                       prop.Method = method;
+                       prop.QueryString = query.StartsWith ("?") ? query.Substring (1) : query;
+                       // FIXME: prop.SuppressEntityBody
+                       prop.Headers.Add (headers);
+                       return prop;
+               }
        }
 }