2008-07-01 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.Web.Services / System.Web.Services.Protocols / SoapHttpClientProtocol.cs
1 // 
2 // System.Web.Services.Protocols.SoapHttpClientProtocol.cs
3 //
4 // Author:
5 //   Tim Coleman (tim@timcoleman.com)
6 //   Miguel de Icaza (miguel@ximian.com)
7 //   Lluis Sanchez Gual (lluis@ximian.com)
8 //
9 // Copyright (C) Tim Coleman, 2002
10 // Copyright (C) Ximian, Inc, 2003
11 //
12
13 //
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 // 
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 // 
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33
34 using System.ComponentModel;
35 using System.Globalization;
36 using System.IO;
37 using System.Net;
38 using System.Web;
39 using System.Xml;
40 using System.Text;
41 using System.Reflection;
42 using System.Web.Services;
43 using System.Diagnostics;
44 using System.Runtime.CompilerServices;
45 using System.Web.Services.Description;
46 using System.Web.Services.Discovery;
47 using System.Xml.Serialization;
48 using System.Xml.Schema;
49 using System.Collections;
50 using System.Threading;
51
52 namespace System.Web.Services.Protocols 
53 {
54 #if NET_2_0
55         [System.Runtime.InteropServices.ComVisible (true)]
56 #endif
57         public class SoapHttpClientProtocol : HttpWebClientProtocol 
58         {
59                 SoapTypeStubInfo type_info;
60 #if NET_2_0
61                 SoapProtocolVersion soapVersion;
62 #endif
63
64                 #region SoapWebClientAsyncResult class
65
66                 internal class SoapWebClientAsyncResult: WebClientAsyncResult
67                 {
68                         public SoapWebClientAsyncResult (WebRequest request, AsyncCallback callback, object asyncState)
69                         : base (request, callback, asyncState)
70                         {
71                         }
72                 
73                         public SoapClientMessage Message;
74                         public SoapExtension[] Extensions;
75                 }
76                 #endregion
77
78                 #region Constructors
79
80                 public SoapHttpClientProtocol () 
81                 {
82                         type_info = (SoapTypeStubInfo) TypeStubManager.GetTypeStub (this.GetType (), "Soap");
83                 }
84
85                 #endregion // Constructors
86
87                 #region Methods
88
89                 protected IAsyncResult BeginInvoke (string methodName, object[] parameters, AsyncCallback callback, object asyncState)
90                 {
91                         SoapMethodStubInfo msi = (SoapMethodStubInfo) type_info.GetMethod (methodName);
92
93                         SoapWebClientAsyncResult ainfo = null;
94                         try
95                         {
96                                 SoapClientMessage message = new SoapClientMessage (this, msi, Url, parameters);
97                                 message.CollectHeaders (this, message.MethodStubInfo.Headers, SoapHeaderDirection.In);
98                                 
99                                 WebRequest request = GetRequestForMessage (uri, message);
100                                 
101                                 ainfo = new SoapWebClientAsyncResult (request, callback, asyncState);
102                                 ainfo.Message = message;
103                                 ainfo.Extensions = SoapExtension.CreateExtensionChain (type_info.SoapExtensions[0], msi.SoapExtensions, type_info.SoapExtensions[1]);
104
105                                 ainfo.Request.BeginGetRequestStream (new AsyncCallback (AsyncGetRequestStreamDone), ainfo);
106                                 RegisterMapping (asyncState, ainfo);
107                         }
108                         catch (Exception ex)
109                         {
110                                 if (ainfo != null)
111                                         ainfo.SetCompleted (null, ex, false);
112                         }
113
114                         return ainfo;
115                 }
116
117                 void AsyncGetRequestStreamDone (IAsyncResult ar)
118                 {
119                         SoapWebClientAsyncResult ainfo = (SoapWebClientAsyncResult) ar.AsyncState;
120                         try
121                         {
122                                 SendRequest (ainfo.Request.EndGetRequestStream (ar), ainfo.Message, ainfo.Extensions);
123                                 ainfo.Request.BeginGetResponse (new AsyncCallback (AsyncGetResponseDone), ainfo);
124                         }
125                         catch (Exception ex)
126                         {
127                                 ainfo.SetCompleted (null, ex, true);
128                         }
129                 }
130
131                 void AsyncGetResponseDone (IAsyncResult ar)
132                 {
133                         SoapWebClientAsyncResult ainfo = (SoapWebClientAsyncResult) ar.AsyncState;
134                         WebResponse response = null;
135
136                         try {
137                                 response = GetWebResponse (ainfo.Request, ar);
138                         }
139                         catch (WebException ex) {
140                                 response = ex.Response;
141                                 HttpWebResponse http_response = response as HttpWebResponse;
142                                 if (http_response == null || http_response.StatusCode != HttpStatusCode.InternalServerError) {
143                                         ainfo.SetCompleted (null, ex, true);
144                                         return;
145                                 }
146                         }
147                         catch (Exception ex) {
148                                 ainfo.SetCompleted (null, ex, true);
149                                 return;
150                         }
151
152                         try {
153                                 object[] result = ReceiveResponse (response, ainfo.Message, ainfo.Extensions);
154                                 ainfo.SetCompleted (result, null, true);
155                         }
156                         catch (Exception ex) {
157                                 ainfo.SetCompleted (null, ex, true);
158                         }
159                         finally {
160                                 response.Close();
161                         }
162                 }
163
164                 protected object[] EndInvoke (IAsyncResult asyncResult)
165                 {
166                         if (!(asyncResult is SoapWebClientAsyncResult)) throw new ArgumentException ("asyncResult is not the return value from BeginInvoke");
167
168                         SoapWebClientAsyncResult ainfo = (SoapWebClientAsyncResult) asyncResult;
169                         lock (ainfo)
170                         {
171                                 if (!ainfo.IsCompleted)
172                                         ainfo.WaitForComplete ();
173
174                                 UnregisterMapping (ainfo.AsyncState);
175                                 
176                                 if (ainfo.Exception != null)
177                                         throw ainfo.Exception;
178                                 else
179                                         return (object[]) ainfo.Result;
180                         }
181                 }
182
183                 public void Discover ()
184                 {
185                         BindingInfo bnd = (BindingInfo) type_info.Bindings [0];
186                         
187                         DiscoveryClientProtocol discoverer = new DiscoveryClientProtocol ();
188                         discoverer.Discover (Url);
189                         
190                         foreach (object info in discoverer.AdditionalInformation)
191                         {
192                                 System.Web.Services.Discovery.SoapBinding sb = info as System.Web.Services.Discovery.SoapBinding;
193                                 if (sb != null && sb.Binding.Name == bnd.Name && sb.Binding.Namespace == bnd.Namespace) {
194                                         Url = sb.Address;
195                                         return;
196                                 }
197                         }
198                         
199                         string msg = string.Format (
200                                 "The binding named '{0}' from namespace '{1}' was not found in the discovery document at '{2}'",
201                                 bnd.Name, bnd.Namespace, Url);
202                         throw new Exception (msg);
203                 }
204
205                 protected override WebRequest GetWebRequest (Uri uri)
206                 {
207                         return base.GetWebRequest (uri);
208                 }
209
210                 WebRequest GetRequestForMessage (Uri uri, SoapClientMessage message)
211                 {
212                         WebRequest request = GetWebRequest (uri);
213                         request.Method = "POST";
214                         WebHeaderCollection headers = request.Headers;
215                         if (!message.IsSoap12)
216                                 headers.Add ("SOAPAction", "\"" + message.Action + "\"");
217                         request.ContentType = message.ContentType + "; charset=utf-8";
218                         return request;
219                 }
220
221 #if NET_2_0
222                 [MonoTODO]
223                 protected virtual
224 #endif
225                 XmlReader GetReaderForMessage (
226                         SoapClientMessage message, int bufferSize)
227                 {
228                         throw new NotImplementedException ();
229                 }
230
231 #if NET_2_0
232                 [MonoTODO]
233                 protected virtual
234 #endif
235                 XmlWriter GetWriterForMessage (
236                         SoapClientMessage message, int bufferSize)
237                 {
238                         throw new NotImplementedException ();
239                 }
240
241                 void SendRequest (Stream s, SoapClientMessage message, SoapExtension[] extensions)
242                 {
243                         using (s) {
244
245                                 if (extensions != null) {
246                                         s = SoapExtension.ExecuteChainStream (extensions, s);
247                                         message.SetStage (SoapMessageStage.BeforeSerialize);
248                                         SoapExtension.ExecuteProcessMessage (extensions, message, s, true);
249                                 }
250
251                                 XmlTextWriter xtw = WebServiceHelper.CreateXmlWriter (s);
252                                 WebServiceHelper.WriteSoapMessage (xtw, message.MethodStubInfo, SoapHeaderDirection.In, message.Parameters, message.Headers, message.IsSoap12);
253
254                                 if (extensions != null) {
255                                         message.SetStage (SoapMessageStage.AfterSerialize);
256                                         SoapExtension.ExecuteProcessMessage (extensions, message, s, true);
257                                 }
258
259                                 xtw.Flush ();
260                                 xtw.Close ();
261                          }
262                 }
263
264
265                 //
266                 // TODO:
267                 //    Handle other web responses (multi-output?)
268                 //    
269                 object [] ReceiveResponse (WebResponse response, SoapClientMessage message, SoapExtension[] extensions)
270                 {
271                         SoapMethodStubInfo msi = message.MethodStubInfo;
272                         HttpWebResponse http_response = response as HttpWebResponse;
273
274                         if (http_response != null)
275                         {
276                                 HttpStatusCode code = http_response.StatusCode;
277         
278                                 if (!(code == HttpStatusCode.Accepted || code == HttpStatusCode.OK || code == HttpStatusCode.InternalServerError)) {
279                                         string msg = "The request failed with HTTP status {0}: {1}";
280                                         msg = String.Format (msg, (int) code, code);
281                                         throw new WebException (msg, null, WebExceptionStatus.ProtocolError, http_response);
282                                 }
283                                 if (message.OneWay && response.ContentLength <= 0 && (code == HttpStatusCode.Accepted || code == HttpStatusCode.OK)) {
284                                         return new object[0];
285                                 }
286                         }
287                         
288                         //
289                         // Remove optional encoding
290                         //
291                         string ctype;
292                         Encoding encoding = WebServiceHelper.GetContentEncoding (response.ContentType, out ctype);
293                         ctype = ctype.ToLower (CultureInfo.InvariantCulture);
294 #if NET_2_0
295                         if ((!message.IsSoap12 || ctype != "application/soap+xml") && ctype != "text/xml")
296 #else
297                         if (ctype != "text/xml")
298 #endif
299                                 WebServiceHelper.InvalidOperation (
300                                         String.Format ("Not supported Content-Type in the response: '{0}'", response.ContentType),
301                                         response, encoding);
302
303                         message.ContentType = ctype;
304                         message.ContentEncoding = encoding.WebName;
305                         
306                         Stream stream = response.GetResponseStream ();
307
308                         if (extensions != null) {
309                                 stream = SoapExtension.ExecuteChainStream (extensions, stream);
310                                 message.SetStage (SoapMessageStage.BeforeDeserialize);
311                                 SoapExtension.ExecuteProcessMessage (extensions, message, stream, false);
312                         }
313                         
314                         // Deserialize the response
315
316                         SoapHeaderCollection headers;
317                         object content;
318
319                         using (StreamReader reader = new StreamReader (stream, encoding, false)) {
320                                 XmlTextReader xml_reader = new XmlTextReader (reader);
321
322                                 WebServiceHelper.ReadSoapMessage (xml_reader, msi, SoapHeaderDirection.Out, message.IsSoap12, out content, out headers);
323                         }
324
325 #if NET_2_0
326                         if (content is Soap12Fault) {
327                                 SoapException ex = WebServiceHelper.Soap12FaultToSoapException ((Soap12Fault) content);
328                                 message.SetException (ex);
329                         }
330                         else
331 #endif
332                         if (content is Fault) {
333                                 Fault fault = (Fault) content;
334                                 SoapException ex = new SoapException (fault.faultstring, fault.faultcode, fault.faultactor, fault.detail);
335                                 message.SetException (ex);
336                         }
337                         else
338                                 message.OutParameters = (object[]) content;
339                         
340                         message.SetHeaders (headers);
341                         message.UpdateHeaderValues (this, message.MethodStubInfo.Headers);
342
343                         if (extensions != null) {
344                                 message.SetStage (SoapMessageStage.AfterDeserialize);
345                                 SoapExtension.ExecuteProcessMessage (extensions, message, stream, false);
346                         }
347
348                         if (message.Exception == null)
349                                 return message.OutParameters;
350                         else
351                                 throw message.Exception;
352                 }
353
354                 protected object[] Invoke (string method_name, object[] parameters)
355                 {
356                         SoapMethodStubInfo msi = (SoapMethodStubInfo) type_info.GetMethod (method_name);
357                         
358                         SoapClientMessage message = new SoapClientMessage (this, msi, Url, parameters);
359                         message.CollectHeaders (this, message.MethodStubInfo.Headers, SoapHeaderDirection.In);
360
361                         SoapExtension[] extensions = SoapExtension.CreateExtensionChain (type_info.SoapExtensions[0], msi.SoapExtensions, type_info.SoapExtensions[1]);
362
363                         WebResponse response;
364                         try
365                         {
366                                 WebRequest request = GetRequestForMessage (uri, message);
367                                 SendRequest (request.GetRequestStream (), message, extensions);
368                                 response = GetWebResponse (request);
369                         }
370                         catch (WebException ex)
371                         {
372                                 response = ex.Response;
373                                 HttpWebResponse http_response = response as HttpWebResponse;
374                                 if (http_response == null || http_response.StatusCode != HttpStatusCode.InternalServerError)
375                                         throw ex;
376                         }
377
378                         try {
379                                 return ReceiveResponse (response, message, extensions);
380                         }
381                         finally {
382                                 response.Close();
383                         }
384                 }
385                 
386 #if NET_2_0
387
388                 [MonoTODO ("Do something with this")]
389                 [System.Runtime.InteropServices.ComVisible(false)]
390                 [DefaultValue (SoapProtocolVersion.Default)]
391                 public SoapProtocolVersion SoapVersion {
392                         get { return soapVersion; }
393                         set { soapVersion = value; }
394                 }
395                 
396                 protected void InvokeAsync (string methodName, object[] parameters, SendOrPostCallback callback)
397                 {
398                         InvokeAsync (methodName, parameters, callback, null);
399                 }
400
401                 protected void InvokeAsync (string methodName, object[] parameters, SendOrPostCallback callback, object userState)
402                 {
403                         InvokeAsyncInfo info = new InvokeAsyncInfo (callback, userState);
404                         BeginInvoke (methodName, parameters, new AsyncCallback (InvokeAsyncCallback), info);
405                 }
406                 
407                 void InvokeAsyncCallback (IAsyncResult ar)
408                 {
409                         InvokeAsyncInfo info = (InvokeAsyncInfo) ar.AsyncState;
410                         SoapWebClientAsyncResult sar = (SoapWebClientAsyncResult) ar;
411                         InvokeCompletedEventArgs args = new InvokeCompletedEventArgs (sar.Exception, false, info.UserState, (object[]) sar.Result);
412                         if (info.Context != null)
413                                 info.Context.Send (info.Callback, args);
414                         else
415                                 info.Callback (args);
416                 }
417
418 #endif
419
420                 #endregion // Methods
421         }
422 }
423