Previously, CapsBase.SetupRequest() hardcoded this to 32 and DownloadManager.ParallelDownloads was setting this with a default value of 8. This meant that the ConnectionLimit would oscillate rapidly between these two figures as requests were made. On Mono 3.2.8 and quite possibly other Mono, this appears to increase the chance that the VM will crash under heavy request load. This commit makes both SetupRequest() calls use the static Settings.HTTP_MAX_CONNECTIONS - other static settings already exist, and so also makes this configurable. DownloadManager.ParallelDownloads remains a separate setting since it also governs max parallel downloads through a separate coded mechanism.
281 lines
13 KiB
C#
281 lines
13 KiB
C#
/*
|
|
* Copyright (c) 2006-2014, 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.Net.Security;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
namespace OpenMetaverse.Http
|
|
{
|
|
public class TrustAllCertificatePolicy : ICertificatePolicy
|
|
{
|
|
public bool CheckValidationResult(ServicePoint sp, X509Certificate cert, WebRequest req, int problem)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public static bool TrustAllCertificateHandler(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public static class CapsBase
|
|
{
|
|
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);
|
|
|
|
static CapsBase()
|
|
{
|
|
ServicePointManager.CertificatePolicy = new TrustAllCertificatePolicy();
|
|
// Even though this will compile on Mono 2.4, it throws a runtime exception
|
|
//ServicePointManager.ServerCertificateValidationCallback = TrustAllCertificatePolicy.TrustAllCertificateHandler;
|
|
}
|
|
|
|
private class RequestState
|
|
{
|
|
public HttpWebRequest Request;
|
|
public byte[] UploadData;
|
|
public int MillisecondsTimeout;
|
|
public OpenWriteEventHandler OpenWriteCallback;
|
|
public DownloadProgressEventHandler DownloadProgressCallback;
|
|
public RequestCompletedEventHandler CompletedCallback;
|
|
|
|
public RequestState(HttpWebRequest request, byte[] uploadData, int millisecondsTimeout, OpenWriteEventHandler openWriteCallback,
|
|
DownloadProgressEventHandler downloadProgressCallback, RequestCompletedEventHandler completedCallback)
|
|
{
|
|
Request = request;
|
|
UploadData = uploadData;
|
|
MillisecondsTimeout = millisecondsTimeout;
|
|
OpenWriteCallback = openWriteCallback;
|
|
DownloadProgressCallback = downloadProgressCallback;
|
|
CompletedCallback = completedCallback;
|
|
}
|
|
}
|
|
|
|
public static HttpWebRequest UploadDataAsync(Uri address, X509Certificate2 clientCert, string contentType, byte[] data,
|
|
int millisecondsTimeout, OpenWriteEventHandler openWriteCallback, DownloadProgressEventHandler downloadProgressCallback,
|
|
RequestCompletedEventHandler completedCallback)
|
|
{
|
|
// Create the request
|
|
HttpWebRequest request = SetupRequest(address, clientCert);
|
|
request.ContentLength = data.Length;
|
|
if (!String.IsNullOrEmpty(contentType))
|
|
request.ContentType = contentType;
|
|
request.Method = "POST";
|
|
|
|
// Create an object to hold all of the state for this request
|
|
RequestState state = new RequestState(request, data, millisecondsTimeout, openWriteCallback,
|
|
downloadProgressCallback, completedCallback);
|
|
|
|
// 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);
|
|
|
|
return request;
|
|
}
|
|
|
|
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";
|
|
DownloadDataAsync(request, millisecondsTimeout, downloadProgressCallback, completedCallback);
|
|
return request;
|
|
}
|
|
|
|
public static void DownloadDataAsync(HttpWebRequest request, int millisecondsTimeout,
|
|
DownloadProgressEventHandler downloadProgressCallback, RequestCompletedEventHandler completedCallback)
|
|
{
|
|
// Create an object to hold all of the state for this request
|
|
RequestState state = new RequestState(request, null, millisecondsTimeout, null, downloadProgressCallback,
|
|
completedCallback);
|
|
|
|
// 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);
|
|
}
|
|
|
|
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 per endpoint
|
|
// We set this manually here instead of in ServicePointManager to avoid intereference with callers.
|
|
if (request.ServicePoint.ConnectionLimit < Settings.MAX_HTTP_CONNECTIONS)
|
|
{
|
|
Logger.Log(
|
|
string.Format(
|
|
"In CapsBase.SetupRequest() setting conn limit for {0}:{1} to {2}",
|
|
address.Host, address.Port, Settings.MAX_HTTP_CONNECTIONS), Helpers.LogLevel.Debug);
|
|
request.ServicePoint.ConnectionLimit = Settings.MAX_HTTP_CONNECTIONS;
|
|
}
|
|
// Caps requests are never sent as trickles of data, so Nagle's
|
|
// coalescing algorithm won't help us
|
|
request.ServicePoint.UseNagleAlgorithm = false;
|
|
// If not on mono, set accept-encoding header that allows response compression
|
|
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
|
return request;
|
|
}
|
|
|
|
static void OpenWrite(IAsyncResult ar)
|
|
{
|
|
RequestState state = (RequestState)ar.AsyncState;
|
|
|
|
try
|
|
{
|
|
// Get the stream to write our upload to
|
|
using (Stream uploadStream = state.Request.EndGetRequestStream(ar))
|
|
{
|
|
// Fire the callback for successfully opening the stream
|
|
if (state.OpenWriteCallback != null)
|
|
state.OpenWriteCallback(state.Request);
|
|
|
|
// Write our data to the upload stream
|
|
uploadStream.Write(state.UploadData, 0, state.UploadData.Length);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
//Logger.Log.Debug("CapsBase.OpenWrite(): " + ex.Message);
|
|
if (state.CompletedCallback != null)
|
|
state.CompletedCallback(state.Request, null, null, ex);
|
|
}
|
|
}
|
|
|
|
static void GetResponse(IAsyncResult ar)
|
|
{
|
|
RequestState state = (RequestState)ar.AsyncState;
|
|
HttpWebResponse response = null;
|
|
byte[] responseData = null;
|
|
Exception error = null;
|
|
|
|
try
|
|
{
|
|
using (response = (HttpWebResponse)state.Request.EndGetResponse(ar))
|
|
{
|
|
// Get the stream for downloading the response
|
|
using (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) || (Type.GetType("Mono.Runtime") != null);
|
|
int size = (nolength) ? 8192 : (int)response.ContentLength;
|
|
MemoryStream ms = (nolength) ? new MemoryStream() : null;
|
|
byte[] buffer = new byte[size];
|
|
|
|
int bytesRead = 0;
|
|
int offset = 0;
|
|
int totalBytesRead = 0;
|
|
int totalSize = nolength ? 0 : size;
|
|
|
|
while ((bytesRead = responseStream.Read(buffer, offset, size)) != 0)
|
|
{
|
|
totalBytesRead += bytesRead;
|
|
|
|
if (nolength)
|
|
{
|
|
totalSize += (size - bytesRead);
|
|
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, totalBytesRead, totalSize);
|
|
}
|
|
|
|
if (nolength)
|
|
{
|
|
responseData = ms.ToArray();
|
|
ms.Close();
|
|
ms.Dispose();
|
|
}
|
|
else
|
|
{
|
|
responseData = buffer;
|
|
}
|
|
|
|
#endregion Read the response
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Logger.DebugLog("CapsBase.GetResponse(): " + ex.Message);
|
|
error = ex;
|
|
}
|
|
|
|
if (state.CompletedCallback != null)
|
|
state.CompletedCallback(state.Request, response, responseData, error);
|
|
}
|
|
|
|
static void TimeoutCallback(object state, bool timedOut)
|
|
{
|
|
if (timedOut)
|
|
{
|
|
RequestState requestState = state as RequestState;
|
|
//Logger.Log.Debug("CapsBase.TimeoutCallback(): Request to " + requestState.Request.RequestUri +
|
|
// " timed out after " + requestState.MillisecondsTimeout + " milliseconds");
|
|
if (requestState != null && requestState.Request != null)
|
|
requestState.Request.Abort();
|
|
}
|
|
}
|
|
}
|
|
}
|