2004-05-05 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / class / System.Web.Services / System.Web.Services.Protocols / SoapHttpClientProtocol.cs
index 19019dbb6ae4b404a9361bde5f691c2e40175a28..b93aa73a9b1e34d611d94e28a6d31d47cfc0e6ee 100644 (file)
@@ -4,6 +4,7 @@
 // Author:\r
 //   Tim Coleman (tim@timcoleman.com)\r
 //   Miguel de Icaza (miguel@ximian.com)\r
+//   Lluis Sanchez Gual (lluis@ximian.com)\r
 //\r
 // Copyright (C) Tim Coleman, 2002\r
 // Copyright (C) Ximian, Inc, 2003\r
@@ -19,167 +20,284 @@ using System.Web.Services;
 using System.Diagnostics;\r
 using System.Runtime.CompilerServices;\r
 using System.Web.Services.Description;\r
+using System.Web.Services.Discovery;\r
+using System.Xml.Serialization;\r
+using System.Xml.Schema;\r
+using System.Collections;\r
+using System.Threading;\r
 \r
 namespace System.Web.Services.Protocols {\r
        public class SoapHttpClientProtocol : HttpWebClientProtocol {\r
+               SoapTypeStubInfo type_info;\r
+\r
+               #region SoapWebClientAsyncResult class\r
+\r
+               internal class SoapWebClientAsyncResult: WebClientAsyncResult\r
+               {\r
+                       public SoapWebClientAsyncResult (WebRequest request, AsyncCallback callback, object asyncState)\r
+                       : base (request, callback, asyncState)\r
+                       {\r
+                       }\r
+               \r
+                       public SoapClientMessage Message;\r
+                       public SoapExtension[] Extensions;\r
+               }\r
+               #endregion\r
 \r
                #region Constructors\r
 \r
                public SoapHttpClientProtocol () \r
                {\r
+                       type_info = (SoapTypeStubInfo) TypeStubManager.GetTypeStub (this.GetType (), "Soap");\r
                }\r
-               \r
+\r
                #endregion // Constructors\r
 \r
                #region Methods\r
 \r
-               [MonoTODO]\r
                protected IAsyncResult BeginInvoke (string methodName, object[] parameters, AsyncCallback callback, object asyncState)\r
                {\r
-                       throw new NotImplementedException ();\r
-               }\r
+                       SoapMethodStubInfo msi = (SoapMethodStubInfo) type_info.GetMethod (methodName);\r
 \r
-               [MonoTODO]\r
-               public void Discover ()\r
-               {\r
-                       throw new NotImplementedException ();\r
+                       SoapWebClientAsyncResult ainfo = null;\r
+                       try\r
+                       {\r
+                               SoapClientMessage message = new SoapClientMessage (this, msi, Url, parameters);\r
+                               message.CollectHeaders (this, message.MethodStubInfo.Headers, SoapHeaderDirection.In);\r
+                               \r
+                               WebRequest request = GetRequestForMessage (uri, message);\r
+                               \r
+                               ainfo = new SoapWebClientAsyncResult (request, callback, asyncState);\r
+                               ainfo.Message = message;\r
+                               ainfo.Extensions = SoapExtension.CreateExtensionChain (type_info.SoapExtensions[0], msi.SoapExtensions, type_info.SoapExtensions[1]);\r
+\r
+                               ainfo.Request.BeginGetRequestStream (new AsyncCallback (AsyncGetRequestStreamDone), ainfo);\r
+                       }\r
+                       catch (Exception ex)\r
+                       {\r
+                               if (ainfo != null)\r
+                                       ainfo.SetCompleted (null, ex, false);\r
+                       }\r
+\r
+                       return ainfo;\r
                }\r
 \r
-               [MonoTODO]\r
-               protected object[] EndInvoke (IAsyncResult asyncResult)\r
+               void AsyncGetRequestStreamDone (IAsyncResult ar)\r
                {\r
-                       throw new NotImplementedException ();\r
+                       SoapWebClientAsyncResult ainfo = (SoapWebClientAsyncResult) ar.AsyncState;\r
+                       try\r
+                       {\r
+                               SendRequest (ainfo.Request.EndGetRequestStream (ar), ainfo.Message, ainfo.Extensions);\r
+                               ainfo.Request.BeginGetResponse (new AsyncCallback (AsyncGetResponseDone), ainfo);\r
+                       }\r
+                       catch (Exception ex)\r
+                       {\r
+                               ainfo.SetCompleted (null, ex, true);\r
+                       }\r
                }\r
 \r
-               protected override WebRequest GetWebRequest (Uri uri)\r
+               void AsyncGetResponseDone (IAsyncResult ar)\r
                {\r
-                       WebRequest request = WebRequest.Create (uri);\r
-                       request.Method = "POST";\r
+                       SoapWebClientAsyncResult ainfo = (SoapWebClientAsyncResult) ar.AsyncState;\r
+                       WebResponse response = null;\r
 \r
-                       return request;\r
+                       try {\r
+                               response = GetWebResponse (ainfo.Request, ar);\r
+                       }\r
+                       catch (WebException ex) {\r
+                               response = ex.Response;\r
+                               HttpWebResponse http_response = response as HttpWebResponse;\r
+                               if (http_response == null || http_response.StatusCode != HttpStatusCode.InternalServerError) {\r
+                                       ainfo.SetCompleted (null, ex, true);\r
+                                       return;\r
+                               }\r
+                       }\r
+                       catch (Exception ex) {\r
+                               ainfo.SetCompleted (null, ex, true);\r
+                               return;\r
+                       }\r
+\r
+                       try {\r
+                               object[] result = ReceiveResponse (response, ainfo.Message, ainfo.Extensions);\r
+                               ainfo.SetCompleted (result, null, true);\r
+                       }\r
+                       catch (Exception ex) {\r
+                               ainfo.SetCompleted (null, ex, true);\r
+                       }\r
                }\r
 \r
-               //\r
-               // Just for debugging\r
-               //\r
-               void DumpStackFrames ()\r
+               protected object[] EndInvoke (IAsyncResult asyncResult)\r
                {\r
-                       StackTrace st = new StackTrace ();\r
+                       if (!(asyncResult is SoapWebClientAsyncResult)) throw new ArgumentException ("asyncResult is not the return value from BeginInvoke");\r
 \r
-                       for (int i = 0; i < st.FrameCount; i++){\r
-                               StackFrame sf = st.GetFrame (i);\r
-                               Console.WriteLine ("At frame: {0} {1}", i, sf.GetMethod ().Name);\r
+                       SoapWebClientAsyncResult ainfo = (SoapWebClientAsyncResult) asyncResult;\r
+                       lock (ainfo)\r
+                       {\r
+                               if (!ainfo.IsCompleted) ainfo.WaitForComplete ();\r
+                               if (ainfo.Exception != null) throw ainfo.Exception;\r
+                               else return (object[]) ainfo.Result;\r
                        }\r
                }\r
-               \r
-               //\r
-               // The `method_name' should be the name of our invoker, this is only used\r
-               // for sanity checks, nothing else\r
-               //\r
-               [MethodImplAttribute(MethodImplOptions.NoInlining)]\r
-               MethodInfo GetCallerMethod (string method_name)\r
-               {\r
-                       MethodInfo mi;\r
-#if StackFrameWorks\r
-                       StackFrame stack_trace = new StackFrame (5, false);\r
-                       mi = (MethodInfo) stack_frame.GetMethod ();\r
 \r
-#else\r
-                       //\r
-                       // Temporary hack: look for a type which is not this type\r
-                       //\r
-                       StackTrace st = new StackTrace ();\r
-                       mi = null;\r
-                       for (int i = 0; i < st.FrameCount; i++){\r
-                               StackFrame sf = st.GetFrame (i);\r
-                               mi = (MethodInfo) sf.GetMethod ();\r
-                               if (mi.DeclaringType != typeof (SoapHttpClientProtocol))\r
-                                       break;\r
-                       }\r
-#endif\r
-                       //\r
-                       // A few sanity checks, just in case the code moves around later\r
-                       //\r
-                       if (!mi.DeclaringType.IsSubclassOf (typeof (System.Web.Services.Protocols.SoapHttpClientProtocol)))\r
-                               throw new Exception ("We are pointing to the wrong method (T=" + mi.DeclaringType + ")");\r
-\r
-                       if (mi.DeclaringType == typeof (System.Web.Services.Protocols.SoapHttpClientProtocol))\r
-                               throw new Exception ("We are pointing to the wrong method (we are pointing to our Invoke)");\r
-\r
-                       if (mi.Name != method_name)\r
-                               throw new Exception ("The method we point to is: " + mi.Name);\r
-\r
-                       return mi;\r
-               }\r
-               \r
-               [MethodImplAttribute(MethodImplOptions.NoInlining)]\r
-               SoapClientMessage CreateMessage (string method_name, object [] parameters)\r
+               public void Discover ()\r
                {\r
-                       MethodInfo mi = GetCallerMethod (method_name);\r
-                       object [] attributes = mi.GetCustomAttributes (typeof (System.Web.Services.Protocols.SoapDocumentMethodAttribute), false);\r
-                       SoapDocumentMethodAttribute sma = (SoapDocumentMethodAttribute) attributes [0];\r
-\r
-                       Console.WriteLine ("SMAA:    " + sma.Action);\r
-                       Console.WriteLine ("Binding: " + sma.Binding);\r
-                       Console.WriteLine ("OneWay:  " + sma.OneWay);\r
-                       Console.WriteLine ("PStyle:  " + sma.ParameterStyle);\r
-                       Console.WriteLine ("REN:     " + sma.RequestElementName);\r
-                       Console.WriteLine ("REN:     " + sma.RequestElementName);\r
-\r
-                       if (sma.Use != SoapBindingUse.Literal)\r
-                               throw new Exception ("Soap Section 5 Encoding not supported");\r
+                       BindingInfo bnd = (BindingInfo) type_info.Bindings [0];\r
+                       \r
+                       DiscoveryClientProtocol discoverer = new DiscoveryClientProtocol ();\r
+                       discoverer.Discover (Url);\r
+                       \r
+                       foreach (object info in discoverer.AdditionalInformation)\r
+                       {\r
+                               System.Web.Services.Discovery.SoapBinding sb = info as System.Web.Services.Discovery.SoapBinding;\r
+                               if (sb != null && sb.Binding.Name == bnd.Name && sb.Binding.Namespace == bnd.Namespace) {\r
+                                       Url = sb.Address;\r
+                                       return;\r
+                               }\r
+                       }\r
                        \r
-                       SoapClientMessage message = new SoapClientMessage (\r
-                               this, sma, new LogicalMethodInfo (mi), sma.OneWay, Url, parameters);\r
+                       string msg = string.Format ("The binding named '{0}' from namespace '{1}' was not found in the discovery document at '{2}'", bnd.Name, bnd.Namespace, Url);\r
+                       throw new Exception (msg);\r
+               }\r
 \r
-                       return message;\r
+               protected override WebRequest GetWebRequest (Uri uri)\r
+               {\r
+                       return base.GetWebRequest (uri);\r
                }\r
 \r
-               const string soap_envelope = "http://schemas.xmlsoap.org/soap/envelope/";\r
-               \r
-               void WriteSoapEnvelope (XmlTextWriter xtw)\r
+               WebRequest GetRequestForMessage (Uri uri, SoapClientMessage message)\r
                {\r
-                       xtw.WriteStartDocument ();\r
-                       \r
-                       xtw.WriteStartElement ("soap", "Envelope", soap_envelope);\r
-                       xtw.WriteAttributeString ("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance");\r
-                       xtw.WriteAttributeString ("xmlns", "xsd", null, "http://www.w3.org/2001/XMLSchema");\r
+                       WebRequest request = GetWebRequest (uri);\r
+                       request.Method = "POST";\r
+                       WebHeaderCollection headers = request.Headers;\r
+                       headers.Add ("SOAPAction", "\"" + message.Action + "\"");\r
+                       request.ContentType = message.ContentType + "; charset=utf-8";\r
+                       return request;\r
                }\r
                \r
-               void SendMessage (WebRequest request, SoapClientMessage message)\r
+               void SendRequest (Stream s, SoapClientMessage message, SoapExtension[] extensions)\r
                {\r
-                       WebHeaderCollection headers = request.Headers;\r
-                       headers.Add ("SOAPAction", message.Action);\r
+                       using (s) {\r
 \r
-                       request.ContentType = message.ContentType + "; charset=utf-8";\r
+                               if (extensions != null) {\r
+                                       s = SoapExtension.ExecuteChainStream (extensions, s);\r
+                                       message.SetStage (SoapMessageStage.BeforeSerialize);\r
+                                       SoapExtension.ExecuteProcessMessage (extensions, message, true);
+                               }
 \r
-                       using (Stream s = request.GetRequestStream ()){\r
-                               // serialize arguments\r
+                               // What a waste of UTF8encoders, but it has to be thread safe.\r
+                               XmlTextWriter xtw = new XmlTextWriter (s, new UTF8Encoding (false));\r
+\r
+                               WebServiceHelper.WriteSoapMessage (xtw, type_info, message.MethodStubInfo.Use, message.MethodStubInfo.RequestSerializer, message.Parameters, message.Headers);\r
+\r
+                               if (extensions != null) {\r
+                                       message.SetStage (SoapMessageStage.AfterSerialize);\r
+                                       SoapExtension.ExecuteProcessMessage (extensions, message, true);
+                               }
 \r
-                               XmlTextWriter xtw = new XmlTextWriter (s, Encoding.UTF8);\r
-                               WriteSoapEnvelope (xtw);\r
-                               xtw.WriteStartElement ("soap", "Body", soap_envelope);\r
-                               \r
-                               // Serialize arguments here\r
-                               \r
-                               xtw.WriteEndElement ();\r
-                               xtw.WriteEndElement ();\r
                                xtw.Flush ();\r
                                xtw.Close ();\r
                         }\r
                }\r
-               \r
-               [MethodImplAttribute(MethodImplOptions.NoInlining)]\r
-               protected object[] Invoke (string method_name, object[] parameters)\r
+\r
+\r
+               //\r
+               // TODO:\r
+               //    Handle other web responses (multi-output?)\r
+               //    \r
+               object [] ReceiveResponse (WebResponse response, SoapClientMessage message, SoapExtension[] extensions)\r
                {\r
-                       SoapClientMessage message = CreateMessage (method_name, parameters);\r
-                       WebRequest request = GetWebRequest (uri);\r
+                       SoapMethodStubInfo msi = message.MethodStubInfo;\r
+                       HttpWebResponse http_response = response as HttpWebResponse;\r
+                       \r
+                       if (http_response != null)\r
+                       {\r
+                               HttpStatusCode code = http_response.StatusCode;\r
+       \r
+                               if (!(code == HttpStatusCode.Accepted || code == HttpStatusCode.OK || code == HttpStatusCode.InternalServerError))\r
+                                       throw new WebException ("Request error. Return code was: " + http_response.StatusCode);\r
+                       }\r
+                       \r
+                       //\r
+                       // Remove optional encoding\r
+                       //\r
+                       string ctype;
+                       Encoding encoding = WebServiceHelper.GetContentEncoding (response.ContentType, out ctype);\r
+                       if (ctype != "text/xml")
+                               WebServiceHelper.InvalidOperation (
+                                       "Content is not 'text/xml' but '" + response.ContentType + "'",
+                                       response, encoding);
+\r
+                       message.ContentType = ctype;\r
+                       message.ContentEncoding = encoding.WebName;\r
+                       \r
+                       Stream stream = response.GetResponseStream ();\r
+\r
+                       if (extensions != null) {\r
+                               stream = SoapExtension.ExecuteChainStream (extensions, stream);\r
+                               message.SetStage (SoapMessageStage.BeforeDeserialize);\r
+                               SoapExtension.ExecuteProcessMessage (extensions, message, false);
+                       }
+                       \r
+                       // Deserialize the response\r
+\r
+                       StreamReader reader = new StreamReader (stream, encoding, false);\r
+                       XmlTextReader xml_reader = new XmlTextReader (reader);\r
+\r
+                       SoapHeaderCollection headers;\r
+                       object content;\r
+\r
+                       WebServiceHelper.ReadSoapMessage (xml_reader, type_info, msi.Use, msi.ResponseSerializer, out content, out headers);\r
+                       \r
+                       if (content is Fault)\r
+                       {\r
+                               Fault fault = (Fault) content;\r
+                               SoapException ex = new SoapException (fault.faultstring, fault.faultcode, fault.faultactor, fault.detail);\r
+                               message.SetException (ex);\r
+                       }\r
+                       else\r
+                               message.OutParameters = (object[]) content;\r
                        \r
-                       SendMessage (request, message);\r
+                       message.SetHeaders (headers);\r
+                       message.UpdateHeaderValues (this, message.MethodStubInfo.Headers);\r
+\r
+                       if (extensions != null) {\r
+                               message.SetStage (SoapMessageStage.AfterDeserialize);\r
+                               SoapExtension.ExecuteProcessMessage (extensions, message, false);
+                       }
+\r
+                       if (message.Exception == null)\r
+                               return message.OutParameters;\r
+                       else\r
+                               throw message.Exception;\r
+               }\r
+\r
+               protected object[] Invoke (string method_name, object[] parameters)\r
+               {\r
+                       SoapMethodStubInfo msi = (SoapMethodStubInfo) type_info.GetMethod (method_name);\r
                        \r
-                       return null;\r
+                       SoapClientMessage message = new SoapClientMessage (this, msi, Url, parameters);\r
+                       message.CollectHeaders (this, message.MethodStubInfo.Headers, SoapHeaderDirection.In);\r
+\r
+                       SoapExtension[] extensions = SoapExtension.CreateExtensionChain (type_info.SoapExtensions[0], msi.SoapExtensions, type_info.SoapExtensions[1]);\r
+\r
+                       WebResponse response;\r
+                       try\r
+                       {\r
+                               WebRequest request = GetRequestForMessage (uri, message);\r
+                               SendRequest (request.GetRequestStream (), message, extensions);\r
+                               response = GetWebResponse (request);\r
+                       }\r
+                       catch (WebException ex)\r
+                       {\r
+                               response = ex.Response;\r
+                               HttpWebResponse http_response = response as HttpWebResponse;\r
+                               if (http_response == null || http_response.StatusCode != HttpStatusCode.InternalServerError)\r
+                                       throw ex;\r
+                       }\r
+\r
+                       return ReceiveResponse (response, message, extensions);\r
                }\r
 \r
                #endregion // Methods\r
        }\r
-}\r
+}