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