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