diff --git a/OpenMetaverse.Http/CapsBase.cs b/OpenMetaverse.Http/CapsBase.cs index 28f057a0..a30c49b6 100644 --- a/OpenMetaverse.Http/CapsBase.cs +++ b/OpenMetaverse.Http/CapsBase.cs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007-2008, openmetaverse.org + * Copyright (c) 2009, openmetaverse.org * All rights reserved. * * - Redistribution and use in source and binary forms, with or without @@ -27,529 +27,202 @@ using System; using System.Net; using System.IO; +using System.Text; using System.Threading; using System.Security.Cryptography.X509Certificates; namespace OpenMetaverse.Http { - public class CapsBase + public static class CapsBase { - #region Callback Data Classes + public delegate void OpenWriteEventHandler(HttpWebRequest request); + public delegate void DownloadProgressEventHandler(HttpWebRequest request, HttpWebResponse response, int bytesReceived, int totalBytesToReceive); + public delegate void RequestCompletedEventHandler(HttpWebRequest request, HttpWebResponse response, byte[] responseData, Exception error); - public class OpenWriteCompletedEventArgs + private class RequestState { - public Stream Result; - public Exception Error; - public bool Cancelled; - public object UserState; + public HttpWebRequest Request; + public byte[] UploadData; + public int MillisecondsTimeout; + public OpenWriteEventHandler OpenWriteCallback; + public DownloadProgressEventHandler DownloadProgressCallback; + public RequestCompletedEventHandler CompletedCallback; - public OpenWriteCompletedEventArgs(Stream result, Exception error, bool cancelled, object userState) + public RequestState(HttpWebRequest request, byte[] uploadData, int millisecondsTimeout, OpenWriteEventHandler openWriteCallback, + DownloadProgressEventHandler downloadProgressCallback, RequestCompletedEventHandler completedCallback) { - Result = result; - Error = error; - Cancelled = cancelled; - UserState = userState; + Request = request; + UploadData = uploadData; + MillisecondsTimeout = millisecondsTimeout; + OpenWriteCallback = openWriteCallback; + DownloadProgressCallback = downloadProgressCallback; + CompletedCallback = completedCallback; } } - public class UploadDataCompletedEventArgs + public static HttpWebRequest UploadDataAsync(Uri address, X509Certificate2 clientCert, string contentType, byte[] data, + int millisecondsTimeout, OpenWriteEventHandler openWriteCallback, DownloadProgressEventHandler downloadProgressCallback, + RequestCompletedEventHandler completedCallback) { - 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 X509Certificate2 ClientCertificate; - - 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, X509Certificate2 clientCert) - { - this.location = location; - ClientCertificate = clientCert; - } - - 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() - { - WebRequest request = null; - - try - { - request = SetupRequest(address); - Stream stream = request.GetRequestStream(); - - OnOpenWriteCompleted(new OpenWriteCompletedEventArgs( - stream, null, false, userToken)); - } - catch (ThreadInterruptedException) - { - if (request != null) - request.Abort(); - - OnOpenWriteCompleted(new OpenWriteCompletedEventArgs( - null, null, true, userToken)); - } - catch (Exception e) - { - OnOpenWriteCompleted(new OpenWriteCompletedEventArgs( - null, e, false, userToken)); - } - } - ); - asyncThread.IsBackground = true; - asyncThread.Start(); - } - - 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.IsBackground = true; - 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() - { - try - { - string data = encoding.GetString(DownloadDataCore(address, userToken)); - OnDownloadStringCompleted( - new DownloadStringCompletedEventArgs(location, data, null, false, userToken)); - } - catch (ThreadInterruptedException) - { - OnDownloadStringCompleted( - new DownloadStringCompletedEventArgs(location, null, null, true, userToken)); - } - catch (Exception e) - { - OnDownloadStringCompleted( - new DownloadStringCompletedEventArgs(location, null, e, false, userToken)); - } - } - ); - asyncThread.Start(); - } - - 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; - - if (ClientCertificate != null) - request.ClientCertificates.Add(ClientCertificate); - + // Create the request + HttpWebRequest request = SetupRequest(address, clientCert); + request.ContentLength = data.Length; + if (!String.IsNullOrEmpty(contentType)) + request.ContentType = contentType; 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"]; + // Create an object to hold all of the state for this request + RequestState state = new RequestState(request, data, millisecondsTimeout, openWriteCallback, + downloadProgressCallback, completedCallback); - if (!String.IsNullOrEmpty(expect)) - request.Expect = expect; + // Start the request for a stream to upload to + IAsyncResult result = request.BeginGetRequestStream(OpenWrite, state); + // Register a timeout for the request + ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, TimeoutCallback, state, millisecondsTimeout, true); - if (!String.IsNullOrEmpty(accept)) - request.Accept = accept; + return request; + } - if (!String.IsNullOrEmpty(contentType)) - request.ContentType = contentType; + public static HttpWebRequest DownloadStringAsync(Uri address, X509Certificate2 clientCert, int millisecondsTimeout, + DownloadProgressEventHandler downloadProgressCallback, RequestCompletedEventHandler completedCallback) + { + // Create the request + HttpWebRequest request = SetupRequest(address, clientCert); + request.Method = "GET"; - if (!String.IsNullOrEmpty(connection)) - request.Connection = connection; + // Create an object to hold all of the state for this request + RequestState state = new RequestState(request, null, millisecondsTimeout, null, downloadProgressCallback, + completedCallback); - if (!String.IsNullOrEmpty(userAgent)) - request.UserAgent = userAgent; + // Start the request for the remote server response + IAsyncResult result = request.BeginGetResponse(GetResponse, state); + // Register a timeout for the request + ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, TimeoutCallback, state, millisecondsTimeout, true); - if (!String.IsNullOrEmpty(referer)) - request.Referer = referer; - } + return request; + } - // Disable keep-alive by default - request.KeepAlive = false; - // Set the closed connection (idle) time to one second - request.ServicePoint.MaxIdleTime = 1000; + static HttpWebRequest SetupRequest(Uri address, X509Certificate2 clientCert) + { + if (address == null) + throw new ArgumentNullException("address"); + + // Create the request + HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(address); + + // Add the client certificate to the request if one was given + if (clientCert != null) + request.ClientCertificates.Add(clientCert); + + // Leave idle connections to this endpoint open for up to 60 seconds + request.ServicePoint.MaxIdleTime = 1000 * 60; // Disable stupid Expect-100: Continue header request.ServicePoint.Expect100Continue = false; - // Crank up the max number of connections (default is 2!) - request.ServicePoint.ConnectionLimit = Int32.MaxValue; + // Crank up the max number of connections per endpoint (default is 2!) + request.ServicePoint.ConnectionLimit = 20; + // Caps requests are never sent as trickles of data, so Nagle's + // coalescing algorithm won't help us + request.ServicePoint.UseNagleAlgorithm = false; return request; } - protected WebRequest SetupRequest(Uri uri, string method) + static void OpenWrite(IAsyncResult ar) { - 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; + RequestState state = (RequestState)ar.AsyncState; try { - // Content-Length - int contentLength = data.Length; - request.ContentLength = contentLength; + // Get the stream to write our upload to + Stream uploadStream = state.Request.EndGetRequestStream(ar); - 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; + // Fire the callback for successfully opening the stream + if (state.OpenWriteCallback != null) + state.OpenWriteCallback(state.Request); - MemoryStream ms = new MemoryStream(data); + // Write our data to the upload stream + uploadStream.Write(state.UploadData, 0, state.UploadData.Length); - 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; + // Start the request for the remote server response + IAsyncResult result = state.Request.BeginGetResponse(GetResponse, state); + // Register a timeout for the request + ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, TimeoutCallback, state, + state.MillisecondsTimeout, true); } catch (Exception ex) { - throw new WebException("An error occurred performing a WebClient request.", ex); + if (state.CompletedCallback != null) + state.CompletedCallback(state.Request, null, null, ex); } } - protected virtual void OnOpenWriteCompleted(OpenWriteCompletedEventArgs args) + static void GetResponse(IAsyncResult ar) { - CompleteAsync(); - if (OpenWriteCompleted != null) - OpenWriteCompleted(this, args); + RequestState state = (RequestState)ar.AsyncState; + HttpWebResponse response = null; + byte[] responseData = null; + Exception error = null; + + try + { + response = (HttpWebResponse)state.Request.EndGetResponse(ar); + + // Get the stream for downloading the response + Stream responseStream = response.GetResponseStream(); + + #region Read the response + + // If Content-Length is set we create a buffer of the exact size, otherwise + // a MemoryStream is used to receive the response + bool nolength = (response.ContentLength <= 0); + int size = (nolength) ? 8192 : (int)response.ContentLength; + MemoryStream ms = (nolength) ? new MemoryStream() : null; + byte[] buffer = new byte[size]; + + int bytesRead = 0; + int offset = 0; + + while ((bytesRead = responseStream.Read(buffer, offset, size)) != 0) + { + if (nolength) + { + ms.Write(buffer, 0, bytesRead); + } + else + { + offset += bytesRead; + size -= bytesRead; + } + + // Fire the download progress callback for each chunk of received data + if (state.DownloadProgressCallback != null) + state.DownloadProgressCallback(state.Request, response, bytesRead, size); + } + + if (nolength) + responseData = ms.ToArray(); + else + responseData = buffer; + + #endregion Read the response + } + catch (Exception ex) + { + error = ex; + } + + if (state.CompletedCallback != null) + state.CompletedCallback(state.Request, response, responseData, error); } - protected virtual void OnUploadDataCompleted(UploadDataCompletedEventArgs args) + static void TimeoutCallback(object state, bool timedOut) { - CompleteAsync(); - if (UploadDataCompleted != null) - UploadDataCompleted(this, args); - } - - protected virtual void OnDownloadStringCompleted(DownloadStringCompletedEventArgs args) - { - CompleteAsync(); - if (DownloadStringCompleted != null) - DownloadStringCompleted(this, args); + if (timedOut) + { + RequestState requestState = state as RequestState; + if (requestState != null && requestState.Request != null) + requestState.Request.Abort(); + } } } } diff --git a/OpenMetaverse.Http/CapsClient.cs b/OpenMetaverse.Http/CapsClient.cs index 3c3c2f73..3684b4e7 100644 --- a/OpenMetaverse.Http/CapsClient.cs +++ b/OpenMetaverse.Http/CapsClient.cs @@ -34,272 +34,154 @@ namespace OpenMetaverse.Http { public class CapsClient { - public delegate void ProgressCallback(CapsClient client, long bytesReceived, long bytesSent, - long totalBytesToReceive, long totalBytesToSend); + public delegate void DownloadProgressCallback(CapsClient client, int bytesReceived, int totalBytesToReceive); public delegate void CompleteCallback(CapsClient client, OSD result, Exception error); - public event ProgressCallback OnProgress; + public event DownloadProgressCallback OnDownloadProgress; public event CompleteCallback OnComplete; - public IWebProxy Proxy; public object UserData; - protected CapsBase _Client; + protected Uri _Address; protected byte[] _PostData; + protected X509Certificate2 _ClientCert; protected string _ContentType; + protected HttpWebRequest _Request; + protected OSD _Response; + protected AutoResetEvent _ResponseEvent = new AutoResetEvent(false); public CapsClient(Uri capability) + : this(capability, null) { - Init(capability, null); } public CapsClient(Uri capability, X509Certificate2 clientCert) { - Init(capability, clientCert); + _Address = capability; + _ClientCert = clientCert; } - void Init(Uri capability, X509Certificate2 clientCert) + public void BeginGetResponse(int millisecondsTimeout) { - _Client = new CapsBase(capability, clientCert); - _Client.DownloadProgressChanged += new CapsBase.DownloadProgressChangedEventHandler(Client_DownloadProgressChanged); - _Client.UploadProgressChanged += new CapsBase.UploadProgressChangedEventHandler(Client_UploadProgressChanged); - _Client.UploadDataCompleted += new CapsBase.UploadDataCompletedEventHandler(Client_UploadDataCompleted); - _Client.DownloadStringCompleted += new CapsBase.DownloadStringCompletedEventHandler(Client_DownloadStringCompleted); + BeginGetResponse(null, null, millisecondsTimeout); } - public void BeginGetResponse() + public void BeginGetResponse(OSD data, OSDFormat format, int millisecondsTimeout) { - BeginGetResponse(null, null); - } + byte[] postData; + string contentType; - public void BeginGetResponse(OSD data) - { - byte[] postData = OSDParser.SerializeLLSDXmlBytes(data); - BeginGetResponse(postData, null); - } - - public void BeginGetResponse(OSD data, bool json) - { - if (json) + switch (format) { - byte[] postData = System.Text.Encoding.UTF8.GetBytes(OSDParser.SerializeJsonString(data)); - BeginGetResponse(postData, "application/json"); - } - else - { - BeginGetResponse(data); + case OSDFormat.Xml: + postData = OSDParser.SerializeLLSDXmlBytes(data); + contentType = "application/llsd+xml"; + break; + case OSDFormat.Binary: + postData = OSDParser.SerializeLLSDBinary(data); + contentType = "application/llsd+binary"; + break; + case OSDFormat.Json: + default: + postData = System.Text.Encoding.UTF8.GetBytes(OSDParser.SerializeJsonString(data)); + contentType = "application/llsd+json"; + break; } + + BeginGetResponse(postData, contentType, millisecondsTimeout); } - public void BeginGetResponse(byte[] postData) - { - BeginGetResponse(postData, null); - } - - public void BeginGetResponse(byte[] postData, string contentType) + public void BeginGetResponse(byte[] postData, string contentType, int millisecondsTimeout) { _PostData = postData; _ContentType = contentType; - if (_Client.IsBusy) - _Client.CancelAsync(); - - _Client.Headers.Clear(); - - // Proxy - if (Proxy != null) - _Client.Proxy = Proxy; - - // Content-Type - if (!String.IsNullOrEmpty(contentType)) - _Client.Headers.Add(HttpRequestHeader.ContentType, contentType); - else - _Client.Headers.Add(HttpRequestHeader.ContentType, "application/xml"); + if (_Request != null) + { + _Request.Abort(); + _Request = null; + } if (postData == null) - _Client.DownloadStringAsync(_Client.Location); + { + // GET + Logger.Log.Debug("[CapsClient] GET " + _Address); + _Request = CapsBase.DownloadStringAsync(_Address, _ClientCert, millisecondsTimeout, DownloadProgressHandler, + RequestCompletedHandler); + } else - _Client.UploadDataAsync(_Client.Location, postData); + { + // POST + Logger.Log.Debug("[CapsClient] POST (" + postData.Length + " bytes) " + _Address); + _Request = CapsBase.UploadDataAsync(_Address, _ClientCert, contentType, postData, millisecondsTimeout, null, + DownloadProgressHandler, RequestCompletedHandler); + } } public OSD GetResponse(int millisecondsTimeout) { - OSD response = null; - AutoResetEvent waitEvent = new AutoResetEvent(false); - OnComplete += delegate(CapsClient client, OSD result, Exception error) { response = result; waitEvent.Set(); }; - BeginGetResponse(); - waitEvent.WaitOne(millisecondsTimeout, false); - return response; + BeginGetResponse(millisecondsTimeout); + _ResponseEvent.WaitOne(millisecondsTimeout, false); + return _Response; } - public OSD GetResponse(OSD data, int millisecondsTimeout) + public OSD GetResponse(OSD data, OSDFormat format, int millisecondsTimeout) { - OSD response = null; - AutoResetEvent waitEvent = new AutoResetEvent(false); - OnComplete += delegate(CapsClient client, OSD result, Exception error) { response = result; waitEvent.Set(); }; - BeginGetResponse(data); - waitEvent.WaitOne(millisecondsTimeout, false); - return response; - } - - public OSD GetResponse(OSD data, bool json, int millisecondsTimeout) - { - OSD response = null; - AutoResetEvent waitEvent = new AutoResetEvent(false); - OnComplete += delegate(CapsClient client, OSD result, Exception error) { response = result; waitEvent.Set(); }; - BeginGetResponse(data, json); - waitEvent.WaitOne(millisecondsTimeout, false); - return response; - } - - public OSD GetResponse(byte[] postData, int millisecondsTimeout) - { - OSD response = null; - AutoResetEvent waitEvent = new AutoResetEvent(false); - OnComplete += delegate(CapsClient client, OSD result, Exception error) { response = result; waitEvent.Set(); }; - BeginGetResponse(postData); - waitEvent.WaitOne(millisecondsTimeout, false); - return response; + BeginGetResponse(data, format, millisecondsTimeout); + _ResponseEvent.WaitOne(millisecondsTimeout, false); + return _Response; } public OSD GetResponse(byte[] postData, string contentType, int millisecondsTimeout) { - OSD response = null; - AutoResetEvent waitEvent = new AutoResetEvent(false); - OnComplete += delegate(CapsClient client, OSD result, Exception error) { response = result; waitEvent.Set(); }; - BeginGetResponse(postData, contentType); - waitEvent.WaitOne(millisecondsTimeout, false); - return response; + BeginGetResponse(postData, contentType, millisecondsTimeout); + _ResponseEvent.WaitOne(millisecondsTimeout, false); + return _Response; } public void Cancel() { - if (_Client.IsBusy) - _Client.CancelAsync(); + if (_Request != null) + _Request.Abort(); } - #region Callback Handlers - - private void Client_DownloadProgressChanged(object sender, CapsBase.DownloadProgressChangedEventArgs e) + void DownloadProgressHandler(HttpWebRequest request, HttpWebResponse response, int bytesReceived, int totalBytesToReceive) { - if (OnProgress != null) + _Request = request; + + if (OnDownloadProgress != null) { - try { OnProgress(this, e.BytesReceived, 0, e.TotalBytesToReceive, 0); } + try { OnDownloadProgress(this, bytesReceived, totalBytesToReceive); } catch (Exception ex) { Logger.Log.Error(ex.Message, ex); } } } - private void Client_UploadProgressChanged(object sender, CapsBase.UploadProgressChangedEventArgs e) + void RequestCompletedHandler(HttpWebRequest request, HttpWebResponse response, byte[] responseData, Exception error) { - if (OnProgress != null) + _Request = request; + + OSD result = null; + + if (responseData != null) { - try { OnProgress(this, e.BytesReceived, e.BytesSent, e.TotalBytesToReceive, e.TotalBytesToSend); } + try { result = OSDParser.Deserialize(responseData); } + catch (Exception ex) { error = ex; } + } + + FireCompleteCallback(result, error); + } + + private void FireCompleteCallback(OSD result, Exception error) + { + CompleteCallback callback = OnComplete; + if (callback != null) + { + try { callback(this, result, error); } catch (Exception ex) { Logger.Log.Error(ex.Message, ex); } } + + _Response = result; + _ResponseEvent.Set(); } - - private void Client_UploadDataCompleted(object sender, CapsBase.UploadDataCompletedEventArgs e) - { - if (OnComplete != null && !e.Cancelled) - { - if (e.Error == null) - { - OSD result = OSDParser.Deserialize(e.Result); - - try { OnComplete(this, result, e.Error); } - catch (Exception ex) { Logger.Log.Error(ex.Message, ex); } - } - else - { - // Some error occurred, try to figure out what happened - HttpStatusCode code = HttpStatusCode.OK; - if (e.Error is WebException && ((WebException)e.Error).Response != null) - code = ((HttpWebResponse)((WebException)e.Error).Response).StatusCode; - - if (code == HttpStatusCode.BadGateway) - { - // This is not good (server) protocol design, but it's normal. - // The CAPS server is a proxy that connects to a Squid - // cache which will time out periodically. The CAPS server - // interprets this as a generic error and returns a 502 to us - // that we ignore - BeginGetResponse(_PostData, _ContentType); - } - else if (code != HttpStatusCode.OK) - { - // Status code was set to something unknown, this is a failure - Logger.Log.DebugFormat("Caps error at {0}: {1}", _Client.Location, code); - - try { OnComplete(this, null, e.Error); } - catch (Exception ex) { Logger.Log.Error(ex.Message, ex); } - } - else - { - // Status code was not set, some other error occurred. This is a failure - Logger.Log.DebugFormat("Caps error at {0}: {1}", _Client.Location, e.Error.Message); - - try { OnComplete(this, null, e.Error); } - catch (Exception ex) { Logger.Log.Error(ex.Message, ex); } - } - } - } - else if (e.Cancelled) - { - Logger.Log.Debug("Capability action at " + _Client.Location + " cancelled"); - } - } - - private void Client_DownloadStringCompleted(object sender, CapsBase.DownloadStringCompletedEventArgs e) - { - if (OnComplete != null && !e.Cancelled) - { - if (e.Error == null) - { - OSD result = OSDParser.DeserializeLLSDXml(e.Result); - - try { OnComplete(this, result, e.Error); } - catch (Exception ex) { Logger.Log.Error(ex.Message, ex); } - } - else - { - // Some error occurred, try to figure out what happened - HttpStatusCode code = HttpStatusCode.OK; - if (e.Error is WebException && ((WebException)e.Error).Response != null) - code = ((HttpWebResponse)((WebException)e.Error).Response).StatusCode; - - if (code == HttpStatusCode.BadGateway) - { - // This is not good (server) protocol design, but it's normal. - // The CAPS server is a proxy that connects to a Squid - // cache which will time out periodically. The CAPS server - // interprets this as a generic error and returns a 502 to us - // that we ignore - BeginGetResponse(_PostData, _ContentType); - } - else if (code != HttpStatusCode.OK) - { - // Status code was set to something unknown, this is a failure - Logger.Log.DebugFormat("Caps error at {0}: {1}", _Client.Location, code); - - try { OnComplete(this, null, e.Error); } - catch (Exception ex) { Logger.Log.Error(ex.Message, ex); } - } - else - { - // Status code was not set, some other error occurred. This is a failure - Logger.Log.DebugFormat("Caps error at {0}: {1}", _Client.Location, e.Error.Message); - - try { OnComplete(this, null, e.Error); } - catch (Exception ex) { Logger.Log.Error(ex.Message, ex); } - } - } - } - else if (e.Cancelled) - { - Logger.Log.Debug("Capability action at " + _Client.Location + " cancelled"); - } - } - - #endregion Callback Handlers } } diff --git a/OpenMetaverse.Http/EventQueueClient.cs b/OpenMetaverse.Http/EventQueueClient.cs index 04adf36c..4deda398 100644 --- a/OpenMetaverse.Http/EventQueueClient.cs +++ b/OpenMetaverse.Http/EventQueueClient.cs @@ -33,41 +33,39 @@ namespace OpenMetaverse.Http { public class EventQueueClient { - /// - /// - /// + /// = + public const int REQUEST_TIMEOUT = 1000 * 120; + public delegate void ConnectedCallback(); - /// - /// - /// - /// - /// public delegate void EventCallback(string eventName, OSDMap body); - /// public ConnectedCallback OnConnected; - /// public EventCallback OnEvent; - public IWebProxy Proxy; + public bool Running { get { return _Running; } } - public bool Running { get { return _Running && _Client.IsBusy; } } - - protected CapsBase _Client; + protected Uri _Address; protected bool _Dead; protected bool _Running; + protected HttpWebRequest _Request; public EventQueueClient(Uri eventQueueLocation) { - _Client = new CapsBase(eventQueueLocation, null); - _Client.OpenWriteCompleted += new CapsBase.OpenWriteCompletedEventHandler(Client_OpenWriteCompleted); - _Client.UploadDataCompleted += new CapsBase.UploadDataCompletedEventHandler(Client_UploadDataCompleted); + _Address = eventQueueLocation; } public void Start() { _Dead = false; - _Client.OpenWriteAsync(_Client.Location); + + // Create an EventQueueGet request + OSDMap request = new OSDMap(); + request["ack"] = new OSD(); + request["done"] = OSD.FromBoolean(false); + + byte[] postData = OSDParser.SerializeLLSDXmlBytes(request); + + _Request = CapsBase.UploadDataAsync(_Address, null, "application/xml", postData, REQUEST_TIMEOUT, OpenWriteHandler, null, RequestCompletedHandler); } public void Stop(bool immediate) @@ -77,60 +75,71 @@ namespace OpenMetaverse.Http if (immediate) _Running = false; - if (_Client.IsBusy) - _Client.CancelAsync(); + if (_Request != null) + _Request.Abort(); } - #region Callback Handlers - - private void Client_OpenWriteCompleted(object sender, CapsBase.OpenWriteCompletedEventArgs e) + void OpenWriteHandler(HttpWebRequest request) { - bool raiseEvent = false; + _Running = true; + _Request = request; - if (!_Dead) + Logger.Log.Debug("Capabilities event queue connected"); + + // The event queue is starting up for the first time + if (OnConnected != null) { - if (!_Running) raiseEvent = true; - - // We are connected to the event queue - _Running = true; - } - - // Create an EventQueueGet request - OSDMap request = new OSDMap(); - request["ack"] = new OSD(); - request["done"] = OSD.FromBoolean(false); - - byte[] postData = OSDParser.SerializeLLSDXmlBytes(request); - - _Client.UploadDataAsync(_Client.Location, postData); - - if (raiseEvent) - { - Logger.Log.Debug("Capabilities event queue connected"); - - // The event queue is starting up for the first time - if (OnConnected != null) - { - try { OnConnected(); } - catch (Exception ex) { Logger.Log.Error(ex.Message, ex); } - } + try { OnConnected(); } + catch (Exception ex) { Logger.Log.Error(ex.Message, ex); } } } - private void Client_UploadDataCompleted(object sender, CapsBase.UploadDataCompletedEventArgs e) + void RequestCompletedHandler(HttpWebRequest request, HttpWebResponse response, byte[] responseData, Exception error) { + // We don't care about this request now that it has completed + _Request = null; + OSDArray events = null; int ack = 0; - if (e.Error != null) + if (responseData != null) { + // Got a response + OSDMap result = OSDParser.DeserializeLLSDXml(responseData) as OSDMap; + + if (result != null) + { + events = result["events"] as OSDArray; + ack = result["id"].AsInteger(); + } + else + { + Logger.Log.Warn("Got an unparseable response from the event queue: \"" + + System.Text.Encoding.UTF8.GetString(responseData) + "\""); + } + } + else if (error != null) + { + #region Error handling + HttpStatusCode code = HttpStatusCode.OK; - if (e.Error is WebException && ((WebException)e.Error).Response != null) - code = ((HttpWebResponse)((WebException)e.Error).Response).StatusCode; + + if (error is WebException) + { + WebException webException = (WebException)error; + + if (webException.Response != null) + code = ((HttpWebResponse)webException.Response).StatusCode; + else if (webException.Status == WebExceptionStatus.RequestCanceled) + goto HandlingDone; + } + + if (error is WebException && ((WebException)error).Response != null) + code = ((HttpWebResponse)((WebException)error).Response).StatusCode; if (code == HttpStatusCode.NotFound || code == HttpStatusCode.Gone) { - Logger.Log.InfoFormat("Closing event queue at {0} due to missing caps URI", _Client.Location); + Logger.Log.InfoFormat("Closing event queue at {0} due to missing caps URI", _Address); _Running = false; _Dead = true; @@ -143,55 +152,50 @@ namespace OpenMetaverse.Http // interprets this as a generic error and returns a 502 to us // that we ignore } - else if (!e.Cancelled) + else { // Try to log a meaningful error message if (code != HttpStatusCode.OK) { Logger.Log.WarnFormat("Unrecognized caps connection problem from {0}: {1}", - _Client.Location, code); + _Address, code); } - else if (e.Error.InnerException != null) + else if (error.InnerException != null) { Logger.Log.WarnFormat("Unrecognized caps exception from {0}: {1}", - _Client.Location, e.Error.InnerException.Message); + _Address, error.InnerException.Message); } else { Logger.Log.WarnFormat("Unrecognized caps exception from {0}: {1}", - _Client.Location, e.Error.Message); + _Address, error.Message); } } - } - else if (!e.Cancelled && e.Result != null) - { - // Got a response - OSD result = OSDParser.DeserializeLLSDXml(e.Result); - if (result != null && result.Type == OSDType.Map) - { - // Parse any events returned by the event queue - OSDMap map = (OSDMap)result; - events = (OSDArray)map["events"]; - ack = map["id"].AsInteger(); - } + #endregion Error handling } - else if (e.Cancelled) + else { - // Connection was cancelled - Logger.Log.Debug("Cancelled connection to event queue at " + _Client.Location); + Logger.Log.Warn("No response from the event queue but no reported error either"); } + HandlingDone: + + #region Resume the connection + if (_Running) { - OSDMap request = new OSDMap(); - if (ack != 0) request["ack"] = OSD.FromInteger(ack); - else request["ack"] = new OSD(); - request["done"] = OSD.FromBoolean(_Dead); + OSDMap osdRequest = new OSDMap(); + if (ack != 0) osdRequest["ack"] = OSD.FromInteger(ack); + else osdRequest["ack"] = new OSD(); + osdRequest["done"] = OSD.FromBoolean(_Dead); - byte[] postData = OSDParser.SerializeLLSDXmlBytes(request); + byte[] postData = OSDParser.SerializeLLSDXmlBytes(osdRequest); - _Client.UploadDataAsync(_Client.Location, postData); + // Resume the connection. The event handler for the connection opening + // just sets class _Request variable to the current HttpWebRequest + CapsBase.UploadDataAsync(_Address, null, "application/xml", postData, REQUEST_TIMEOUT, + delegate(HttpWebRequest newRequest) { _Request = newRequest; }, null, RequestCompletedHandler); // If the event queue is dead at this point, turn it off since // that was the last thing we want to do @@ -202,6 +206,10 @@ namespace OpenMetaverse.Http } } + #endregion Resume the connection + + #region Handle incoming events + if (OnEvent != null && events != null && events.Count > 0) { // Fire callbacks for each event received @@ -214,8 +222,8 @@ namespace OpenMetaverse.Http catch (Exception ex) { Logger.Log.Error(ex.Message, ex); } } } - } - #endregion Callback Handlers + #endregion Handle incoming events + } } } diff --git a/OpenMetaverse.StructuredData/StructuredData.cs b/OpenMetaverse.StructuredData/StructuredData.cs index 2f58ce71..d9b7b21a 100644 --- a/OpenMetaverse.StructuredData/StructuredData.cs +++ b/OpenMetaverse.StructuredData/StructuredData.cs @@ -62,6 +62,13 @@ namespace OpenMetaverse.StructuredData Array } + public enum OSDFormat + { + Xml = 0, + Json, + Binary + } + /// /// /// diff --git a/OpenMetaverse.Utilities/RegistrationApi.cs b/OpenMetaverse.Utilities/RegistrationApi.cs index 0e61272e..06d86249 100644 --- a/OpenMetaverse.Utilities/RegistrationApi.cs +++ b/OpenMetaverse.Utilities/RegistrationApi.cs @@ -35,6 +35,8 @@ namespace OpenMetaverse { public class RegistrationApi { + const int REQUEST_TIMEOUT = 1000 * 100; + private struct UserInfo { public string FirstName; @@ -137,7 +139,7 @@ namespace OpenMetaverse CapsClient request = new CapsClient(RegistrationApiCaps); request.OnComplete += new CapsClient.CompleteCallback(GatherCapsResponse); - request.BeginGetResponse(postData); + request.BeginGetResponse(postData, "application/x-www-form-urlencoded", REQUEST_TIMEOUT); } private void GatherCapsResponse(CapsClient client, OSD response, Exception error) @@ -168,7 +170,7 @@ namespace OpenMetaverse CapsClient request = new CapsClient(_caps.GetErrorCodes); request.OnComplete += new CapsClient.CompleteCallback(GatherErrorMessagesResponse); - request.BeginGetResponse(); + request.BeginGetResponse(REQUEST_TIMEOUT); } private void GatherErrorMessagesResponse(CapsClient client, OSD response, Exception error) @@ -206,7 +208,7 @@ namespace OpenMetaverse CapsClient request = new CapsClient(_caps.GetLastNames); request.OnComplete += new CapsClient.CompleteCallback(GatherLastNamesResponse); - request.BeginGetResponse(); + request.BeginGetResponse(REQUEST_TIMEOUT); // FIXME: Block } @@ -250,7 +252,7 @@ namespace OpenMetaverse CapsClient request = new CapsClient(_caps.CheckName); request.OnComplete += new CapsClient.CompleteCallback(CheckNameResponse); - request.BeginGetResponse(); + request.BeginGetResponse(REQUEST_TIMEOUT); // FIXME: return false; @@ -316,7 +318,7 @@ namespace OpenMetaverse // Make the request CapsClient request = new CapsClient(_caps.CreateUser); request.OnComplete += new CapsClient.CompleteCallback(CreateUserResponse); - request.BeginGetResponse(); + request.BeginGetResponse(REQUEST_TIMEOUT); // FIXME: Block return UUID.Zero; diff --git a/OpenMetaverse.Utilities/VoiceManager.cs b/OpenMetaverse.Utilities/VoiceManager.cs index 4e3c1f1f..ac6f772c 100644 --- a/OpenMetaverse.Utilities/VoiceManager.cs +++ b/OpenMetaverse.Utilities/VoiceManager.cs @@ -335,7 +335,7 @@ namespace OpenMetaverse.Utilities CapsClient request = new CapsClient(url); OSDMap body = new OSDMap(); request.OnComplete += new CapsClient.CompleteCallback(callback); - request.BeginGetResponse(body); + request.BeginGetResponse(body, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); return true; } diff --git a/OpenMetaverse/AgentManager.cs b/OpenMetaverse/AgentManager.cs index 11b5aac3..748a616c 100644 --- a/OpenMetaverse/AgentManager.cs +++ b/OpenMetaverse/AgentManager.cs @@ -1490,10 +1490,8 @@ namespace OpenMetaverse ChatSessionAcceptInvitation acceptInvite = new ChatSessionAcceptInvitation(); acceptInvite.SessionID = session_id; - byte[] postData = OSDParser.SerializeLLSDXmlBytes(acceptInvite.Serialize()); - CapsClient request = new CapsClient(url); - request.BeginGetResponse(postData); + request.BeginGetResponse(acceptInvite.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { @@ -1527,7 +1525,7 @@ namespace OpenMetaverse byte[] postData = StructuredData.OSDParser.SerializeLLSDXmlBytes(startConference.Serialize()); CapsClient request = new CapsClient(url); - request.BeginGetResponse(postData); + request.BeginGetResponse(startConference.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { @@ -3353,12 +3351,8 @@ namespace OpenMetaverse req.SessionID = sessionID; req.AgentID = memberID; - OSDMap map = req.Serialize(); - - byte[] postData = OSDParser.SerializeLLSDXmlBytes(map); - CapsClient request = new CapsClient(url); - request.BeginGetResponse(postData); + request.BeginGetResponse(req.Serialize(), OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } else { diff --git a/OpenMetaverse/Caps.cs b/OpenMetaverse/Caps.cs index 19c1fadc..0127bf9e 100644 --- a/OpenMetaverse/Caps.cs +++ b/OpenMetaverse/Caps.cs @@ -51,7 +51,6 @@ namespace OpenMetaverse /// The simulator that generated the event //public delegate void EventQueueCallback(string message, StructuredData.OSD body, Simulator simulator); - public delegate void EventQueueCallback(string capsKey, IMessage message, Simulator simulator); /// Reference to the simulator this system is connected to @@ -79,7 +78,6 @@ namespace OpenMetaverse } } - /// /// Default constructor /// @@ -171,7 +169,7 @@ namespace OpenMetaverse _SeedRequest = new CapsClient(new Uri(_SeedCapsURI)); _SeedRequest.OnComplete += new CapsClient.CompleteCallback(SeedRequestCompleteHandler); - _SeedRequest.BeginGetResponse(req); + _SeedRequest.BeginGetResponse(req, OSDFormat.Xml, Simulator.Client.Settings.CAPS_TIMEOUT); } private void SeedRequestCompleteHandler(CapsClient client, OSD result, Exception error) diff --git a/OpenMetaverse/GridManager.cs b/OpenMetaverse/GridManager.cs index 12728cfb..9f2c5bed 100644 --- a/OpenMetaverse/GridManager.cs +++ b/OpenMetaverse/GridManager.cs @@ -286,7 +286,7 @@ namespace OpenMetaverse CapsClient request = new CapsClient(url); request.OnComplete += new CapsClient.CompleteCallback(MapLayerResponseHandler); - request.BeginGetResponse(body); + request.BeginGetResponse(body, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); } } diff --git a/OpenMetaverse/InventoryManager.cs b/OpenMetaverse/InventoryManager.cs index b1041f14..894edf4d 100644 --- a/OpenMetaverse/InventoryManager.cs +++ b/OpenMetaverse/InventoryManager.cs @@ -1834,7 +1834,7 @@ namespace OpenMetaverse } public void RequestCreateItemFromAsset(byte[] data, string name, string description, AssetType assetType, - InventoryType invType, UUID folderID, CapsClient.ProgressCallback progCallback, ItemCreatedFromAssetCallback callback) + InventoryType invType, UUID folderID, ItemCreatedFromAssetCallback callback) { if (_Client.Network.CurrentSim == null || _Client.Network.CurrentSim.Caps == null) throw new Exception("NewFileAgentInventory capability is not currently available"); @@ -1853,9 +1853,9 @@ namespace OpenMetaverse // Make the request CapsClient request = new CapsClient(url); request.OnComplete += new CapsClient.CompleteCallback(CreateItemFromAssetResponse); - request.UserData = new object[] { progCallback, callback, data }; + request.UserData = new object[] { callback, data, _Client.Settings.CAPS_TIMEOUT }; - request.BeginGetResponse(query); + request.BeginGetResponse(query, OSDFormat.Xml, _Client.Settings.CAPS_TIMEOUT); } else { @@ -2056,13 +2056,11 @@ namespace OpenMetaverse OSDMap query = new OSDMap(); query.Add("item_id", OSD.FromUUID(notecardID)); - byte[] postData = StructuredData.OSDParser.SerializeLLSDXmlBytes(query); - // Make the request CapsClient request = new CapsClient(url); request.OnComplete += new CapsClient.CompleteCallback(UploadNotecardAssetResponse); - request.UserData = new object[2] { new KeyValuePair(callback, data), notecardID }; - request.BeginGetResponse(postData); + request.UserData = new object[] { new KeyValuePair(callback, data), notecardID }; + request.BeginGetResponse(query, OSDFormat.Xml, _Client.Settings.CAPS_TIMEOUT); } else { @@ -2987,9 +2985,9 @@ namespace OpenMetaverse private void CreateItemFromAssetResponse(CapsClient client, OSD result, Exception error) { object[] args = (object[])client.UserData; - CapsClient.ProgressCallback progCallback = (CapsClient.ProgressCallback)args[0]; - ItemCreatedFromAssetCallback callback = (ItemCreatedFromAssetCallback)args[1]; - byte[] itemData = (byte[])args[2]; + ItemCreatedFromAssetCallback callback = (ItemCreatedFromAssetCallback)args[0]; + byte[] itemData = (byte[])args[1]; + int millisecondsTimeout = (int)args[2]; if (result == null) { @@ -3011,10 +3009,9 @@ namespace OpenMetaverse // This makes the assumption that all uploads go to CurrentSim, to avoid // the problem of HttpRequestState not knowing anything about simulators CapsClient upload = new CapsClient(new Uri(uploadURL)); - upload.OnProgress += progCallback; upload.OnComplete += new CapsClient.CompleteCallback(CreateItemFromAssetResponse); upload.UserData = new object[] { null, callback, itemData }; - upload.BeginGetResponse(itemData, "application/octet-stream"); + upload.BeginGetResponse(itemData, "application/octet-stream", millisecondsTimeout); } else if (status == "complete") { @@ -3609,7 +3606,7 @@ namespace OpenMetaverse CapsClient upload = new CapsClient(new Uri(uploadURL)); upload.OnComplete += new CapsClient.CompleteCallback(UploadNotecardAssetResponse); upload.UserData = new object[2] { kvp, (UUID)(((object[])client.UserData)[1]) }; - upload.BeginGetResponse(itemData, "application/octet-stream"); + upload.BeginGetResponse(itemData, "application/octet-stream", _Client.Settings.CAPS_TIMEOUT); } else if (status == "complete") { diff --git a/OpenMetaverse/Login.cs b/OpenMetaverse/Login.cs index 0db509c9..20809c74 100644 --- a/OpenMetaverse/Login.cs +++ b/OpenMetaverse/Login.cs @@ -1350,7 +1350,7 @@ namespace OpenMetaverse loginRequest.OnComplete += new CapsClient.CompleteCallback(LoginReplyLLSDHandler); loginRequest.UserData = CurrentContext; UpdateLoginStatus(LoginStatus.ConnectingToLogin, String.Format("Logging in as {0} {1}...", loginParams.FirstName, loginParams.LastName)); - loginRequest.BeginGetResponse(OSDParser.SerializeLLSDXmlBytes(loginLLSD), "application/xml+llsd"); + loginRequest.BeginGetResponse(loginLLSD, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); #endregion } diff --git a/OpenMetaverse/Messages/CableBeachHelpers.cs b/OpenMetaverse/Messages/CableBeachHelpers.cs index d22b66ca..50cfb3c8 100644 --- a/OpenMetaverse/Messages/CableBeachHelpers.cs +++ b/OpenMetaverse/Messages/CableBeachHelpers.cs @@ -9,100 +9,149 @@ namespace OpenMetaverse.Messages.CableBeach #region SL / file extension / content-type conversions - public static string SLAssetTypeToContentType(int assetType) + public static string SLAssetTypeToContentType(AssetType assetType) { switch (assetType) { - case 0: + case AssetType.Texture: return "image/x-j2c"; - case 1: + case AssetType.Sound: return "application/ogg"; - case 2: + case AssetType.CallingCard: return "application/vnd.ll.callingcard"; - case 3: + case AssetType.Landmark: return "application/vnd.ll.landmark"; - case 5: + case AssetType.Clothing: return "application/vnd.ll.clothing"; - case 6: + case AssetType.Object: return "application/vnd.ll.primitive"; - case 7: + case AssetType.Notecard: return "application/vnd.ll.notecard"; - case 10: + case AssetType.Folder: + return "application/vnd.ll.folder"; + case AssetType.RootFolder: + return "application/vnd.ll.rootfolder"; + case AssetType.LSLText: return "application/vnd.ll.lsltext"; - case 11: + case AssetType.LSLBytecode: return "application/vnd.ll.lslbyte"; - case 13: + case AssetType.TextureTGA: + case AssetType.ImageTGA: + return "image/tga"; + case AssetType.Bodypart: return "application/vnd.ll.bodypart"; - case 20: + case AssetType.TrashFolder: + return "application/vnd.ll.trashfolder"; + case AssetType.SnapshotFolder: + return "application/vnd.ll.snapshotfolder"; + case AssetType.LostAndFoundFolder: + return "application/vnd.ll.lostandfoundfolder"; + case AssetType.SoundWAV: + return "audio/x-wav"; + case AssetType.ImageJPEG: + return "image/jpeg"; + case AssetType.Animation: return "application/vnd.ll.animation"; - case 21: + case AssetType.Gesture: return "application/vnd.ll.gesture"; + case AssetType.Simstate: + case AssetType.Unknown: default: return "application/octet-stream"; } } - public static int ContentTypeToSLAssetType(string contentType) + public static AssetType ContentTypeToSLAssetType(string contentType) { switch (contentType) { case "image/x-j2c": - return 0; + return AssetType.Texture; case "application/ogg": - return 1; + return AssetType.Sound; case "application/vnd.ll.callingcard": - return 2; + return AssetType.CallingCard; case "application/vnd.ll.landmark": - return 3; + return AssetType.Landmark; case "application/vnd.ll.clothing": - return 5; + return AssetType.Clothing; case "application/vnd.ll.primitive": - return 6; + return AssetType.Object; case "application/vnd.ll.notecard": - return 7; + return AssetType.Notecard; + case "application/vnd.ll.folder": + return AssetType.Folder; + case "application/vnd.ll.rootfolder": + return AssetType.RootFolder; case "application/vnd.ll.lsltext": - return 10; + return AssetType.LSLText; case "application/vnd.ll.lslbyte": - return 11; + return AssetType.LSLBytecode; + case "image/tga": + // Note that AssetType.TextureTGA will be converted to AssetType.ImageTGA + return AssetType.ImageTGA; case "application/vnd.ll.bodypart": - return 13; + return AssetType.Bodypart; + case "application/vnd.ll.trashfolder": + return AssetType.TrashFolder; + case "application/vnd.ll.snapshotfolder": + return AssetType.SnapshotFolder; + case "application/vnd.ll.lostandfoundfolder": + return AssetType.LostAndFoundFolder; + case "audio/x-wav": + return AssetType.SoundWAV; + case "image/jpeg": + return AssetType.ImageJPEG; case "application/vnd.ll.animation": - return 20; + return AssetType.Animation; case "application/vnd.ll.gesture": - return 21; + return AssetType.Gesture; + case "application/octet-stream": default: - return -1; + return AssetType.Unknown; } } - public static int ContentTypeToSLInvType(string contentType) + public static InventoryType ContentTypeToSLInvType(string contentType) { switch (contentType) { case "image/x-j2c": - return (int)InventoryType.Texture; + case "image/tga": + case "image/jpeg": + return InventoryType.Texture; case "application/ogg": - return (int)InventoryType.Sound; + case "audio/x-wav": + return InventoryType.Sound; case "application/vnd.ll.callingcard": - return (int)InventoryType.CallingCard; + return InventoryType.CallingCard; case "application/vnd.ll.landmark": - return (int)InventoryType.Landmark; + return InventoryType.Landmark; case "application/vnd.ll.clothing": case "application/vnd.ll.bodypart": - return (int)InventoryType.Wearable; + return InventoryType.Wearable; case "application/vnd.ll.primitive": - return (int)InventoryType.Object; + return InventoryType.Object; case "application/vnd.ll.notecard": - return (int)InventoryType.Notecard; + return InventoryType.Notecard; + case "application/vnd.ll.folder": + return InventoryType.Folder; + case "application/vnd.ll.rootfolder": + return InventoryType.RootCategory; case "application/vnd.ll.lsltext": case "application/vnd.ll.lslbyte": - return (int)InventoryType.LSL; + return InventoryType.LSL; + case "application/vnd.ll.trashfolder": + case "application/vnd.ll.snapshotfolder": + case "application/vnd.ll.lostandfoundfolder": + return InventoryType.Folder; case "application/vnd.ll.animation": - return (int)InventoryType.Animation; + return InventoryType.Animation; case "application/vnd.ll.gesture": - return (int)InventoryType.Gesture; + return InventoryType.Gesture; + case "application/octet-stream": default: - return (int)InventoryType.Unknown; + return InventoryType.Unknown; } } @@ -124,16 +173,33 @@ namespace OpenMetaverse.Messages.CableBeach return "primitive"; case "application/vnd.ll.notecard": return "notecard"; + case "application/vnd.ll.folder": + return "folder"; + case "application/vnd.ll.rootfolder": + return "rootfolder"; case "application/vnd.ll.lsltext": return "lsltext"; case "application/vnd.ll.lslbyte": return "lslbyte"; + case "image/tga": + return "tga"; case "application/vnd.ll.bodypart": return "bodypart"; + case "application/vnd.ll.trashfolder": + return "trashfolder"; + case "application/vnd.ll.snapshotfolder": + return "snapshotfolder"; + case "application/vnd.ll.lostandfoundfolder": + return "lostandfoundfolder"; + case "audio/x-wav": + return "wav"; + case "image/jpeg": + return "jpg"; case "application/vnd.ll.animation": return "animatn"; case "application/vnd.ll.gesture": return "gesture"; + case "application/octet-stream": default: return "binary"; } @@ -144,10 +210,7 @@ namespace OpenMetaverse.Messages.CableBeach switch (extension) { case "texture": - case "jp2": - case "j2c": return "image/x-j2c"; - case "sound": case "ogg": return "application/ogg"; case "callingcard": @@ -160,16 +223,33 @@ namespace OpenMetaverse.Messages.CableBeach return "application/vnd.ll.primitive"; case "notecard": return "application/vnd.ll.notecard"; - case "lsl": + case "folder": + return "application/vnd.ll.folder"; + case "rootfolder": + return "application/vnd.ll.rootfolder"; + case "lsltext": return "application/vnd.ll.lsltext"; - case "lso": + case "lslbyte": return "application/vnd.ll.lslbyte"; + case "tga": + return "image/tga"; case "bodypart": return "application/vnd.ll.bodypart"; + case "trashfolder": + return "application/vnd.ll.trashfolder"; + case "snapshotfolder": + return "application/vnd.ll.snapshotfolder"; + case "lostandfoundfolder": + return "application/vnd.ll.lostandfoundfolder"; + case "wav": + return "audio/x-wav"; + case "jpg": + return "image/jpeg"; case "animatn": return "application/vnd.ll.animation"; case "gesture": return "application/vnd.ll.gesture"; + case "binary": default: return "application/octet-stream"; } diff --git a/OpenMetaverse/Messages/CableBeachMessages.cs b/OpenMetaverse/Messages/CableBeachMessages.cs index 1f3c6253..ee824b8a 100644 --- a/OpenMetaverse/Messages/CableBeachMessages.cs +++ b/OpenMetaverse/Messages/CableBeachMessages.cs @@ -691,12 +691,15 @@ namespace OpenMetaverse.Messages.CableBeach { public bool Success; public string Message; + public InventoryBlock Object; public OSDMap Serialize() { OSDMap map = new OSDMap(); map["success"] = OSD.FromBoolean(Success); map["message"] = OSD.FromString(Message); + if (Object != null) + map["object"] = Object.Serialize(); return map; } @@ -704,6 +707,20 @@ namespace OpenMetaverse.Messages.CableBeach { Success = map["success"].AsBoolean(); Message = map["message"].AsString(); + OSDMap objMap = map["object"] as OSDMap; + if (objMap != null) + { + if (objMap.ContainsKey("asset_id")) + Object = new InventoryBlockItem(); + else + Object = new InventoryBlockFolder(); + + Object.Deserialize(objMap); + } + else + { + Object = null; + } } } diff --git a/OpenMetaverse/ParcelManager.cs b/OpenMetaverse/ParcelManager.cs index 8e686fd3..da3e1233 100644 --- a/OpenMetaverse/ParcelManager.cs +++ b/OpenMetaverse/ParcelManager.cs @@ -674,15 +674,12 @@ namespace OpenMetaverse //body["user_look_at"] = ulat; OSDMap body = req.Serialize(); - byte[] postData = StructuredData.OSDParser.SerializeLLSDXmlBytes(body); CapsClient capsPost = new CapsClient(url); - capsPost.BeginGetResponse(postData); - + capsPost.BeginGetResponse(body, OSDFormat.Xml, simulator.Client.Settings.CAPS_TIMEOUT); } else { - ParcelPropertiesUpdatePacket request = new ParcelPropertiesUpdatePacket(); request.AgentData.AgentID = simulator.Client.Self.AgentID; diff --git a/OpenMetaverse/Settings.cs b/OpenMetaverse/Settings.cs index 9caf5307..ebb64b60 100644 --- a/OpenMetaverse/Settings.cs +++ b/OpenMetaverse/Settings.cs @@ -85,10 +85,9 @@ namespace OpenMetaverse /// time out public int LOGOUT_TIMEOUT = 5 * 1000; - /// Number of milliseconds before a CAPS call will time out - /// and try again - /// Setting this too low will cause web requests to repeatedly - /// time out and retry + /// Number of milliseconds before a CAPS call will time out + /// Setting this too low will cause web requests time out and + /// possibly retry repeatedly public int CAPS_TIMEOUT = 60 * 1000; /// Number of milliseconds for xml-rpc to timeout diff --git a/Programs/GridImageUpload/frmGridImageUpload.cs b/Programs/GridImageUpload/frmGridImageUpload.cs index da59ef8a..75bf3ee8 100644 --- a/Programs/GridImageUpload/frmGridImageUpload.cs +++ b/Programs/GridImageUpload/frmGridImageUpload.cs @@ -304,16 +304,6 @@ namespace GridImageUpload Client.Inventory.RequestCreateItemFromAsset(UploadData, name, "Uploaded with SL Image Upload", AssetType.Texture, InventoryType.Texture, Client.Inventory.FindFolderForType(AssetType.Texture), - - delegate(CapsClient client, long bytesReceived, long bytesSent, long totalBytesToReceive, long totalBytesToSend) - { - if (bytesSent > 0) - { - Transferred = (int)bytesSent; - BeginInvoke((MethodInvoker)delegate() { SetProgress(); }); - } - }, - delegate(bool success, string status, UUID itemID, UUID assetID) { if (this.InvokeRequired) diff --git a/Programs/GridProxy/GridProxy.cs b/Programs/GridProxy/GridProxy.cs index a38d81ba..ff569b29 100644 --- a/Programs/GridProxy/GridProxy.cs +++ b/Programs/GridProxy/GridProxy.cs @@ -1132,8 +1132,8 @@ namespace GridProxy remoteComplete.Set(); } ); - loginRequest.BeginGetResponse(content, "application/xml+llsd"); //xml+llsd - remoteComplete.WaitOne(30000, false); + loginRequest.BeginGetResponse(content, "application/llsd+xml", 1000 * 100); + remoteComplete.WaitOne(1000 * 100, false); if (response == null) { byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n"); diff --git a/Programs/examples/TestClient/Commands/Inventory/UploadImageCommand.cs b/Programs/examples/TestClient/Commands/Inventory/UploadImageCommand.cs index 015d6e28..5322bcae 100644 --- a/Programs/examples/TestClient/Commands/Inventory/UploadImageCommand.cs +++ b/Programs/examples/TestClient/Commands/Inventory/UploadImageCommand.cs @@ -64,13 +64,6 @@ namespace OpenMetaverse.TestClient Client.Inventory.RequestCreateItemFromAsset(UploadData, name, "Uploaded with TestClient", AssetType.Texture, InventoryType.Texture, Client.Inventory.FindFolderForType(AssetType.Texture), - - delegate(CapsClient client, long bytesReceived, long bytesSent, long totalBytesToReceive, long totalBytesToSend) - { - if (bytesSent > 0) - Console.WriteLine(String.Format("Texture upload: {0} / {1}", bytesSent, totalBytesToSend)); - }, - delegate(bool success, string status, UUID itemID, UUID assetID) { Console.WriteLine(String.Format( diff --git a/Programs/importprimscript/importprimscript.cs b/Programs/importprimscript/importprimscript.cs index 61d19fa5..94a4847c 100644 --- a/Programs/importprimscript/importprimscript.cs +++ b/Programs/importprimscript/importprimscript.cs @@ -216,12 +216,6 @@ namespace importprimscript AutoResetEvent uploadEvent = new AutoResetEvent(false); Client.Inventory.RequestCreateItemFromAsset(jp2data, Path.GetFileNameWithoutExtension(filename), "Uploaded with importprimscript", AssetType.Texture, InventoryType.Texture, UploadFolderID, - - delegate(CapsClient client, long bytesReceived, long bytesSent, long totalBytesToReceive, long totalBytesToSend) - { - // FIXME: Do something with progress? - }, - delegate(bool success, string status, UUID itemID, UUID assetID) { if (success)