2006-11-02 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                 object server;
52
53                 public HttpSoapWebServiceHandler (Type type): base (type)
54                 {
55                         _typeStubInfo = (SoapTypeStubInfo) TypeStubManager.GetTypeStub (ServiceType, "Soap");
56                 }
57
58                 public override bool IsReusable 
59                 {
60                         get { return false; }
61                 }
62
63                 internal override MethodStubInfo GetRequestMethod (HttpContext context)
64                 {
65                         try
66                         {
67                                 requestMessage = DeserializeRequest (context.Request);
68                                 return methodInfo;
69                         }
70                         catch (Exception ex)
71                         {
72                                 SerializeFault (context, requestMessage, ex);
73                                 return null;
74                         }
75                 }
76
77                 public override void ProcessRequest (HttpContext context)
78                 {
79                         Context = context;
80                         SoapServerMessage responseMessage = null;
81
82                         try
83                         {
84                                 if (requestMessage == null)
85                                         requestMessage = DeserializeRequest (context.Request);
86                                         
87                                 responseMessage = Invoke (context, requestMessage);
88                                 SerializeResponse (context.Response, responseMessage);
89                         }
90                         catch (Exception ex)
91                         {
92                                 SerializeFault (context, requestMessage, ex);
93                                 return;
94                         }
95                 }
96
97                 SoapServerMessage DeserializeRequest (HttpRequest request)
98                 {
99                         Stream stream = request.InputStream;
100
101                         using (stream)
102                         {
103                                 string soapAction = null;
104                                 string ctype;
105                                 Encoding encoding = WebServiceHelper.GetContentEncoding (request.ContentType, out ctype);
106                                 if (ctype != "text/xml")
107                                         throw new WebException ("Content is not XML: " + ctype);
108                                         
109                                 server = CreateServerInstance ();
110
111                                 SoapServerMessage message = new SoapServerMessage (request, server, stream);
112                                 message.SetStage (SoapMessageStage.BeforeDeserialize);
113                                 message.ContentType = ctype;
114                                 message.ContentEncoding = encoding.WebName;
115
116                                 // If the routing style is SoapAction, then we can get the method information now
117                                 // and set it to the SoapMessage
118
119                                 if (_typeStubInfo.RoutingStyle == SoapServiceRoutingStyle.SoapAction)
120                                 {
121                                         soapAction = request.Headers ["SOAPAction"];
122                                         if (soapAction == null) throw new SoapException ("Missing SOAPAction header", SoapException.ClientFaultCode);
123                                         methodInfo = _typeStubInfo.GetMethodForSoapAction (soapAction);
124                                         if (methodInfo == null) throw new SoapException ("Server did not recognize the value of HTTP header SOAPAction: " + soapAction, SoapException.ClientFaultCode);
125                                         message.MethodStubInfo = methodInfo;
126                                 }
127
128                                 // Execute the high priority global extensions. Do not try to execute the medium and
129                                 // low priority extensions because if the routing style is RequestElement we still
130                                 // don't have method information
131
132                                 _extensionChainHighPrio = SoapExtension.CreateExtensionChain (_typeStubInfo.SoapExtensions[0]);
133                                 stream = SoapExtension.ExecuteChainStream (_extensionChainHighPrio, stream);
134                                 SoapExtension.ExecuteProcessMessage (_extensionChainHighPrio, message, false);
135
136                                 // If the routing style is RequestElement, try to get the method name from the
137                                 // stream processed by the high priority extensions
138
139                                 if (_typeStubInfo.RoutingStyle == SoapServiceRoutingStyle.RequestElement)
140                                 {
141                                         MemoryStream mstream;
142                                         byte[] buffer = null;
143
144                                         if (stream.CanSeek)
145                                         {
146                                                 buffer = new byte [stream.Length];
147                                                 for (int n=0; n<stream.Length;)
148                                                         n += stream.Read (buffer, n, (int)stream.Length-n);
149                                                 mstream = new MemoryStream (buffer);
150                                         }
151                                         else
152                                         {
153                                                 buffer = new byte [500];
154                                                 mstream = new MemoryStream ();
155                                         
156                                                 int len;
157                                                 while ((len = stream.Read (buffer, 0, 500)) > 0)
158                                                         mstream.Write (buffer, 0, len);
159                                                 mstream.Position = 0;
160                                                 buffer = mstream.ToArray ();
161                                         }
162
163                                         soapAction = ReadActionFromRequestElement (new MemoryStream (buffer), encoding);
164
165                                         stream = mstream;
166                                         methodInfo = (SoapMethodStubInfo) _typeStubInfo.GetMethod (soapAction);
167                                         message.MethodStubInfo = methodInfo;
168                                 }
169
170                                 // Whatever routing style we used, we should now have the method information.
171                                 // We can now notify the remaining extensions
172
173                                 if (methodInfo == null) throw new SoapException ("Method '" + soapAction + "' not defined in the web service '" + _typeStubInfo.LogicalType.WebServiceName + "'", SoapException.ClientFaultCode);
174
175                                 _extensionChainMedPrio = SoapExtension.CreateExtensionChain (methodInfo.SoapExtensions);
176                                 _extensionChainLowPrio = SoapExtension.CreateExtensionChain (_typeStubInfo.SoapExtensions[1]);
177
178                                 stream = SoapExtension.ExecuteChainStream (_extensionChainMedPrio, stream);
179                                 stream = SoapExtension.ExecuteChainStream (_extensionChainLowPrio, stream);
180                                 SoapExtension.ExecuteProcessMessage (_extensionChainMedPrio, message, false);
181                                 SoapExtension.ExecuteProcessMessage (_extensionChainLowPrio, message, false);
182
183                                 // Deserialize the request
184
185                                 StreamReader reader = new StreamReader (stream, encoding, false);
186                                 XmlTextReader xmlReader = new XmlTextReader (reader);
187
188                                 try
189                                 {
190                                         object content;\r
191                                         SoapHeaderCollection headers;\r
192                                         WebServiceHelper.ReadSoapMessage (xmlReader, _typeStubInfo, methodInfo.Use, methodInfo.RequestSerializer, out content, out headers);\r
193                                         message.InParameters = (object []) content;
194                                         message.SetHeaders (headers);
195                                 }
196                                 catch (Exception ex)
197                                 {
198                                         throw new SoapException ("Could not deserialize Soap message", SoapException.ClientFaultCode, ex);
199                                 }
200
201                                 // Notify the extensions after deserialization
202
203                                 message.SetStage (SoapMessageStage.AfterDeserialize);
204                                 SoapExtension.ExecuteProcessMessage (_extensionChainHighPrio, message, false);
205                                 SoapExtension.ExecuteProcessMessage (_extensionChainMedPrio, message, false);
206                                 SoapExtension.ExecuteProcessMessage (_extensionChainLowPrio, message, false);
207
208                                 xmlReader.Close ();
209
210                                 return message;
211                         }
212                 }
213
214                 string ReadActionFromRequestElement (Stream stream, Encoding encoding)
215                 {
216                         try
217                         {
218                                 StreamReader reader = new StreamReader (stream, encoding, false);
219                                 XmlTextReader xmlReader = new XmlTextReader (reader);
220
221                                 xmlReader.MoveToContent ();
222                                 xmlReader.ReadStartElement ("Envelope", WebServiceHelper.SoapEnvelopeNamespace);
223
224                                 while (! (xmlReader.NodeType == XmlNodeType.Element && xmlReader.LocalName == "Body" && xmlReader.NamespaceURI == WebServiceHelper.SoapEnvelopeNamespace))
225                                         xmlReader.Skip ();
226
227                                 xmlReader.ReadStartElement ("Body", WebServiceHelper.SoapEnvelopeNamespace);
228                                 xmlReader.MoveToContent ();
229
230                                 return xmlReader.LocalName;
231                         }
232                         catch (Exception ex)
233                         {
234                                 string errmsg = "The root element for the request could not be determined. ";
235                                 errmsg += "When RoutingStyle is set to RequestElement, SoapExtensions configured ";
236                                 errmsg += "via an attribute on the method cannot modify the request stream before it is read. ";
237                                 errmsg += "The extension must be configured via the SoapExtensionTypes element in web.config ";
238                                 errmsg += "or the request must arrive at the server as clear text.";
239                                 throw new SoapException (errmsg, SoapException.ServerFaultCode, ex);
240                         }
241                 }
242
243                 void SerializeResponse (HttpResponse response, SoapServerMessage message)
244                 {
245                         SoapMethodStubInfo methodInfo = message.MethodStubInfo;
246                         
247                         response.ContentType = "text/xml; charset=utf-8";
248                         if (message.Exception != null) response.StatusCode = 500;
249
250                         long contentLength = 0;
251                         Stream outStream = null;
252                         MemoryStream bufferStream = null;
253                         Stream responseStream = response.OutputStream;
254                         bool bufferResponse = (methodInfo == null || methodInfo.MethodAttribute.BufferResponse);
255                         
256                         if (bufferResponse)
257                         {
258                                 bufferStream = new MemoryStream ();
259                                 outStream = bufferStream;
260                         }
261                         else
262                                 outStream = responseStream;
263
264                         try
265                         {
266                                 // While serializing, process extensions in reverse order
267
268                                 if (bufferResponse)
269                                 {
270                                         outStream = SoapExtension.ExecuteChainStream (_extensionChainHighPrio, outStream);
271                                         outStream = SoapExtension.ExecuteChainStream (_extensionChainMedPrio, outStream);
272                                         outStream = SoapExtension.ExecuteChainStream (_extensionChainLowPrio, outStream);
273         
274                                         message.SetStage (SoapMessageStage.BeforeSerialize);
275                                         SoapExtension.ExecuteProcessMessage (_extensionChainLowPrio, message, true);
276                                         SoapExtension.ExecuteProcessMessage (_extensionChainMedPrio, message, true);
277                                         SoapExtension.ExecuteProcessMessage (_extensionChainHighPrio, message, true);
278                                 }
279                                 
280                                 XmlTextWriter xtw = WebServiceHelper.CreateXmlWriter (outStream);
281                                 
282                                 if (message.Exception == null)
283                                         WebServiceHelper.WriteSoapMessage (xtw, _typeStubInfo, methodInfo.Use, methodInfo.ResponseSerializer, message.OutParameters, message.Headers);
284                                 else
285                                         WebServiceHelper.WriteSoapMessage (xtw, _typeStubInfo, SoapBindingUse.Literal, Fault.Serializer, new Fault (message.Exception), null);
286
287                                 if (bufferResponse)
288                                 {
289                                         message.SetStage (SoapMessageStage.AfterSerialize);
290                                         SoapExtension.ExecuteProcessMessage (_extensionChainLowPrio, message, true);
291                                         SoapExtension.ExecuteProcessMessage (_extensionChainMedPrio, message, true);
292                                         SoapExtension.ExecuteProcessMessage (_extensionChainHighPrio, message, true);
293                                 }
294                                 
295                                 if (bufferStream != null) contentLength = bufferStream.Length;
296                                 xtw.Close ();
297                         }
298                         catch (Exception ex)
299                         {
300                                 // If the response is buffered, we can discard the response and
301                                 // serialize a new Fault message as response.
302                                 if (bufferResponse) throw ex;
303                                 
304                                 // If it is not buffered, we can't rollback what has been sent,
305                                 // so we can only close the connection and return.
306                                 responseStream.Close ();
307                                 return;
308                         }
309                         
310                         try
311                         {
312                                 if (bufferResponse)
313                                         responseStream.Write (bufferStream.GetBuffer(), 0, (int) contentLength);
314                         }
315                         catch {}
316                         
317                         responseStream.Close ();
318                 }
319
320                 void SerializeFault (HttpContext context, SoapServerMessage requestMessage, Exception ex)
321                 {
322                         SoapException soex = ex as SoapException;
323                         if (soex == null) soex = new SoapException (ex.Message, SoapException.ServerFaultCode, ex);
324
325                         SoapServerMessage faultMessage;
326                         if (requestMessage != null)
327                                 faultMessage = new SoapServerMessage (context.Request, soex, requestMessage.MethodStubInfo, requestMessage.Server, requestMessage.Stream);
328                         else
329                                 faultMessage = new SoapServerMessage (context.Request, soex, null, null, null);
330
331                         SerializeResponse (context.Response, faultMessage);
332                         context.Response.End ();
333                         return;
334                 }
335                 
336                 private SoapServerMessage Invoke (HttpContext ctx, SoapServerMessage requestMessage)
337                 {
338                         SoapMethodStubInfo methodInfo = requestMessage.MethodStubInfo;
339
340                         // Assign header values to web service members
341
342                         requestMessage.UpdateHeaderValues (requestMessage.Server, methodInfo.Headers);
343
344                         // Fill an array with the input parameters at the right position
345
346                         object[] parameters = new object[methodInfo.MethodInfo.Parameters.Length];
347                         ParameterInfo[] inParams = methodInfo.MethodInfo.InParameters;
348                         for (int n=0; n<inParams.Length; n++)
349                                 parameters [inParams[n].Position] = requestMessage.InParameters [n];
350
351                         // Invoke the method
352
353                         try
354                         {
355                                 object[] results = methodInfo.MethodInfo.Invoke (requestMessage.Server, parameters);
356                                 requestMessage.OutParameters = results;
357                         }
358                         catch (TargetInvocationException ex)
359                         {
360                                 throw ex.InnerException;
361                         }
362
363                         // Check that headers with MustUnderstand flag have been understood
364                         
365                         foreach (SoapHeader header in requestMessage.Headers)
366                         {
367                                 if (header.MustUnderstand && !header.DidUnderstand)
368                                         throw new SoapHeaderException ("Header not understood: " + header.GetType(), SoapException.MustUnderstandFaultCode);
369                         }
370
371                         // Collect headers that must be sent to the client
372                         requestMessage.CollectHeaders (requestMessage.Server, methodInfo.Headers, SoapHeaderDirection.Out);
373
374                         return requestMessage;
375                 }
376         }
377 }