* Removing some debug noise git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@2095 52acb1d6-8a22-11de-b505-999d5b087335
551 lines
20 KiB
C#
551 lines
20 KiB
C#
/*
|
|
* Copyright (c) 2007-2008, openmetaverse.org
|
|
* All rights reserved.
|
|
*
|
|
* - Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* - Redistributions of source code must retain the above copyright notice, this
|
|
* list of conditions and the following disclaimer.
|
|
* - Neither the name of the openmetaverse.org nor the names
|
|
* of its contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Net;
|
|
using System.IO;
|
|
using System.Threading;
|
|
|
|
namespace OpenMetaverse.Capabilities
|
|
{
|
|
public class CapsBase
|
|
{
|
|
#region Callback Data Classes
|
|
|
|
public class OpenWriteCompletedEventArgs
|
|
{
|
|
public Stream Result;
|
|
public Exception Error;
|
|
public bool Cancelled;
|
|
public object UserState;
|
|
|
|
public OpenWriteCompletedEventArgs(Stream result, Exception error, bool cancelled, object userState)
|
|
{
|
|
Result = result;
|
|
Error = error;
|
|
Cancelled = cancelled;
|
|
UserState = userState;
|
|
}
|
|
}
|
|
|
|
public class UploadDataCompletedEventArgs
|
|
{
|
|
public byte[] Result;
|
|
public Exception Error;
|
|
public bool Cancelled;
|
|
public object UserState;
|
|
|
|
public UploadDataCompletedEventArgs(byte[] result, Exception error, bool cancelled, object userState)
|
|
{
|
|
Result = result;
|
|
Error = error;
|
|
Cancelled = cancelled;
|
|
UserState = userState;
|
|
}
|
|
}
|
|
|
|
public class DownloadDataCompletedEventArgs
|
|
{
|
|
public byte[] Result;
|
|
public Exception Error;
|
|
public bool Cancelled;
|
|
public object UserState;
|
|
}
|
|
|
|
public class DownloadStringCompletedEventArgs
|
|
{
|
|
public Uri Address;
|
|
public string Result;
|
|
public Exception Error;
|
|
public bool Cancelled;
|
|
public object UserState;
|
|
|
|
public DownloadStringCompletedEventArgs(Uri address, string result, Exception error, bool cancelled, object userState)
|
|
{
|
|
Address = address;
|
|
Result = result;
|
|
Error = error;
|
|
Cancelled = cancelled;
|
|
UserState = userState;
|
|
}
|
|
}
|
|
|
|
public class DownloadProgressChangedEventArgs
|
|
{
|
|
public long BytesReceived;
|
|
public int ProgressPercentage;
|
|
public long TotalBytesToReceive;
|
|
public object UserState;
|
|
|
|
public DownloadProgressChangedEventArgs(long bytesReceived, long totalBytesToReceive, object userToken)
|
|
{
|
|
BytesReceived = bytesReceived;
|
|
ProgressPercentage = (int)(((float)bytesReceived / (float)totalBytesToReceive) * 100f);
|
|
TotalBytesToReceive = totalBytesToReceive;
|
|
UserState = userToken;
|
|
}
|
|
}
|
|
|
|
public class UploadProgressChangedEventArgs
|
|
{
|
|
public long BytesReceived;
|
|
public long BytesSent;
|
|
public int ProgressPercentage;
|
|
public long TotalBytesToReceive;
|
|
public long TotalBytesToSend;
|
|
public object UserState;
|
|
|
|
public UploadProgressChangedEventArgs(long bytesReceived, long totalBytesToReceive, long bytesSent, long totalBytesToSend, object userState)
|
|
{
|
|
BytesReceived = bytesReceived;
|
|
TotalBytesToReceive = totalBytesToReceive;
|
|
ProgressPercentage = (int)(((float)bytesSent / (float)totalBytesToSend) * 100f);
|
|
BytesSent = bytesSent;
|
|
TotalBytesToSend = totalBytesToSend;
|
|
UserState = userState;
|
|
}
|
|
}
|
|
|
|
#endregion Callback Data Classes
|
|
|
|
public delegate void OpenWriteCompletedEventHandler(object sender, OpenWriteCompletedEventArgs e);
|
|
public delegate void UploadDataCompletedEventHandler(object sender, UploadDataCompletedEventArgs e);
|
|
public delegate void DownloadStringCompletedEventHandler(object sender, DownloadStringCompletedEventArgs e);
|
|
public delegate void DownloadProgressChangedEventHandler(object sender, DownloadProgressChangedEventArgs e);
|
|
public delegate void UploadProgressChangedEventHandler(object sender, UploadProgressChangedEventArgs e);
|
|
|
|
public event OpenWriteCompletedEventHandler OpenWriteCompleted;
|
|
public event UploadDataCompletedEventHandler UploadDataCompleted;
|
|
public event DownloadStringCompletedEventHandler DownloadStringCompleted;
|
|
public event DownloadProgressChangedEventHandler DownloadProgressChanged;
|
|
public event UploadProgressChangedEventHandler UploadProgressChanged;
|
|
|
|
public WebHeaderCollection Headers = new WebHeaderCollection();
|
|
public IWebProxy Proxy;
|
|
|
|
public Uri Location { get { return location; } }
|
|
public bool IsBusy { get { return isBusy; } }
|
|
public WebHeaderCollection ResponseHeaders { get { return responseHeaders; } }
|
|
|
|
protected WebHeaderCollection responseHeaders;
|
|
protected Uri location;
|
|
protected bool isBusy;
|
|
protected Thread asyncThread;
|
|
protected System.Text.Encoding encoding = System.Text.Encoding.Default;
|
|
|
|
public CapsBase(Uri location)
|
|
{
|
|
this.location = location;
|
|
}
|
|
|
|
public void OpenWriteAsync(Uri address)
|
|
{
|
|
OpenWriteAsync(address, null, null);
|
|
}
|
|
|
|
public void OpenWriteAsync(Uri address, string method)
|
|
{
|
|
OpenWriteAsync(address, method, null);
|
|
}
|
|
|
|
public void OpenWriteAsync(Uri address, string method, object userToken)
|
|
{
|
|
if (address == null)
|
|
throw new ArgumentNullException("address");
|
|
|
|
SetBusy();
|
|
|
|
asyncThread = new Thread(delegate(object state)
|
|
{
|
|
object[] args = (object[])state;
|
|
WebRequest request = null;
|
|
|
|
try
|
|
{
|
|
request = SetupRequest((Uri)args[0]);
|
|
Stream stream = request.GetRequestStream();
|
|
|
|
OnOpenWriteCompleted(new OpenWriteCompletedEventArgs(
|
|
stream, null, false, args[2]));
|
|
}
|
|
catch (ThreadInterruptedException)
|
|
{
|
|
if (request != null)
|
|
request.Abort();
|
|
|
|
OnOpenWriteCompleted(new OpenWriteCompletedEventArgs(
|
|
null, null, true, args[2]));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
OnOpenWriteCompleted(new OpenWriteCompletedEventArgs(
|
|
null, e, false, args[2]));
|
|
}
|
|
});
|
|
|
|
object[] cbArgs = new object[] { address, method, userToken };
|
|
asyncThread.Start(cbArgs);
|
|
}
|
|
|
|
public void UploadDataAsync(Uri address, byte[] data)
|
|
{
|
|
UploadDataAsync(address, null, data, null);
|
|
}
|
|
|
|
public void UploadDataAsync(Uri address, string method, byte[] data)
|
|
{
|
|
UploadDataAsync(address, method, data, null);
|
|
}
|
|
|
|
public void UploadDataAsync(Uri address, string method, byte[] data, object userToken)
|
|
{
|
|
if (address == null)
|
|
throw new ArgumentNullException("address");
|
|
if (data == null)
|
|
throw new ArgumentNullException("data");
|
|
|
|
SetBusy();
|
|
|
|
asyncThread = new Thread(delegate(object state)
|
|
{
|
|
object[] args = (object[])state;
|
|
byte[] data2;
|
|
|
|
try
|
|
{
|
|
data2 = UploadDataCore((Uri)args[0], (string)args[1], (byte[])args[2], args[3]);
|
|
|
|
OnUploadDataCompleted(
|
|
new UploadDataCompletedEventArgs(data2, null, false, args[3]));
|
|
}
|
|
catch (ThreadInterruptedException)
|
|
{
|
|
OnUploadDataCompleted(
|
|
new UploadDataCompletedEventArgs(null, null, true, args[3]));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
OnUploadDataCompleted(
|
|
new UploadDataCompletedEventArgs(null, e, false, args[3]));
|
|
}
|
|
});
|
|
|
|
object[] cbArgs = new object[] { address, method, data, userToken };
|
|
asyncThread.Start(cbArgs);
|
|
}
|
|
|
|
public void DownloadStringAsync(Uri address)
|
|
{
|
|
DownloadStringAsync(address, null);
|
|
}
|
|
|
|
public void DownloadStringAsync(Uri address, object userToken)
|
|
{
|
|
if (address == null)
|
|
throw new ArgumentNullException("address");
|
|
|
|
SetBusy();
|
|
|
|
asyncThread = new Thread(delegate(object state)
|
|
{
|
|
object[] args = (object[])state;
|
|
|
|
try
|
|
{
|
|
string data = encoding.GetString(DownloadDataCore((Uri)args[0], args[1]));
|
|
OnDownloadStringCompleted(
|
|
new DownloadStringCompletedEventArgs(location, data, null, false, args[1]));
|
|
}
|
|
catch (ThreadInterruptedException)
|
|
{
|
|
OnDownloadStringCompleted(
|
|
new DownloadStringCompletedEventArgs(location, null, null, true, args[1]));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
OnDownloadStringCompleted(
|
|
new DownloadStringCompletedEventArgs(location, null, e, false, args[1]));
|
|
}
|
|
});
|
|
|
|
object[] cbArgs = new object[] { address, userToken };
|
|
asyncThread.Start(cbArgs);
|
|
}
|
|
|
|
public void CancelAsync()
|
|
{
|
|
if (asyncThread == null)
|
|
return;
|
|
|
|
Thread t = asyncThread;
|
|
CompleteAsync();
|
|
t.Interrupt();
|
|
}
|
|
|
|
protected void CompleteAsync()
|
|
{
|
|
isBusy = false;
|
|
asyncThread = null;
|
|
}
|
|
|
|
protected void SetBusy()
|
|
{
|
|
CheckBusy();
|
|
isBusy = true;
|
|
}
|
|
|
|
protected void CheckBusy()
|
|
{
|
|
if (isBusy)
|
|
throw new NotSupportedException("CapsBase does not support concurrent I/O operations.");
|
|
}
|
|
|
|
protected Stream ProcessResponse(WebResponse response)
|
|
{
|
|
responseHeaders = response.Headers;
|
|
return response.GetResponseStream();
|
|
}
|
|
|
|
protected byte[] ReadAll(Stream stream, int length, object userToken, bool uploading)
|
|
{
|
|
MemoryStream ms = null;
|
|
|
|
bool nolength = (length == -1);
|
|
int size = ((nolength) ? 8192 : length);
|
|
if (nolength)
|
|
ms = new MemoryStream();
|
|
|
|
long total = 0;
|
|
int nread = 0;
|
|
int offset = 0;
|
|
byte[] buffer = new byte[size];
|
|
|
|
while ((nread = stream.Read(buffer, offset, size)) != 0)
|
|
{
|
|
if (nolength)
|
|
{
|
|
ms.Write(buffer, 0, nread);
|
|
}
|
|
else
|
|
{
|
|
offset += nread;
|
|
size -= nread;
|
|
}
|
|
|
|
if (uploading)
|
|
{
|
|
if (UploadProgressChanged != null)
|
|
{
|
|
total += nread;
|
|
UploadProgressChanged(this, new UploadProgressChangedEventArgs(nread, length, 0, 0, userToken));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (DownloadProgressChanged != null)
|
|
{
|
|
total += nread;
|
|
DownloadProgressChanged(this, new DownloadProgressChangedEventArgs(nread, length, userToken));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nolength)
|
|
return ms.ToArray();
|
|
|
|
return buffer;
|
|
}
|
|
|
|
protected WebRequest SetupRequest(Uri uri)
|
|
{
|
|
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
|
|
|
|
if (request == null)
|
|
throw new ArgumentException("Could not create an HttpWebRequest from the given Uri", "address");
|
|
|
|
location = uri;
|
|
|
|
if (Proxy != null)
|
|
request.Proxy = Proxy;
|
|
|
|
request.Method = "POST";
|
|
|
|
if (Headers != null && Headers.Count != 0)
|
|
{
|
|
string expect = Headers["Expect"];
|
|
string contentType = Headers["Content-Type"];
|
|
string accept = Headers["Accept"];
|
|
string connection = Headers["Connection"];
|
|
string userAgent = Headers["User-Agent"];
|
|
string referer = Headers["Referer"];
|
|
|
|
if (!String.IsNullOrEmpty(expect))
|
|
request.Expect = expect;
|
|
|
|
if (!String.IsNullOrEmpty(accept))
|
|
request.Accept = accept;
|
|
|
|
if (!String.IsNullOrEmpty(contentType))
|
|
request.ContentType = contentType;
|
|
|
|
if (!String.IsNullOrEmpty(connection))
|
|
request.Connection = connection;
|
|
|
|
if (!String.IsNullOrEmpty(userAgent))
|
|
request.UserAgent = userAgent;
|
|
|
|
if (!String.IsNullOrEmpty(referer))
|
|
request.Referer = referer;
|
|
}
|
|
|
|
// Disable keep-alive by default
|
|
request.KeepAlive = false;
|
|
// Set the closed connection (idle) time to one second
|
|
request.ServicePoint.MaxIdleTime = 1000;
|
|
// Disable stupid Expect-100: Continue header
|
|
request.ServicePoint.Expect100Continue = false;
|
|
// Crank up the max number of connections (default is 2!)
|
|
request.ServicePoint.ConnectionLimit = 20;
|
|
|
|
return request;
|
|
}
|
|
|
|
protected WebRequest SetupRequest(Uri uri, string method)
|
|
{
|
|
WebRequest request = SetupRequest(uri);
|
|
request.Method = method;
|
|
return request;
|
|
}
|
|
|
|
protected byte[] UploadDataCore(Uri address, string method, byte[] data, object userToken)
|
|
{
|
|
HttpWebRequest request = (HttpWebRequest)SetupRequest(address);
|
|
|
|
// Mono insists that if you have Content-Length set, Keep-Alive must be true.
|
|
// Otherwise the unhelpful exception of "Content-Length not set" will be thrown.
|
|
// The Linden Lab event queue server breaks HTTP 1.1 by always replying with a
|
|
// Connection: Close header, which will confuse the Windows .NET runtime and throw
|
|
// a "Connection unexpectedly closed" exception. This is our cross-platform hack
|
|
if (Utils.GetRunningRuntime() == Utils.Runtime.Mono)
|
|
request.KeepAlive = true;
|
|
|
|
try
|
|
{
|
|
// Content-Length
|
|
int contentLength = data.Length;
|
|
request.ContentLength = contentLength;
|
|
|
|
using (Stream stream = request.GetRequestStream())
|
|
{
|
|
// Most uploads are very small chunks of data, use an optimized path for these
|
|
if (contentLength < 4096)
|
|
{
|
|
stream.Write(data, 0, contentLength);
|
|
}
|
|
else
|
|
{
|
|
// Upload chunks directly instead of buffering to memory
|
|
request.AllowWriteStreamBuffering = false;
|
|
|
|
MemoryStream ms = new MemoryStream(data);
|
|
|
|
byte[] buffer = new byte[checked((uint)Math.Min(4096, (int)contentLength))];
|
|
int bytesRead = 0;
|
|
|
|
while ((bytesRead = ms.Read(buffer, 0, buffer.Length)) != 0)
|
|
{
|
|
stream.Write(buffer, 0, bytesRead);
|
|
|
|
if (UploadProgressChanged != null)
|
|
{
|
|
UploadProgressChanged(this, new UploadProgressChangedEventArgs(0, 0, bytesRead, contentLength, userToken));
|
|
}
|
|
}
|
|
|
|
ms.Close();
|
|
}
|
|
}
|
|
|
|
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
|
|
Stream st = ProcessResponse(response);
|
|
|
|
contentLength = (int)response.ContentLength;
|
|
return ReadAll(st, contentLength, userToken, true);
|
|
}
|
|
catch (ThreadInterruptedException)
|
|
{
|
|
if (request != null)
|
|
request.Abort();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
protected byte[] DownloadDataCore(Uri address, object userToken)
|
|
{
|
|
WebRequest request = null;
|
|
|
|
try
|
|
{
|
|
request = SetupRequest(address, "GET");
|
|
WebResponse response = request.GetResponse();
|
|
Stream st = ProcessResponse(response);
|
|
return ReadAll(st, (int)response.ContentLength, userToken, false);
|
|
}
|
|
catch (ThreadInterruptedException)
|
|
{
|
|
if (request != null)
|
|
request.Abort();
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new WebException("An error occurred performing a WebClient request.", ex);
|
|
}
|
|
}
|
|
|
|
protected virtual void OnOpenWriteCompleted(OpenWriteCompletedEventArgs args)
|
|
{
|
|
CompleteAsync();
|
|
if (OpenWriteCompleted != null)
|
|
OpenWriteCompleted(this, args);
|
|
}
|
|
|
|
protected virtual void OnUploadDataCompleted(UploadDataCompletedEventArgs args)
|
|
{
|
|
CompleteAsync();
|
|
if (UploadDataCompleted != null)
|
|
UploadDataCompleted(this, args);
|
|
}
|
|
|
|
protected virtual void OnDownloadStringCompleted(DownloadStringCompletedEventArgs args)
|
|
{
|
|
CompleteAsync();
|
|
if (DownloadStringCompleted != null)
|
|
DownloadStringCompleted(this, args);
|
|
}
|
|
}
|
|
}
|