2008-07-01 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.Web.Services / System.Web.Services.Protocols / HttpSoapWebServiceHandler.cs
1 //
2 // System.Web.Services.Protocols.HttpSoapWebServiceHandler.cs
3 //
4 // Author:
5 //   Lluis Sanchez Gual (lluis@ximian.com)
6 //
7 // Copyright (C) Ximian, Inc. 2003
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System;
32 using System.Net;
33 using System.Web;
34 using System.Xml;
35 using System.Text;
36 using System.IO;
37 using System.Reflection;
38 using System.Xml.Serialization;
39 using System.Web.Services.Description;
40
41 namespace System.Web.Services.Protocols
42 {
43         internal class HttpSoapWebServiceHandler: WebServiceHandler
44         {
45                 SoapTypeStubInfo _typeStubInfo;
46                 SoapExtension[] _extensionChainHighPrio;
47                 SoapExtension[] _extensionChainMedPrio;
48                 SoapExtension[] _extensionChainLowPrio;
49                 SoapMethodStubInfo methodInfo;
50                 SoapServerMessage requestMessage = null;
51
52                 public HttpSoapWebServiceHandler (Type type): base (type)
53                 {
54                         _typeStubInfo = (SoapTypeStubInfo) TypeStubManager.GetTypeStub (ServiceType, "Soap");
55                 }
56
57                 public override bool IsReusable 
58                 {
59                         get { return false; }
60                 }
61
62                 internal override MethodStubInfo GetRequestMethod (HttpContext context)
63                 {
64                         try
65                         {
66                                 requestMessage = DeserializeRequest (context);
67                                 return methodInfo;
68                         }
69                         catch (Exception ex)
70                         {
71                                 SerializeFault (context, requestMessage, ex);
72                                 return null;
73                         }
74                 }
75
76                 public override void ProcessRequest (HttpContext context)
77                 {
78                         Context = context;
79                         SoapServerMessage responseMessage = null;
80
81                         try
82                         {
83                                 if (requestMessage == null) {
84                                         requestMessage = DeserializeRequest (context);
85                                 }
86                                 
87                                 if (methodInfo != null && methodInfo.OneWay) {
88                                         context.Response.BufferOutput = false;
89                                         context.Response.StatusCode = 202;
90                                         context.Response.Flush ();
91                                         context.Response.Close ();
92                                         Invoke (context, requestMessage);
93                                 } else {
94                                         responseMessage = Invoke (context, requestMessage);
95                                         SerializeResponse (context.Response, responseMessage);
96                                 }
97                         }
98                         catch (Exception ex)
99                         {
100                                 if (methodInfo != null && methodInfo.OneWay) {
101                                         context.Response.StatusCode = 500;
102                                         context.Response.Flush ();
103                                         context.Response.Close ();
104                                 } else {
105                                         SerializeFault (context, requestMessage, ex);
106                                 }
107                         }
108                         finally {
109                                 IDisposable disp = requestMessage.Server as IDisposable;
110                                 requestMessage = null;
111                                 if (disp != null)
112                                         disp.Dispose();
113                         }
114                 }
115
116                 SoapServerMessage DeserializeRequest (HttpContext context)
117                 {
118                         HttpRequest request = context.Request;
119                         Stream stream = request.InputStream;
120
121                         //using (stream)
122                         //{
123                                 string soapAction = null;
124                                 string ctype;
125                                 Encoding encoding = WebServiceHelper.GetContentEncoding (request.ContentType, out ctype);
126 #if NET_2_0
127                                 if (ctype != "text/xml" && ctype != "application/soap+xml")
128 #else
129                                 if (ctype != "text/xml")
130 #endif
131                                         throw new WebException ("Content is not XML: " + ctype);
132                                         
133                                 object server = CreateServerInstance ();
134
135                                 SoapServerMessage message = new SoapServerMessage (request, server, stream);
136                                 message.SetStage (SoapMessageStage.BeforeDeserialize);
137                                 message.ContentType = ctype;
138 #if NET_2_0
139                                 object soapVer = context.Items ["WebServiceSoapVersion"];
140                                 if (soapVer != null)
141                                         message.SetSoapVersion ((SoapProtocolVersion) soapVer);
142 #endif
143
144                                 // If the routing style is SoapAction, then we can get the method information now
145                                 // and set it to the SoapMessage
146
147                                 if (_typeStubInfo.RoutingStyle == SoapServiceRoutingStyle.SoapAction)
148                                 {
149                                         string headerName = message.IsSoap12 ? "action" : "SOAPAction";
150                                         soapAction = message.IsSoap12 ? WebServiceHelper.GetContextAction(request.ContentType) : request.Headers [headerName];
151                                         if (soapAction == null) {
152                                                 if (!message.IsSoap12)
153                                                         throw new SoapException ("Missing SOAPAction header", WebServiceHelper.ClientFaultCode (message.IsSoap12));
154                                         }
155                                         else
156                                         {
157                                         methodInfo = _typeStubInfo.GetMethodForSoapAction (soapAction);
158                                         if (methodInfo == null) throw new SoapException ("Server did not recognize the value of HTTP header " + headerName + ": " + soapAction, WebServiceHelper.ClientFaultCode (message.IsSoap12));
159                                         message.MethodStubInfo = methodInfo;
160                                         }
161                                 }
162
163                                 // Execute the high priority global extensions. Do not try to execute the medium and
164                                 // low priority extensions because if the routing style is RequestElement we still
165                                 // don't have method information
166
167                                 _extensionChainHighPrio = SoapExtension.CreateExtensionChain (_typeStubInfo.SoapExtensions[0]);
168                                 stream = SoapExtension.ExecuteChainStream (_extensionChainHighPrio, stream);
169                                 SoapExtension.ExecuteProcessMessage (_extensionChainHighPrio, message, stream, false);
170
171                                 // If the routing style is RequestElement, try to get the method name from the
172                                 // stream processed by the high priority extensions
173
174                                 if (_typeStubInfo.RoutingStyle == SoapServiceRoutingStyle.RequestElement || (message.IsSoap12 && soapAction == null))
175                                 {
176                                         MemoryStream mstream;
177                                         byte[] buffer = null;
178
179                                         if (stream.CanSeek)
180                                         {
181                                                 buffer = new byte [stream.Length];
182                                                 for (int n=0; n<stream.Length;)
183                                                         n += stream.Read (buffer, n, (int)stream.Length-n);
184                                                 mstream = new MemoryStream (buffer);
185                                         }
186                                         else
187                                         {
188                                                 buffer = new byte [500];
189                                                 mstream = new MemoryStream ();
190                                         
191                                                 int len;
192                                                 while ((len = stream.Read (buffer, 0, 500)) > 0)
193                                                         mstream.Write (buffer, 0, len);
194                                                 mstream.Position = 0;
195                                                 buffer = mstream.ToArray ();
196                                         }
197
198                                         soapAction = ReadActionFromRequestElement (new MemoryStream (buffer), encoding, message.IsSoap12);
199
200                                         stream = mstream;
201                                         methodInfo = (SoapMethodStubInfo) _typeStubInfo.GetMethod (soapAction);
202                                         message.MethodStubInfo = methodInfo;
203                                 }
204
205                                 // Whatever routing style we used, we should now have the method information.
206                                 // We can now notify the remaining extensions
207
208                                 if (methodInfo == null) throw new SoapException ("Method '" + soapAction + "' not defined in the web service '" + _typeStubInfo.LogicalType.WebServiceName + "'", WebServiceHelper.ClientFaultCode (message.IsSoap12));
209
210                                 _extensionChainMedPrio = SoapExtension.CreateExtensionChain (methodInfo.SoapExtensions);
211                                 _extensionChainLowPrio = SoapExtension.CreateExtensionChain (_typeStubInfo.SoapExtensions[1]);
212
213                                 stream = SoapExtension.ExecuteChainStream (_extensionChainMedPrio, stream);
214                                 stream = SoapExtension.ExecuteChainStream (_extensionChainLowPrio, stream);
215                                 SoapExtension.ExecuteProcessMessage (_extensionChainMedPrio, message, stream, false);
216                                 SoapExtension.ExecuteProcessMessage (_extensionChainLowPrio, message, stream, false);
217
218                                 // Deserialize the request
219
220                                 StreamReader reader = new StreamReader (stream, encoding, false);
221                                 XmlTextReader xmlReader = new XmlTextReader (reader);
222
223                                 try
224                                 {
225                                         object content;
226                                         SoapHeaderCollection headers;
227                                         WebServiceHelper.ReadSoapMessage (xmlReader, methodInfo, SoapHeaderDirection.In, message.IsSoap12, out content, out headers);
228                                         message.InParameters = (object []) content;
229                                         message.SetHeaders (headers);
230                                 }
231                                 catch (Exception ex)
232                                 {
233                                         throw new SoapException ("Could not deserialize Soap message", WebServiceHelper.ClientFaultCode (message.IsSoap12), ex);
234                                 }
235
236                                 // Notify the extensions after deserialization
237
238                                 message.SetStage (SoapMessageStage.AfterDeserialize);
239                                 SoapExtension.ExecuteProcessMessage (_extensionChainHighPrio, message, stream, false);
240                                 SoapExtension.ExecuteProcessMessage (_extensionChainMedPrio, message, stream, false);
241                                 SoapExtension.ExecuteProcessMessage (_extensionChainLowPrio, message, stream, false);
242
243                                 return message;
244                         //}
245                 }
246
247                 string ReadActionFromRequestElement (Stream stream, Encoding encoding, bool soap12)
248                 {
249                         string envNS = soap12 ?
250                                 WebServiceHelper.Soap12EnvelopeNamespace :
251                                 WebServiceHelper.SoapEnvelopeNamespace;
252                         try
253                         {
254                                 StreamReader reader = new StreamReader (stream, encoding, false);
255                                 XmlTextReader xmlReader = new XmlTextReader (reader);
256
257                                 xmlReader.MoveToContent ();
258                                 xmlReader.ReadStartElement ("Envelope", envNS);
259
260                                 while (! (xmlReader.NodeType == XmlNodeType.Element && xmlReader.LocalName == "Body" && xmlReader.NamespaceURI == envNS))
261                                         xmlReader.Skip ();
262
263                                 xmlReader.ReadStartElement ("Body", envNS);
264                                 xmlReader.MoveToContent ();
265
266                                 return xmlReader.LocalName;
267                         }
268                         catch (Exception ex)
269                         {
270                                 string errmsg = "The root element for the request could not be determined. ";
271                                 errmsg += "When RoutingStyle is set to RequestElement, SoapExtensions configured ";
272                                 errmsg += "via an attribute on the method cannot modify the request stream before it is read. ";
273                                 errmsg += "The extension must be configured via the SoapExtensionTypes element in web.config ";
274                                 errmsg += "or the request must arrive at the server as clear text.";
275                                 throw new SoapException (errmsg, WebServiceHelper.ServerFaultCode (soap12), ex);
276                         }
277                 }
278
279                 void SerializeResponse (HttpResponse response, SoapServerMessage message)
280                 {
281                         SoapMethodStubInfo methodInfo = message.MethodStubInfo;
282                         
283                         if ((message.ContentEncoding != null) && (message.ContentEncoding.Length > 0))
284                                 response.AppendHeader("Content-Encoding", message.ContentEncoding);
285
286                         response.ContentType = message.IsSoap12 ?
287                                 "application/soap+xml; charset=utf-8" :
288                                 "text/xml; charset=utf-8";
289                         if (message.Exception != null) response.StatusCode = 500;
290
291                         Stream responseStream = response.OutputStream;
292                         Stream outStream = responseStream;
293                         bool bufferResponse = (methodInfo == null || methodInfo.MethodAttribute.BufferResponse);
294                         response.BufferOutput = bufferResponse;
295
296                         try
297                         {
298                                 // While serializing, process extensions in reverse order
299
300                                 if (bufferResponse)
301                                 {
302                                         outStream = SoapExtension.ExecuteChainStream (_extensionChainHighPrio, outStream);
303                                         outStream = SoapExtension.ExecuteChainStream (_extensionChainMedPrio, outStream);
304                                         outStream = SoapExtension.ExecuteChainStream (_extensionChainLowPrio, outStream);
305         
306                                         message.SetStage (SoapMessageStage.BeforeSerialize);
307                                         SoapExtension.ExecuteProcessMessage (_extensionChainLowPrio, message, outStream, true);
308                                         SoapExtension.ExecuteProcessMessage (_extensionChainMedPrio, message, outStream, true);
309                                         SoapExtension.ExecuteProcessMessage (_extensionChainHighPrio, message, outStream, true);
310                                 }
311                                 
312                                 XmlTextWriter xtw = WebServiceHelper.CreateXmlWriter (outStream);
313
314
315                                 if (message.Exception == null)
316                                         WebServiceHelper.WriteSoapMessage (xtw, methodInfo, SoapHeaderDirection.Out, message.OutParameters, message.Headers, message.IsSoap12);
317                                 else if (methodInfo != null) {
318 #if NET_2_0
319                                         if (message.IsSoap12)
320                                                 WebServiceHelper.WriteSoapMessage (xtw, methodInfo, SoapHeaderDirection.Fault, new Soap12Fault (message.Exception), message.Headers, message.IsSoap12);
321                                         else
322 #endif
323                                         {
324                                                 WebServiceHelper.WriteSoapMessage (xtw, methodInfo, SoapHeaderDirection.Fault, new Fault (message.Exception), message.Headers, message.IsSoap12);
325                                         }
326                                 }
327                                 else {
328 #if NET_2_0
329                                         if (message.IsSoap12)
330                                                 WebServiceHelper.WriteSoapMessage (xtw, SoapBindingUse.Literal, Soap12Fault.Serializer, null, new Soap12Fault (message.Exception), null, message.IsSoap12);
331                                         else
332 #endif
333                                         {
334                                                 WebServiceHelper.WriteSoapMessage (xtw, SoapBindingUse.Literal, Fault.Serializer, null, new Fault (message.Exception), null, message.IsSoap12);
335                                         }
336                                 }
337
338                                 if (bufferResponse)
339                                 {
340                                         message.SetStage (SoapMessageStage.AfterSerialize);
341                                         SoapExtension.ExecuteProcessMessage (_extensionChainLowPrio, message, outStream, true);
342                                         SoapExtension.ExecuteProcessMessage (_extensionChainMedPrio, message, outStream, true);
343                                         SoapExtension.ExecuteProcessMessage (_extensionChainHighPrio, message, outStream, true);
344                                 }
345                                 
346                                 xtw.Flush ();
347                         }
348                         catch (Exception ex)
349                         {
350                                 // If the response is buffered, we can discard the response and
351                                 // serialize a new Fault message as response.
352                                 if (bufferResponse) throw ex;
353                                 
354                                 // If it is not buffered, we can't rollback what has been sent,
355                                 // so we can only close the connection and return.
356                                 responseStream.Close ();
357                                 return;
358                         }
359                 }
360
361                 void SerializeFault (HttpContext context, SoapServerMessage requestMessage, Exception ex)
362                 {
363                         SoapException soex = ex as SoapException;
364                         if (soex == null) soex = new SoapException (ex.Message, WebServiceHelper.ServerFaultCode (requestMessage != null && requestMessage.IsSoap12), ex);
365
366                         SoapServerMessage faultMessage;
367                         if (requestMessage != null)
368                                 faultMessage = new SoapServerMessage (context.Request, soex, requestMessage.MethodStubInfo, requestMessage.Server, requestMessage.Stream);
369                         else
370                                 faultMessage = new SoapServerMessage (context.Request, soex, null, null, null);
371 #if NET_2_0
372                         object soapVer = context.Items ["WebServiceSoapVersion"];
373                         if (soapVer != null)
374                                 faultMessage.SetSoapVersion ((SoapProtocolVersion) soapVer);
375 #endif
376
377                         SerializeResponse (context.Response, faultMessage);
378                         context.Response.End ();
379                         return;
380                 }
381                 
382                 private SoapServerMessage Invoke (HttpContext ctx, SoapServerMessage requestMessage)
383                 {
384                         SoapMethodStubInfo methodInfo = requestMessage.MethodStubInfo;
385
386                         // Assign header values to web service members
387
388                         requestMessage.UpdateHeaderValues (requestMessage.Server, methodInfo.Headers);
389
390                         // Fill an array with the input parameters at the right position
391
392                         object[] parameters = new object[methodInfo.MethodInfo.Parameters.Length];
393                         ParameterInfo[] inParams = methodInfo.MethodInfo.InParameters;
394                         for (int n=0; n<inParams.Length; n++)
395                                 parameters [inParams[n].Position] = requestMessage.InParameters [n];
396
397                         // Invoke the method
398
399                         try
400                         {
401                                 object[] results = methodInfo.MethodInfo.Invoke (requestMessage.Server, parameters);
402                                 requestMessage.OutParameters = results;
403                         }
404                         catch (TargetInvocationException ex)
405                         {
406                                 throw ex.InnerException;
407                         }
408
409                         // Check that headers with MustUnderstand flag have been understood
410                         
411                         foreach (SoapHeader header in requestMessage.Headers)
412                         {
413                                 if (header.MustUnderstand && !header.DidUnderstand)
414                                         throw new SoapHeaderException ("Header not understood: " + header.GetType(), WebServiceHelper.MustUnderstandFaultCode (requestMessage.IsSoap12));
415                         }
416
417                         // Collect headers that must be sent to the client
418                         requestMessage.CollectHeaders (requestMessage.Server, methodInfo.Headers, SoapHeaderDirection.Out);
419
420                         return requestMessage;
421                 }
422         }
423 }