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