//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Web.Services.Protocols { using System.Diagnostics; using System; using System.Runtime.InteropServices; using System.Reflection; using System.IO; using System.Collections; using System.Web; using System.Web.SessionState; using System.Web.Services.Interop; using System.Configuration; using Microsoft.Win32; using System.Threading; using System.Text; using System.Web.UI; using System.Web.Util; using System.Web.UI.WebControls; using System.ComponentModel; // for CompModSwitches using System.EnterpriseServices; using System.Runtime.Remoting.Messaging; using System.Web.Services.Diagnostics; internal class WebServiceHandler { ServerProtocol protocol; Exception exception; AsyncCallback asyncCallback; ManualResetEvent asyncBeginComplete; int asyncCallbackCalls; bool wroteException; object[] parameters = null; internal WebServiceHandler(ServerProtocol protocol) { this.protocol = protocol; } // Flush the trace file after each request so that the trace output makes it to the disk. static void TraceFlush() { Debug.Flush(); } void PrepareContext() { this.exception = null; this.wroteException = false; this.asyncCallback = null; this.asyncBeginComplete = new ManualResetEvent(false); this.asyncCallbackCalls = 0; if (protocol.IsOneWay) return; HttpContext context = protocol.Context; if (context == null) return; // context is null in non-network case // we want the default to be no caching on the client int cacheDuration = protocol.MethodAttribute.CacheDuration; if (cacheDuration > 0) { context.Response.Cache.SetCacheability(HttpCacheability.Server); context.Response.Cache.SetExpires(DateTime.Now.AddSeconds(cacheDuration)); context.Response.Cache.SetSlidingExpiration(false); // with soap 1.2 the action is a param in the content-type context.Response.Cache.VaryByHeaders["Content-type"] = true; context.Response.Cache.VaryByHeaders["SOAPAction"] = true; context.Response.Cache.VaryByParams["*"] = true; } else { context.Response.Cache.SetNoServerCaching(); context.Response.Cache.SetMaxAge(TimeSpan.Zero); } context.Response.BufferOutput = protocol.MethodAttribute.BufferResponse; context.Response.ContentType = null; } void WriteException(Exception e) { if (this.wroteException) return; if (CompModSwitches.Remote.TraceVerbose) Debug.WriteLine("Server Exception: " + e.ToString()); if (e is TargetInvocationException) { if (CompModSwitches.Remote.TraceVerbose) Debug.WriteLine("TargetInvocationException caught."); e = e.InnerException; } this.wroteException = protocol.WriteException(e, protocol.Response.OutputStream); if (!this.wroteException) throw e; } void Invoke() { PrepareContext(); protocol.CreateServerInstance(); string stringBuffer; #if !MONO RemoteDebugger debugger = null; if (!protocol.IsOneWay && RemoteDebugger.IsServerCallInEnabled(protocol, out stringBuffer)) { debugger = new RemoteDebugger(); debugger.NotifyServerCallEnter(protocol, stringBuffer); } #endif try { TraceMethod caller = Tracing.On ? new TraceMethod(this, "Invoke") : null; TraceMethod userMethod = Tracing.On ? new TraceMethod(protocol.Target, protocol.MethodInfo.Name, this.parameters) : null; if (Tracing.On) Tracing.Enter(protocol.MethodInfo.ToString(), caller, userMethod); object[] returnValues = protocol.MethodInfo.Invoke(protocol.Target, this.parameters); if (Tracing.On) Tracing.Exit(protocol.MethodInfo.ToString(), caller); WriteReturns(returnValues); } catch (Exception e) { if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) { throw; } if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Error, this, "Invoke", e); if (!protocol.IsOneWay) { WriteException(e); throw; } } finally { protocol.DisposeServerInstance(); #if !MONO if (debugger != null) debugger.NotifyServerCallExit(protocol.Response); #endif } } // By keeping this in a separate method we avoid jitting system.enterpriseservices.dll in cases // where transactions are not used. void InvokeTransacted() { Transactions.InvokeTransacted(new TransactedCallback(this.Invoke), protocol.MethodAttribute.TransactionOption); } void ThrowInitException() { HandleOneWayException(new Exception(Res.GetString(Res.WebConfigExtensionError), protocol.OnewayInitException), "ThrowInitException"); } void HandleOneWayException(Exception e, string method) { if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Error, this, string.IsNullOrEmpty(method) ? "HandleOneWayException" : method, e); // exceptions for one-way calls are dropped because the response is already written } protected void CoreProcessRequest() { try { bool transacted = protocol.MethodAttribute.TransactionEnabled; if (protocol.IsOneWay) { WorkItemCallback callback = null; TraceMethod callbackMethod = null; if (protocol.OnewayInitException != null) { callback = new WorkItemCallback(this.ThrowInitException); callbackMethod = Tracing.On ? new TraceMethod(this, "ThrowInitException") : null; } else { parameters = protocol.ReadParameters(); callback = transacted ? new WorkItemCallback(this.OneWayInvokeTransacted) : new WorkItemCallback(this.OneWayInvoke); callbackMethod = Tracing.On ? transacted ? new TraceMethod(this, "OneWayInvokeTransacted") : new TraceMethod(this, "OneWayInvoke") : null; } if (Tracing.On) Tracing.Information(Res.TracePostWorkItemIn, callbackMethod); WorkItem.Post(callback); if (Tracing.On) Tracing.Information(Res.TracePostWorkItemOut, callbackMethod); protocol.WriteOneWayResponse(); } else if (transacted) { parameters = protocol.ReadParameters(); InvokeTransacted(); } else { parameters = protocol.ReadParameters(); Invoke(); } } catch (Exception e) { if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) { throw; } if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Error, this, "CoreProcessRequest", e); if (!protocol.IsOneWay) WriteException(e); } TraceFlush(); } private HttpContext SwitchContext(HttpContext context) { PartialTrustHelpers.FailIfInPartialTrustOutsideAspNet(); HttpContext oldContext = HttpContext.Current; HttpContext.Current = context; return oldContext; } private void OneWayInvoke() { HttpContext oldContext = null; if (protocol.Context != null) oldContext = SwitchContext(protocol.Context); try { Invoke(); } catch (Exception e) { HandleOneWayException(e, "OneWayInvoke"); } finally { if (oldContext != null) SwitchContext(oldContext); } } private void OneWayInvokeTransacted() { HttpContext oldContext = null; if (protocol.Context != null) oldContext = SwitchContext(protocol.Context); try { InvokeTransacted(); } catch (Exception e) { HandleOneWayException(e, "OneWayInvokeTransacted"); } finally { if (oldContext != null) SwitchContext(oldContext); } } private void Callback(IAsyncResult result) { if (!result.CompletedSynchronously) this.asyncBeginComplete.WaitOne(); DoCallback(result); } private void DoCallback(IAsyncResult result) { if (this.asyncCallback != null) { if (System.Threading.Interlocked.Increment(ref this.asyncCallbackCalls) == 1) { this.asyncCallback(result); } } } protected IAsyncResult BeginCoreProcessRequest(AsyncCallback callback, object asyncState) { IAsyncResult asyncResult; if (protocol.MethodAttribute.TransactionEnabled) throw new InvalidOperationException(Res.GetString(Res.WebAsyncTransaction)); parameters = protocol.ReadParameters(); if (protocol.IsOneWay) { TraceMethod callbackMethod = Tracing.On ? new TraceMethod(this, "OneWayAsyncInvoke") : null; if (Tracing.On) Tracing.Information(Res.TracePostWorkItemIn, callbackMethod); WorkItem.Post(new WorkItemCallback(this.OneWayAsyncInvoke)); if (Tracing.On) Tracing.Information(Res.TracePostWorkItemOut, callbackMethod); asyncResult = new CompletedAsyncResult(asyncState, true); if (callback != null) callback(asyncResult); } else asyncResult = BeginInvoke(callback, asyncState); return asyncResult; } private void OneWayAsyncInvoke() { if (protocol.OnewayInitException != null) HandleOneWayException(new Exception(Res.GetString(Res.WebConfigExtensionError), protocol.OnewayInitException), "OneWayAsyncInvoke"); else { HttpContext oldContext = null; if (protocol.Context != null) oldContext = SwitchContext(protocol.Context); try { BeginInvoke(new AsyncCallback(this.OneWayCallback), null); } catch (Exception e) { HandleOneWayException(e, "OneWayAsyncInvoke"); } finally { if (oldContext != null) SwitchContext(oldContext); } } } private IAsyncResult BeginInvoke(AsyncCallback callback, object asyncState) { IAsyncResult asyncResult; try { PrepareContext(); protocol.CreateServerInstance(); this.asyncCallback = callback; TraceMethod caller = Tracing.On ? new TraceMethod(this, "BeginInvoke") : null; TraceMethod userMethod = Tracing.On ? new TraceMethod(protocol.Target, protocol.MethodInfo.Name, this.parameters) : null; if (Tracing.On) Tracing.Enter(protocol.MethodInfo.ToString(), caller, userMethod); asyncResult = protocol.MethodInfo.BeginInvoke(protocol.Target, this.parameters, new AsyncCallback(this.Callback), asyncState); if (Tracing.On) Tracing.Enter(protocol.MethodInfo.ToString(), caller); if (asyncResult == null) throw new InvalidOperationException(Res.GetString(Res.WebNullAsyncResultInBegin)); } catch (Exception e) { if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) { throw; } if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Error, this, "BeginInvoke", e); // save off the exception and throw it in EndCoreProcessRequest exception = e; asyncResult = new CompletedAsyncResult(asyncState, true); this.asyncCallback = callback; this.DoCallback(asyncResult); } this.asyncBeginComplete.Set(); TraceFlush(); return asyncResult; } private void OneWayCallback(IAsyncResult asyncResult) { EndInvoke(asyncResult); } protected void EndCoreProcessRequest(IAsyncResult asyncResult) { if (asyncResult == null) return; if (protocol.IsOneWay) protocol.WriteOneWayResponse(); else EndInvoke(asyncResult); } private void EndInvoke(IAsyncResult asyncResult) { try { if (exception != null) throw (exception); object[] returnValues = protocol.MethodInfo.EndInvoke(protocol.Target, asyncResult); WriteReturns(returnValues); } catch (Exception e) { if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) { throw; } if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Error, this, "EndInvoke", e); if (!protocol.IsOneWay) WriteException(e); } finally { protocol.DisposeServerInstance(); } TraceFlush(); } void WriteReturns(object[] returnValues) { if (protocol.IsOneWay) return; // By default ASP.NET will fully buffer the response. If BufferResponse=false // then we still want to do partial buffering since each write is a named // pipe call over to inetinfo. bool fullyBuffered = protocol.MethodAttribute.BufferResponse; Stream outputStream = protocol.Response.OutputStream; if (!fullyBuffered) { outputStream = new BufferedResponseStream(outputStream, 16 * 1024); //#if DEBUG ((BufferedResponseStream)outputStream).FlushEnabled = false; //#endif } protocol.WriteReturns(returnValues, outputStream); // This will flush the buffered stream and the underlying stream. Its important // that it flushes the Response.OutputStream because we always want BufferResponse=false // to mean we are writing back a chunked response. This gives a consistent // behavior to the client, independent of the size of the partial buffering. if (!fullyBuffered) { //#if DEBUG ((BufferedResponseStream)outputStream).FlushEnabled = true; //#endif outputStream.Flush(); } } } internal class SyncSessionlessHandler : WebServiceHandler, IHttpHandler { internal SyncSessionlessHandler(ServerProtocol protocol) : base(protocol) { } public bool IsReusable { get { return false; } } public void ProcessRequest(HttpContext context) { TraceMethod method = Tracing.On ? new TraceMethod(this, "ProcessRequest") : null; if (Tracing.On) Tracing.Enter("IHttpHandler.ProcessRequest", method, Tracing.Details(context.Request)); CoreProcessRequest(); if (Tracing.On) Tracing.Exit("IHttpHandler.ProcessRequest", method); } } internal class SyncSessionHandler : SyncSessionlessHandler, IRequiresSessionState { internal SyncSessionHandler(ServerProtocol protocol) : base(protocol) { } } internal class AsyncSessionlessHandler : SyncSessionlessHandler, IHttpAsyncHandler { internal AsyncSessionlessHandler(ServerProtocol protocol) : base(protocol) { } public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback callback, object asyncState) { TraceMethod method = Tracing.On ? new TraceMethod(this, "BeginProcessRequest") : null; if (Tracing.On) Tracing.Enter("IHttpAsyncHandler.BeginProcessRequest", method, Tracing.Details(context.Request)); IAsyncResult result = BeginCoreProcessRequest(callback, asyncState); if (Tracing.On) Tracing.Exit("IHttpAsyncHandler.BeginProcessRequest", method); return result; } public void EndProcessRequest(IAsyncResult asyncResult) { TraceMethod method = Tracing.On ? new TraceMethod(this, "EndProcessRequest") : null; if (Tracing.On) Tracing.Enter("IHttpAsyncHandler.EndProcessRequest", method); EndCoreProcessRequest(asyncResult); if (Tracing.On) Tracing.Exit("IHttpAsyncHandler.EndProcessRequest", method); } } internal class AsyncSessionHandler : AsyncSessionlessHandler, IRequiresSessionState { internal AsyncSessionHandler(ServerProtocol protocol) : base(protocol) { } } class CompletedAsyncResult : IAsyncResult { object asyncState; bool completedSynchronously; internal CompletedAsyncResult(object asyncState, bool completedSynchronously) { this.asyncState = asyncState; this.completedSynchronously = completedSynchronously; } public object AsyncState { get { return asyncState; } } public bool CompletedSynchronously { get { return completedSynchronously; } } public bool IsCompleted { get { return true; } } public WaitHandle AsyncWaitHandle { get { return null; } } } }