e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
958 lines
41 KiB
C#
958 lines
41 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="ClientProtocol.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Web.Services.Protocols {
|
|
using System;
|
|
using System.Collections;
|
|
using System.Diagnostics;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Xml.Serialization;
|
|
using System.Net;
|
|
using System.Net.Cache;
|
|
using System.Threading;
|
|
using System.Text;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Security.Permissions;
|
|
using System.Runtime.InteropServices;
|
|
using System.Web.Services.Diagnostics;
|
|
|
|
internal class ClientTypeCache {
|
|
Hashtable cache = new Hashtable();
|
|
|
|
internal object this[Type key] {
|
|
get { return cache[key]; }
|
|
}
|
|
|
|
internal void Add(Type key, object value) {
|
|
lock (this) {
|
|
if (cache[key] == value) return;
|
|
Hashtable clone = new Hashtable();
|
|
foreach (object k in cache.Keys) {
|
|
clone.Add(k, cache[k]);
|
|
}
|
|
cache = clone;
|
|
cache[key] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Specifies the base class for all web service protocol client proxies that
|
|
/// use the HTTP protocol.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[ComVisible(true)]
|
|
public abstract class WebClientProtocol : Component {
|
|
static AsyncCallback getRequestStreamAsyncCallback;
|
|
static AsyncCallback getResponseAsyncCallback;
|
|
|
|
// Double-checked locking pattern requires volatile for read/write synchronization
|
|
static volatile AsyncCallback readResponseAsyncCallback;
|
|
private static ClientTypeCache cache;
|
|
private static RequestCachePolicy bypassCache;
|
|
private ICredentials credentials;
|
|
private bool preAuthenticate;
|
|
private Uri uri;
|
|
private int timeout;
|
|
private string connectionGroupName;
|
|
private Encoding requestEncoding;
|
|
#if !MONO
|
|
private RemoteDebugger debugger;
|
|
#endif
|
|
private WebRequest pendingSyncRequest;
|
|
object nullToken = new object();
|
|
Hashtable asyncInvokes = Hashtable.Synchronized(new Hashtable());
|
|
|
|
private static Object s_InternalSyncObject;
|
|
internal static Object InternalSyncObject {
|
|
get {
|
|
if (s_InternalSyncObject == null) {
|
|
Object o = new Object();
|
|
Interlocked.CompareExchange(ref s_InternalSyncObject, o, null);
|
|
}
|
|
return s_InternalSyncObject;
|
|
}
|
|
}
|
|
|
|
static WebClientProtocol() {
|
|
cache = new ClientTypeCache();
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol.WebClientProtocol"]/*' />
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
protected WebClientProtocol() {
|
|
this.timeout = 100000; // should be kept in [....] with HttpWebRequest.Timeout default (see private WebRequest.DefaultTimeout)
|
|
}
|
|
|
|
internal WebClientProtocol(WebClientProtocol protocol) {
|
|
this.credentials = protocol.credentials;
|
|
this.uri = protocol.uri;
|
|
this.timeout = protocol.timeout;
|
|
this.connectionGroupName = protocol.connectionGroupName;
|
|
this.requestEncoding = protocol.requestEncoding;
|
|
}
|
|
|
|
internal static RequestCachePolicy BypassCache {
|
|
get {
|
|
if (bypassCache == null) {
|
|
bypassCache = new RequestCachePolicy(RequestCacheLevel.BypassCache);
|
|
}
|
|
return bypassCache;
|
|
}
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol.Credentials"]/*' />
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
public ICredentials Credentials {
|
|
get {
|
|
return credentials;
|
|
}
|
|
set {
|
|
credentials = value;
|
|
}
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol.UseDefaultCredentials"]/*' />
|
|
/// <devdoc>
|
|
/// <para>Sets Credentials to CredentialCache.DefaultCredentials</para>
|
|
/// </devdoc>
|
|
public bool UseDefaultCredentials {
|
|
get {
|
|
return (credentials == CredentialCache.DefaultCredentials) ? true : false;
|
|
}
|
|
set {
|
|
credentials = value ? CredentialCache.DefaultCredentials : null;
|
|
}
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol.ConnectionGroupName"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets a value indicating the name of the connection group to use when making a request.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[DefaultValue("")]
|
|
public string ConnectionGroupName {
|
|
get { return (connectionGroupName == null) ? string.Empty : connectionGroupName; }
|
|
set { connectionGroupName = value; }
|
|
}
|
|
|
|
internal WebRequest PendingSyncRequest {
|
|
get { return pendingSyncRequest; }
|
|
set { pendingSyncRequest = value; }
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol.PreAuthenticate"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets a value indicating whether pre-authentication is enabled.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[DefaultValue(false), WebServicesDescription(Res.ClientProtocolPreAuthenticate)]
|
|
public bool PreAuthenticate {
|
|
get { return preAuthenticate; }
|
|
set { preAuthenticate = value; }
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol.Url"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets the base Uri to the server to use for requests.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[DefaultValue(""), SettingsBindable(true), WebServicesDescription(Res.ClientProtocolUrl)]
|
|
public string Url {
|
|
get { return uri == null ? string.Empty : uri.ToString(); }
|
|
set { uri = new Uri(value); }
|
|
}
|
|
|
|
internal Hashtable AsyncInvokes {
|
|
get { return asyncInvokes; }
|
|
}
|
|
|
|
internal object NullToken {
|
|
get { return nullToken; }
|
|
}
|
|
|
|
internal Uri Uri {
|
|
get { return uri; }
|
|
set { uri = value; }
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol.RequestEncoding"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets the encoding used for making the request.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[DefaultValue(null), SettingsBindable(true), WebServicesDescription(Res.ClientProtocolEncoding)]
|
|
public Encoding RequestEncoding {
|
|
get { return requestEncoding; }
|
|
set { requestEncoding = value; }
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol.Timeout"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets the timeout (in milliseconds) used for synchronous calls.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[DefaultValue(100000), SettingsBindable(true), WebServicesDescription(Res.ClientProtocolTimeout)]
|
|
public int Timeout {
|
|
get { return timeout; }
|
|
set { timeout = (value < Threading.Timeout.Infinite) ? Threading.Timeout.Infinite : value; }
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol.Abort"]/*' />
|
|
public virtual void Abort() {
|
|
WebRequest request = PendingSyncRequest;
|
|
if (request != null)
|
|
request.Abort();
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Starts async request processing including async retrieval of the request stream and response.
|
|
/// Derived classes can use BeginSend
|
|
/// to help implement their own higher level async methods like BeginInvoke. Derived
|
|
/// classes can add custom behavior by overriding GetWebRequest, GetWebResponse,
|
|
/// InitializeAsyncRequest and WriteAsyncRequest methods.
|
|
/// </para>
|
|
/// </devdoc>
|
|
internal IAsyncResult BeginSend(Uri requestUri, WebClientAsyncResult asyncResult, bool callWriteAsyncRequest) {
|
|
if (readResponseAsyncCallback == null) {
|
|
lock (InternalSyncObject) {
|
|
if (readResponseAsyncCallback == null) {
|
|
getRequestStreamAsyncCallback = new AsyncCallback(GetRequestStreamAsyncCallback);
|
|
getResponseAsyncCallback = new AsyncCallback(GetResponseAsyncCallback);
|
|
readResponseAsyncCallback = new AsyncCallback(ReadResponseAsyncCallback);
|
|
}
|
|
}
|
|
}
|
|
Debug.Assert(asyncResult.Request == null, "calling GetWebRequest twice for the same WebClientAsyncResult");
|
|
WebRequest request = GetWebRequest(requestUri);
|
|
asyncResult.Request = request;
|
|
InitializeAsyncRequest(request, asyncResult.InternalAsyncState);
|
|
if (callWriteAsyncRequest)
|
|
request.BeginGetRequestStream(getRequestStreamAsyncCallback, asyncResult);
|
|
else
|
|
request.BeginGetResponse(getResponseAsyncCallback, asyncResult);
|
|
|
|
if (!asyncResult.IsCompleted)
|
|
asyncResult.CombineCompletedSynchronously(false);
|
|
return asyncResult;
|
|
}
|
|
|
|
static private void ProcessAsyncException(WebClientAsyncResult client, Exception e, string method) {
|
|
if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Error, typeof(WebClientProtocol), method, e);
|
|
WebException webException = e as WebException;
|
|
if (webException != null && webException.Response != null) {
|
|
client.Response = webException.Response;
|
|
}
|
|
else {
|
|
// If we've already completed the call then the exception must have come
|
|
// out of the user callback in which case we need to rethrow it here
|
|
// so that it bubbles up to the AppDomain unhandled exception event.
|
|
if (client.IsCompleted)
|
|
throw new InvalidOperationException(Res.GetString(Res.ThereWasAnErrorDuringAsyncProcessing), e);
|
|
else
|
|
client.Complete(e);
|
|
}
|
|
}
|
|
|
|
static private void GetRequestStreamAsyncCallback(IAsyncResult asyncResult) {
|
|
WebClientAsyncResult client = (WebClientAsyncResult)asyncResult.AsyncState;
|
|
client.CombineCompletedSynchronously(asyncResult.CompletedSynchronously);
|
|
bool processingRequest = true;
|
|
try {
|
|
Stream requestStream = client.Request.EndGetRequestStream(asyncResult);
|
|
processingRequest = false;
|
|
try {
|
|
client.ClientProtocol.AsyncBufferedSerialize(client.Request, requestStream, client.InternalAsyncState);
|
|
}
|
|
finally {
|
|
requestStream.Close();
|
|
}
|
|
client.Request.BeginGetResponse(getResponseAsyncCallback, client);
|
|
}
|
|
catch (Exception e) {
|
|
if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
|
|
throw;
|
|
}
|
|
ProcessAsyncException(client, e, "GetRequestStreamAsyncCallback");
|
|
if (processingRequest)
|
|
{
|
|
WebException we = e as WebException;
|
|
if (we != null && we.Response != null)
|
|
{
|
|
// ProcessAsyncExcption doesn't call client.Complete() if there's a response,
|
|
// because it expects us to read the response. However, in certain cases
|
|
// (e.g. 502 errors), the exception thrown from Request can have a response.
|
|
// We don't process it, so call Complete() now.
|
|
client.Complete(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static private void GetResponseAsyncCallback(IAsyncResult asyncResult) {
|
|
WebClientAsyncResult client = (WebClientAsyncResult)asyncResult.AsyncState;
|
|
client.CombineCompletedSynchronously(asyncResult.CompletedSynchronously);
|
|
try {
|
|
client.Response = client.ClientProtocol.GetWebResponse(client.Request, asyncResult);
|
|
}
|
|
catch (Exception e) {
|
|
if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
|
|
throw;
|
|
}
|
|
ProcessAsyncException(client, e, "GetResponseAsyncCallback");
|
|
if (client.Response == null)
|
|
return;
|
|
}
|
|
|
|
ReadAsyncResponse(client);
|
|
}
|
|
|
|
static private void ReadAsyncResponse(WebClientAsyncResult client) {
|
|
if (client.Response.ContentLength == 0) {
|
|
client.Complete();
|
|
return;
|
|
}
|
|
try {
|
|
client.ResponseStream = client.Response.GetResponseStream();
|
|
ReadAsyncResponseStream(client);
|
|
}
|
|
catch (Exception e) {
|
|
if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
|
|
throw;
|
|
}
|
|
ProcessAsyncException(client, e, "ReadAsyncResponse");
|
|
}
|
|
}
|
|
|
|
static private void ReadAsyncResponseStream(WebClientAsyncResult client) {
|
|
IAsyncResult asyncResult;
|
|
do {
|
|
byte[] buffer = client.Buffer;
|
|
long contentLength = client.Response.ContentLength;
|
|
if (buffer == null)
|
|
buffer = client.Buffer = new byte[(contentLength == -1) ? 1024 : contentLength];
|
|
else if (contentLength != -1 && contentLength > buffer.Length)
|
|
buffer = client.Buffer = new byte[contentLength];
|
|
asyncResult = client.ResponseStream.BeginRead(buffer, 0, buffer.Length, readResponseAsyncCallback, client);
|
|
if (!asyncResult.CompletedSynchronously)
|
|
return;
|
|
}
|
|
while (!ProcessAsyncResponseStreamResult(client, asyncResult));
|
|
}
|
|
|
|
static private bool ProcessAsyncResponseStreamResult(WebClientAsyncResult client, IAsyncResult asyncResult) {
|
|
bool complete;
|
|
int bytesRead = client.ResponseStream.EndRead(asyncResult);
|
|
long contentLength = client.Response.ContentLength;
|
|
if (contentLength > 0 && bytesRead == contentLength) {
|
|
// the non-chunked response finished in a single read
|
|
client.ResponseBufferedStream = new MemoryStream(client.Buffer);
|
|
complete = true;
|
|
}
|
|
else if (bytesRead > 0) {
|
|
if (client.ResponseBufferedStream == null) {
|
|
int capacity = (int)((contentLength == -1) ? client.Buffer.Length : contentLength);
|
|
client.ResponseBufferedStream = new MemoryStream(capacity);
|
|
}
|
|
client.ResponseBufferedStream.Write(client.Buffer, 0, bytesRead);
|
|
complete = false;
|
|
}
|
|
else
|
|
complete = true;
|
|
|
|
if (complete)
|
|
client.Complete();
|
|
return complete;
|
|
}
|
|
|
|
static private void ReadResponseAsyncCallback(IAsyncResult asyncResult) {
|
|
WebClientAsyncResult client = (WebClientAsyncResult)asyncResult.AsyncState;
|
|
client.CombineCompletedSynchronously(asyncResult.CompletedSynchronously);
|
|
if (asyncResult.CompletedSynchronously)
|
|
return;
|
|
try {
|
|
bool complete = ProcessAsyncResponseStreamResult(client, asyncResult);
|
|
if (!complete)
|
|
ReadAsyncResponseStream(client);
|
|
}
|
|
catch (Exception e) {
|
|
if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
|
|
throw;
|
|
}
|
|
ProcessAsyncException(client, e, "ReadResponseAsyncCallback");
|
|
}
|
|
}
|
|
|
|
internal void NotifyClientCallOut(WebRequest request) {
|
|
#if !MONO
|
|
if (RemoteDebugger.IsClientCallOutEnabled()) {
|
|
debugger = new RemoteDebugger();
|
|
debugger.NotifyClientCallOut(request);
|
|
}
|
|
else {
|
|
debugger = null;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol.GetWebRequest"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Creates a new <see cref='System.Net.WebRequest'/> instance for the given url. The base implementation creates a new
|
|
/// instance using the WebRequest.Create() and then sets request related properties from
|
|
/// the WebClientProtocol instance. Derived classes can override this method if additional
|
|
/// properties need to be set on the web request instance.
|
|
/// </para>
|
|
/// </devdoc>
|
|
protected virtual WebRequest GetWebRequest(Uri uri) {
|
|
if (uri == null)
|
|
throw new InvalidOperationException(Res.GetString(Res.WebMissingPath));
|
|
WebRequest request = (WebRequest)WebRequest.Create(uri);
|
|
PendingSyncRequest = request;
|
|
request.Timeout = this.timeout;
|
|
request.ConnectionGroupName = connectionGroupName;
|
|
request.Credentials = Credentials;
|
|
request.PreAuthenticate = PreAuthenticate;
|
|
request.CachePolicy = BypassCache;
|
|
return request;
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol.GetWebResponse"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets the <see cref='System.Net.WebResponse'/> from the given request by calling
|
|
/// GetResponse(). Derived classes can override this method to do additional
|
|
/// processing on the response instance.
|
|
/// </para>
|
|
/// </devdoc>
|
|
protected virtual WebResponse GetWebResponse(WebRequest request) {
|
|
TraceMethod caller = Tracing.On ? new TraceMethod(this, "GetWebResponse") : null;
|
|
WebResponse response = null;
|
|
try {
|
|
if (Tracing.On) Tracing.Enter("WebRequest.GetResponse", caller, new TraceMethod(request, "GetResponse"));
|
|
response = request.GetResponse();
|
|
if (Tracing.On) Tracing.Exit("WebRequest.GetResponse", caller);
|
|
}
|
|
catch (WebException e) {
|
|
if (e.Response == null)
|
|
throw e;
|
|
else {
|
|
if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Error, this, "GetWebResponse", e);
|
|
response = e.Response;
|
|
}
|
|
}
|
|
finally {
|
|
#if !MONO
|
|
if (debugger != null)
|
|
debugger.NotifyClientCallReturn(response);
|
|
#endif
|
|
}
|
|
return response;
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol.GetWebResponse1"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets the <see cref='System.Net.WebResponse'/> from the given request by calling
|
|
/// EndGetResponse(). Derived classes can override this method to do additional
|
|
/// processing on the response instance. This method is only called during
|
|
/// async request processing.
|
|
/// </para>
|
|
/// </devdoc>
|
|
protected virtual WebResponse GetWebResponse(WebRequest request, IAsyncResult result) {
|
|
WebResponse response = request.EndGetResponse(result);
|
|
#if !MONO
|
|
if (response != null && debugger != null)
|
|
debugger.NotifyClientCallReturn(response);
|
|
#endif
|
|
return response;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Called during async request processing to give the derived class an opportunity
|
|
/// to modify the web request instance before the request stream is retrieved at which
|
|
/// point the request headers are sent and can no longer be modified. The base implementation
|
|
/// does nothing.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
|
|
internal virtual void InitializeAsyncRequest(WebRequest request, object internalAsyncState) {
|
|
return;
|
|
}
|
|
|
|
[PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
|
|
internal virtual void AsyncBufferedSerialize(WebRequest request, Stream requestStream, object internalAsyncState) {
|
|
throw new NotSupportedException(Res.GetString(Res.ProtocolDoesNotAsyncSerialize));
|
|
}
|
|
|
|
internal WebResponse EndSend(IAsyncResult asyncResult, ref object internalAsyncState, ref Stream responseStream) {
|
|
if (asyncResult == null) throw new ArgumentNullException(Res.GetString(Res.WebNullAsyncResultInEnd));
|
|
|
|
WebClientAsyncResult client = (WebClientAsyncResult)asyncResult;
|
|
if (client.EndSendCalled)
|
|
throw new InvalidOperationException(Res.GetString(Res.CanTCallTheEndMethodOfAnAsyncCallMoreThan));
|
|
client.EndSendCalled = true;
|
|
WebResponse response = client.WaitForResponse();
|
|
internalAsyncState = client.InternalAsyncState;
|
|
responseStream = client.ResponseBufferedStream;
|
|
return response;
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol.GetFromCache"]/*' />
|
|
/// <devdoc>
|
|
/// Returns an instance of a client protocol handler from the cache.
|
|
/// </devdoc>
|
|
protected static object GetFromCache(Type type) {
|
|
return cache[type];
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientProtocol.AddToCache"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Add an instance of the client protocol handler to the cache.
|
|
/// </para>
|
|
/// </devdoc>
|
|
protected static void AddToCache(Type type, object value) {
|
|
cache.Add(type, value);
|
|
}
|
|
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientAsyncResult"]/*' />
|
|
[PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
|
|
public class WebClientAsyncResult : IAsyncResult {
|
|
private object userAsyncState;
|
|
private bool completedSynchronously;
|
|
private bool isCompleted;
|
|
|
|
// Double-checked locking pattern requires volatile for read/write synchronization
|
|
private volatile ManualResetEvent manualResetEvent;
|
|
private AsyncCallback userCallback;
|
|
|
|
internal WebClientProtocol ClientProtocol;
|
|
internal object InternalAsyncState;
|
|
internal Exception Exception;
|
|
internal WebResponse Response;
|
|
internal WebRequest Request;
|
|
internal Stream ResponseStream;
|
|
internal Stream ResponseBufferedStream;
|
|
internal byte[] Buffer;
|
|
internal bool EndSendCalled;
|
|
|
|
internal WebClientAsyncResult(WebClientProtocol clientProtocol,
|
|
object internalAsyncState,
|
|
WebRequest request,
|
|
AsyncCallback userCallback,
|
|
object userAsyncState)
|
|
{
|
|
this.ClientProtocol = clientProtocol;
|
|
this.InternalAsyncState = internalAsyncState;
|
|
this.userAsyncState = userAsyncState;
|
|
this.userCallback = userCallback;
|
|
this.Request = request;
|
|
this.completedSynchronously = true;
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientAsyncResult.AsyncState"]/*' />
|
|
public object AsyncState { get { return userAsyncState; } }
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientAsyncResult.AsyncWaitHandle"]/*' />
|
|
public WaitHandle AsyncWaitHandle {
|
|
get {
|
|
bool savedIsCompleted = isCompleted;
|
|
if (manualResetEvent == null) {
|
|
lock (this) {
|
|
if (manualResetEvent == null)
|
|
manualResetEvent = new ManualResetEvent(savedIsCompleted);
|
|
}
|
|
}
|
|
if (!savedIsCompleted && isCompleted)
|
|
manualResetEvent.Set();
|
|
return (WaitHandle)manualResetEvent;
|
|
}
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientAsyncResult.CompletedSynchronously"]/*' />
|
|
public bool CompletedSynchronously {
|
|
get { return completedSynchronously; }
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientAsyncResult.IsCompleted"]/*' />
|
|
public bool IsCompleted { get { return isCompleted; } }
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="WebClientAsyncResult.Abort"]/*' />
|
|
public void Abort() {
|
|
WebRequest req = Request;
|
|
if (req != null)
|
|
req.Abort();
|
|
}
|
|
|
|
|
|
internal void Complete() {
|
|
Debug.Assert(!isCompleted, "Complete called more than once.");
|
|
|
|
try {
|
|
if (ResponseStream != null) {
|
|
ResponseStream.Close();
|
|
ResponseStream = null;
|
|
}
|
|
|
|
if (ResponseBufferedStream != null)
|
|
ResponseBufferedStream.Position = 0;
|
|
}
|
|
catch (Exception e) {
|
|
if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
|
|
throw;
|
|
}
|
|
if (this.Exception == null)
|
|
this.Exception = e;
|
|
if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Error, this, "Complete", e);
|
|
}
|
|
|
|
isCompleted = true;
|
|
|
|
try {
|
|
if (manualResetEvent != null)
|
|
manualResetEvent.Set();
|
|
}
|
|
catch (Exception e) {
|
|
if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
|
|
throw;
|
|
}
|
|
if (this.Exception == null)
|
|
this.Exception = e;
|
|
if (Tracing.On) Tracing.ExceptionCatch(TraceEventType.Error, this, "Complete", e);
|
|
}
|
|
|
|
// We want to let exceptions in user callback to bubble up to
|
|
// threadpool so that AppDomain.UnhandledExceptionEventHandler
|
|
// will get it if one is registered
|
|
if (userCallback != null)
|
|
userCallback(this);
|
|
}
|
|
|
|
internal void Complete(Exception e) {
|
|
this.Exception = e;
|
|
Complete();
|
|
}
|
|
|
|
internal WebResponse WaitForResponse() {
|
|
if (!isCompleted)
|
|
AsyncWaitHandle.WaitOne();
|
|
|
|
if (this.Exception != null)
|
|
throw this.Exception;
|
|
|
|
return Response;
|
|
}
|
|
|
|
internal void CombineCompletedSynchronously(bool innerCompletedSynchronously) {
|
|
completedSynchronously = completedSynchronously && innerCompletedSynchronously;
|
|
}
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="InvokeCompletedEventHandler"]/*' />
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
public delegate void InvokeCompletedEventHandler(object sender, InvokeCompletedEventArgs e);
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="InvokeCompletedEventArgs"]/*' />
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
public class InvokeCompletedEventArgs : AsyncCompletedEventArgs {
|
|
object[] results;
|
|
|
|
internal InvokeCompletedEventArgs(object[] results, Exception exception, bool cancelled, object userState) :
|
|
base(exception, cancelled, userState) {
|
|
this.results = results;
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="InvokeCompletedEventArgs.Results"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets a value indicating whether the client should automatically follow server redirects.
|
|
/// </para>
|
|
/// </devdoc>
|
|
public object[] Results {
|
|
get {
|
|
return results;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
internal class UserToken {
|
|
SendOrPostCallback callback;
|
|
object userState;
|
|
|
|
internal UserToken(SendOrPostCallback callback, object userState) {
|
|
this.callback = callback;
|
|
this.userState = userState;
|
|
}
|
|
internal SendOrPostCallback Callback { get { return callback; } }
|
|
internal object UserState { get { return userState; } }
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="HttpWebClientProtocol"]/*' />
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
[ComVisible(true)]
|
|
public abstract class HttpWebClientProtocol : WebClientProtocol {
|
|
private bool allowAutoRedirect;
|
|
private bool enableDecompression = false;
|
|
private CookieContainer cookieJar = null;
|
|
private X509CertificateCollection clientCertificates;
|
|
private IWebProxy proxy;
|
|
private static string UserAgentDefault = "Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol " + System.Environment.Version.ToString() + ")";
|
|
private string userAgent;
|
|
private bool unsafeAuthenticatedConnectionSharing;
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="HttpWebClientProtocol.HttpWebClientProtocol"]/*' />
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
protected HttpWebClientProtocol() : base() {
|
|
this.allowAutoRedirect = false;
|
|
this.userAgent = UserAgentDefault;
|
|
// the right thing to do, for NetClasses to pick up the default
|
|
// GlobalProxySelection settings, is to leave proxy to null
|
|
// (which is the default initialization value)
|
|
// rather than picking up GlobalProxySelection.Select
|
|
// which will never change.
|
|
}
|
|
|
|
// used by SoapHttpClientProtocol.Discover
|
|
internal HttpWebClientProtocol(HttpWebClientProtocol protocol)
|
|
: base(protocol) {
|
|
this.allowAutoRedirect = protocol.allowAutoRedirect;
|
|
this.enableDecompression = protocol.enableDecompression;
|
|
this.cookieJar = protocol.cookieJar;
|
|
this.clientCertificates = protocol.clientCertificates;
|
|
this.proxy = protocol.proxy;
|
|
this.userAgent = protocol.userAgent;
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="HttpWebClientProtocol.AllowAutoRedirect"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets a value indicating whether the client should automatically follow server redirects.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[DefaultValue(false), WebServicesDescription(Res.ClientProtocolAllowAutoRedirect)]
|
|
public bool AllowAutoRedirect {
|
|
get {
|
|
return allowAutoRedirect;
|
|
}
|
|
|
|
set {
|
|
allowAutoRedirect = value;
|
|
}
|
|
}
|
|
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="HttpWebClientProtocol.CookieContainer"]/*' />
|
|
[DefaultValue(null), WebServicesDescription(Res.ClientProtocolCookieContainer)]
|
|
public CookieContainer CookieContainer {
|
|
get {
|
|
return cookieJar;
|
|
}
|
|
set {
|
|
cookieJar = value;
|
|
}
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="HttpWebClientProtocol.ClientCertificates"]/*' />
|
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), WebServicesDescription(Res.ClientProtocolClientCertificates)]
|
|
public X509CertificateCollection ClientCertificates {
|
|
get {
|
|
if (clientCertificates == null) {
|
|
clientCertificates = new X509CertificateCollection();
|
|
}
|
|
return clientCertificates;
|
|
}
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="HttpWebClientProtocol.EnableDecompression"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets a value indicating whether the client should automatically follow server redirects.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[DefaultValue(false), WebServicesDescription(Res.ClientProtocolEnableDecompression)]
|
|
public bool EnableDecompression {
|
|
get {
|
|
return enableDecompression;
|
|
}
|
|
|
|
set {
|
|
enableDecompression = value;
|
|
}
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="HttpWebClientProtocol.UserAgent"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets the value for the user agent header that is
|
|
/// sent with each request.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), WebServicesDescription(Res.ClientProtocolUserAgent)]
|
|
public string UserAgent {
|
|
get { return (userAgent == null) ? string.Empty : userAgent; }
|
|
set { userAgent = value; }
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="HttpWebClientProtocol.Proxy"]/*' />
|
|
/// <devdoc>
|
|
/// <para>
|
|
/// Gets or sets the name of the proxy server to use for requests.
|
|
/// </para>
|
|
/// </devdoc>
|
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public IWebProxy Proxy {
|
|
get { return proxy; }
|
|
set { proxy = value; }
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="HttpWebClientProtocol.GetWebRequest"]/*' />
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
protected override WebRequest GetWebRequest(Uri uri) {
|
|
WebRequest request = base.GetWebRequest(uri);
|
|
HttpWebRequest httpRequest = request as HttpWebRequest;
|
|
if (httpRequest != null) {
|
|
httpRequest.UserAgent = UserAgent;
|
|
httpRequest.AllowAutoRedirect = allowAutoRedirect;
|
|
httpRequest.AutomaticDecompression = enableDecompression ? DecompressionMethods.GZip : DecompressionMethods.None;
|
|
httpRequest.AllowWriteStreamBuffering = true;
|
|
httpRequest.SendChunked = false;
|
|
if (unsafeAuthenticatedConnectionSharing != httpRequest.UnsafeAuthenticatedConnectionSharing)
|
|
httpRequest.UnsafeAuthenticatedConnectionSharing = unsafeAuthenticatedConnectionSharing;
|
|
// if the user has set a proxy explictly then we need to
|
|
// propagate that to the WebRequest, otherwise we'll let NetClasses
|
|
// use their global setting (GlobalProxySelection.Select).
|
|
if (proxy != null) {
|
|
httpRequest.Proxy = proxy;
|
|
}
|
|
if (clientCertificates != null && clientCertificates.Count > 0) {
|
|
httpRequest.ClientCertificates.AddRange(clientCertificates);
|
|
}
|
|
httpRequest.CookieContainer = cookieJar;
|
|
}
|
|
return request;
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="HttpWebClientProtocol.GetWebResponse"]/*' />
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
protected override WebResponse GetWebResponse(WebRequest request) {
|
|
WebResponse response = base.GetWebResponse(request);
|
|
return response;
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="HttpWebClientProtocol.GetWebResponse1"]/*' />
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result) {
|
|
WebResponse response = base.GetWebResponse(request, result);
|
|
return response;
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="HttpWebClientProtocol.UnsafeAuthenticatedConnectionSharing"]/*' />
|
|
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public bool UnsafeAuthenticatedConnectionSharing {
|
|
get { return unsafeAuthenticatedConnectionSharing; }
|
|
set { unsafeAuthenticatedConnectionSharing = value; }
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="ClientProtocol.CancelInvokeAsync"]/*' />
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
protected void CancelAsync(object userState) {
|
|
if (userState == null)
|
|
userState = NullToken;
|
|
WebClientAsyncResult result = OperationCompleted(userState, new object[] { null }, null, true);
|
|
if (result != null) {
|
|
result.Abort();
|
|
}
|
|
}
|
|
|
|
internal WebClientAsyncResult OperationCompleted(object userState, object[] parameters, Exception e, bool canceled) {
|
|
Debug.Assert(userState != null, "We should not call OperationCompleted with null user token.");
|
|
WebClientAsyncResult result = (WebClientAsyncResult)AsyncInvokes[userState];
|
|
if (result != null) {
|
|
AsyncOperation asyncOp = (AsyncOperation)result.AsyncState;
|
|
UserToken token = (UserToken)asyncOp.UserSuppliedState;
|
|
InvokeCompletedEventArgs eventArgs = new InvokeCompletedEventArgs(parameters, e, canceled, userState);
|
|
AsyncInvokes.Remove(userState);
|
|
asyncOp.PostOperationCompleted(token.Callback, eventArgs);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="HttpWebClientProtocol.GenerateXmlMappings"]/*' />
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
public static bool GenerateXmlMappings(Type type, ArrayList mappings) {
|
|
if (typeof(SoapHttpClientProtocol).IsAssignableFrom(type)) {
|
|
WebServiceBindingAttribute binding = WebServiceBindingReflector.GetAttribute(type);
|
|
if (binding == null)
|
|
throw new InvalidOperationException(Res.GetString(Res.WebClientBindingAttributeRequired));
|
|
// Note: Service namespace is taken from WebserviceBindingAttribute and not WebserviceAttribute because
|
|
// the generated proxy does not have a WebServiceAttribute; however all have a WebServiceBindingAttribute.
|
|
string serviceNamespace = binding.Namespace;
|
|
bool serviceDefaultIsEncoded = SoapReflector.ServiceDefaultIsEncoded(type);
|
|
ArrayList soapMethodList = new ArrayList();
|
|
SoapClientType.GenerateXmlMappings(type, soapMethodList, serviceNamespace, serviceDefaultIsEncoded, mappings);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <include file='doc\ClientProtocol.uex' path='docs/doc[@for="HttpWebClientProtocol.GenerateXmlMappings1"]/*' />
|
|
/// <devdoc>
|
|
/// <para>[To be supplied.]</para>
|
|
/// </devdoc>
|
|
public static Hashtable GenerateXmlMappings(Type[] types, ArrayList mappings) {
|
|
if (types == null)
|
|
throw new ArgumentNullException("types");
|
|
|
|
Hashtable mappedTypes = new Hashtable();
|
|
foreach (Type type in types) {
|
|
ArrayList typeMappings = new ArrayList();
|
|
if (GenerateXmlMappings(type, mappings)) {
|
|
mappedTypes.Add(type, typeMappings);
|
|
mappings.Add(typeMappings);
|
|
}
|
|
}
|
|
return mappedTypes;
|
|
}
|
|
}
|
|
}
|