2 // System.Web.Services.Protocols.HttpSoapWebServiceHandler.cs
5 // Lluis Sanchez Gual (lluis@ximian.com)
7 // Copyright (C) Ximian, Inc. 2003
15 using System.Reflection;
16 using System.Xml.Serialization;
18 namespace System.Web.Services.Protocols
20 internal class HttpSoapWebServiceHandler: WebServiceHandler
22 SoapTypeStubInfo _typeStubInfo;
23 SoapExtension[] _extensionChainHighPrio;
24 SoapExtension[] _extensionChainMedPrio;
25 SoapExtension[] _extensionChainLowPrio;
27 public HttpSoapWebServiceHandler (Type type): base (type)
29 _typeStubInfo = (SoapTypeStubInfo) TypeStubManager.GetTypeStub (ServiceType, "Soap");
32 public override bool IsReusable
37 public override void ProcessRequest (HttpContext context)
39 SoapServerMessage requestMessage = null;
40 SoapServerMessage responseMessage = null;
44 requestMessage = DeserializeRequest (context.Request);
45 responseMessage = Invoke (requestMessage);
46 SerializeResponse (context.Response, responseMessage);
50 SerializeFault (context, requestMessage, ex);
55 SoapServerMessage DeserializeRequest (HttpRequest request)
57 Stream stream = request.InputStream;
61 string soapAction = null;
62 SoapMethodStubInfo methodInfo = null;
63 Encoding encoding = WebServiceHelper.GetContentEncoding (request.ContentType);
64 object server = CreateServerInstance ();
66 SoapServerMessage message = new SoapServerMessage (request, server, stream);
67 message.SetStage (SoapMessageStage.BeforeDeserialize);
69 // If the routing style is SoapAction, then we can get the method information now
70 // and set it to the SoapMessage
72 if (_typeStubInfo.RoutingStyle == SoapServiceRoutingStyle.SoapAction)
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;
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
84 _extensionChainHighPrio = SoapExtension.CreateExtensionChain (_typeStubInfo.SoapExtensions[0]);
85 stream = SoapExtension.ExecuteChainStream (_extensionChainHighPrio, stream);
86 SoapExtension.ExecuteProcessMessage (_extensionChainHighPrio, message, false);
88 // If the routing style is RequestElement, try to get the method name from the
89 // stream processed by the high priority extensions
91 if (_typeStubInfo.RoutingStyle == SoapServiceRoutingStyle.RequestElement)
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);
104 byte[] buffer = new byte [500];
105 mstream = new MemoryStream ();
108 while ((len = stream.Read (buffer, 0, 500)) > 0)
109 mstream.Write (buffer, 0, len);
110 mstream.Position = 0;
113 soapAction = ReadActionFromRequestElement (new MemoryStream (mstream.GetBuffer ()), encoding);
116 methodInfo = GetMethodFromAction (soapAction);
117 message.MethodStubInfo = methodInfo;
120 // Whatever routing style we used, we should now have the method information.
121 // We can now notify the remaining extensions
123 if (methodInfo == null) throw new SoapException ("Method '" + soapAction + "' not defined in the web service '" + _typeStubInfo.WebServiceName + "'", SoapException.ClientFaultCode);
125 _extensionChainMedPrio = SoapExtension.CreateExtensionChain (methodInfo.SoapExtensions);
126 _extensionChainLowPrio = SoapExtension.CreateExtensionChain (_typeStubInfo.SoapExtensions[1]);
128 stream = SoapExtension.ExecuteChainStream (_extensionChainMedPrio, stream);
129 stream = SoapExtension.ExecuteChainStream (_extensionChainLowPrio, stream);
130 SoapExtension.ExecuteProcessMessage (_extensionChainMedPrio, message, false);
131 SoapExtension.ExecuteProcessMessage (_extensionChainLowPrio, message, false);
133 // Deserialize the request
135 StreamReader reader = new StreamReader (stream, encoding, false);
136 XmlTextReader xmlReader = new XmlTextReader (reader);
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);
148 Console.WriteLine (ex);
149 throw new SoapException ("Could not deserialize Soap message", SoapException.ClientFaultCode, ex);
152 // Notify the extensions after deserialization
154 message.SetStage (SoapMessageStage.AfterDeserialize);
155 SoapExtension.ExecuteProcessMessage (_extensionChainHighPrio, message, false);
156 SoapExtension.ExecuteProcessMessage (_extensionChainMedPrio, message, false);
157 SoapExtension.ExecuteProcessMessage (_extensionChainLowPrio, message, false);
165 SoapMethodStubInfo GetMethodFromAction (string soapAction)
167 soapAction = soapAction.Trim ('"',' ');
168 int i = soapAction.LastIndexOf ('/');
169 string methodName = soapAction.Substring (i + 1);
170 return (SoapMethodStubInfo) _typeStubInfo.GetMethod (methodName);
173 string ReadActionFromRequestElement (Stream stream, Encoding encoding)
177 StreamReader reader = new StreamReader (stream, encoding, false);
178 XmlTextReader xmlReader = new XmlTextReader (reader);
180 xmlReader.MoveToContent ();
181 xmlReader.ReadStartElement ("Envelope", WebServiceHelper.SoapEnvelopeNamespace);
183 while (! (xmlReader.NodeType == XmlNodeType.Element && xmlReader.LocalName == "Body" && xmlReader.NamespaceURI == WebServiceHelper.SoapEnvelopeNamespace))
186 xmlReader.ReadStartElement ("Body", WebServiceHelper.SoapEnvelopeNamespace);
187 xmlReader.MoveToContent ();
189 if (xmlReader.NamespaceURI.EndsWith ("/")) return xmlReader.NamespaceURI + xmlReader.LocalName;
190 else return xmlReader.NamespaceURI + "/" + xmlReader.LocalName;
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);
203 void SerializeResponse (HttpResponse response, SoapServerMessage message)
205 SoapMethodStubInfo methodInfo = message.MethodStubInfo;
207 response.ContentType = "text/xml; charset=utf-8";
208 if (message.Exception != null) response.StatusCode = 500;
210 long contentLength = 0;
211 Stream outStream = null;
212 MemoryStream bufferStream = null;
213 Stream responseStream = response.OutputStream;
215 if (methodInfo.MethodAttribute.BufferResponse)
217 bufferStream = new MemoryStream ();
218 outStream = bufferStream;
221 outStream = responseStream;
225 // While serializing, process extensions in reverse order
227 if (methodInfo.MethodAttribute.BufferResponse)
229 outStream = SoapExtension.ExecuteChainStream (_extensionChainHighPrio, outStream);
230 outStream = SoapExtension.ExecuteChainStream (_extensionChainMedPrio, outStream);
231 outStream = SoapExtension.ExecuteChainStream (_extensionChainLowPrio, outStream);
233 message.SetStage (SoapMessageStage.BeforeSerialize);
234 SoapExtension.ExecuteProcessMessage (_extensionChainLowPrio, message, true);
235 SoapExtension.ExecuteProcessMessage (_extensionChainMedPrio, message, true);
236 SoapExtension.ExecuteProcessMessage (_extensionChainHighPrio, message, true);
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
243 if (message.Exception == null)
244 WebServiceHelper.WriteSoapMessage (xtw, _typeStubInfo, message.MethodStubInfo.ResponseSerializer, message.OutParameters, message.Headers);
246 WebServiceHelper.WriteSoapMessage (xtw, _typeStubInfo, _typeStubInfo.FaultSerializer, new Fault (message.Exception), null);
248 if (methodInfo.MethodAttribute.BufferResponse)
250 message.SetStage (SoapMessageStage.AfterSerialize);
251 SoapExtension.ExecuteProcessMessage (_extensionChainLowPrio, message, true);
252 SoapExtension.ExecuteProcessMessage (_extensionChainMedPrio, message, true);
253 SoapExtension.ExecuteProcessMessage (_extensionChainHighPrio, message, true);
256 if (bufferStream != null) contentLength = bufferStream.Length;
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;
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 ();
273 if (methodInfo.MethodAttribute.BufferResponse)
274 responseStream.Write (bufferStream.GetBuffer(), 0, (int) contentLength);
278 responseStream.Close ();
281 void SerializeFault (HttpContext context, SoapServerMessage requestMessage, Exception ex)
283 SoapException soex = ex as SoapException;
284 if (soex == null) soex = new SoapException (ex.Message, SoapException.ServerFaultCode, ex);
286 MethodStubInfo stubInfo;
290 SoapServerMessage faultMessage;
291 if (requestMessage != null)
292 faultMessage = new SoapServerMessage (context.Request, soex, requestMessage.MethodStubInfo, requestMessage.Server, requestMessage.Stream);
294 faultMessage = new SoapServerMessage (context.Request, soex, null, null, null);
296 SerializeResponse (context.Response, faultMessage);
300 private SoapServerMessage Invoke (SoapServerMessage requestMessage)
302 SoapMethodStubInfo methodInfo = requestMessage.MethodStubInfo;
304 // Assign header values to web service members
306 requestMessage.UpdateHeaderValues (requestMessage.Server, methodInfo.Headers);
308 // Fill an array with the input parameters at the right position
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];
319 object[] results = methodInfo.MethodInfo.Invoke (requestMessage.Server, parameters);
320 requestMessage.OutParameters = results;
322 catch (TargetInvocationException ex)
324 throw ex.InnerException;
327 // Check that headers with MustUnderstand flag have been understood
329 foreach (SoapHeader header in requestMessage.Headers)
331 if (header.MustUnderstand && !header.DidUnderstand)
332 throw new SoapHeaderException ("Header not understood: " + header.GetType(), SoapException.MustUnderstandFaultCode);
335 // Collect headers that must be sent to the client
336 requestMessage.CollectHeaders (requestMessage.Server, methodInfo.Headers, SoapHeaderDirection.Out);
338 return requestMessage;