Index: Src/GoogleApis.Tests/Apis/Download/MediaDownloaderTest.cs
===================================================================
--- a/Src/GoogleApis.Tests/Apis/Download/MediaDownloaderTest.cs
+++ b/Src/GoogleApis.Tests/Apis/Download/MediaDownloaderTest.cs
@@ -129,6 +129,14 @@
Subtest_Download_Chunks(100);
}
+
+ /// Tests that download works in case the URI download contains query parameters.
+ [Test]
+ public void Download_SingleChunk_UriContainsQueryParameters()
+ {
+ Subtest_Download_Chunks((int)StreamContent.Length, true, 0, "https://www.sample.com?a=1&b=2");
+ }
+
///
/// Tests that download asynchronously works in case the server returns multiple chunks to the client.
///
@@ -165,15 +173,17 @@
/// The chunk size for each part.
/// Indicates if this download should be synchronously or asynchronously.
/// Defines the request index to cancel the download request.
- private void Subtest_Download_Chunks(int chunkSize, bool sync = true, int cancelRequest = 0)
+ ///
+ private void Subtest_Download_Chunks(int chunkSize, bool sync = true, int cancelRequest = 0,
+ string downloadUri = "http://www.sample.com")
{
// reset the steam
StreamContent.Position = 0;
- string downloadUri = "http://www.sample.com";
var handler = new MultipleChunksMessageHandler();
handler.ChunkSize = chunkSize;
- handler.DownloadUri = new Uri(downloadUri + "?alt=media");
+ handler.DownloadUri = new Uri(downloadUri +
+ (downloadUri.Contains("?") ? "&" : "?") + "alt=media");
// support cancellation
if (cancelRequest > 0)
Index: Src/GoogleApis.Tests/Apis/Http/ConfigurableMessageHandlerTest.cs
===================================================================
--- a/Src/GoogleApis.Tests/Apis/Http/ConfigurableMessageHandlerTest.cs
+++ b/Src/GoogleApis.Tests/Apis/Http/ConfigurableMessageHandlerTest.cs
@@ -118,7 +118,7 @@
var redirectHandler = new RedirectMessageHandler(location);
var configurableHanlder = new ConfigurableMessageHandler(redirectHandler)
{
- NumTries = 8
+ NumRedirects = 5
};
using (var client = new HttpClient(configurableHanlder))
{
@@ -131,8 +131,9 @@
HttpResponseMessage response = client.SendAsync(request).Result;
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Redirect));
- Assert.That(response.Headers.Location, Is.EqualTo(new Uri(location + configurableHanlder.NumTries)));
- Assert.That(redirectHandler.Calls, Is.EqualTo(configurableHanlder.NumTries));
+ Assert.That(response.Headers.Location, Is.EqualTo(
+ new Uri(location + (configurableHanlder.NumRedirects + 1))));
+ Assert.That(redirectHandler.Calls, Is.EqualTo(configurableHanlder.NumRedirects + 1));
}
}
Index: Src/GoogleApis.Tests/Apis/Requests/ClientServiceRequestTest.cs
===================================================================
--- a/Src/GoogleApis.Tests/Apis/Requests/ClientServiceRequestTest.cs
+++ b/Src/GoogleApis.Tests/Apis/Requests/ClientServiceRequestTest.cs
@@ -401,16 +401,16 @@
{
// we expect a task canceled exception in case the canceled request is less or equal total
// number of retries
- Assert.False(cancelRequestNum > service.HttpClient.MessageHandler.NumTries);
+ Assert.False(cancelRequestNum > service.HttpClient.MessageHandler.NumRedirects + 1);
}
else
{
// exception should be thrown as a result of casting to MockResponse object
- Assert.True(cancelRequestNum > service.HttpClient.MessageHandler.NumTries);
+ Assert.True(cancelRequestNum > service.HttpClient.MessageHandler.NumRedirects + 1);
}
}
- var expectedCalls = Math.Min(service.HttpClient.MessageHandler.NumTries, cancelRequestNum);
+ var expectedCalls = Math.Min(service.HttpClient.MessageHandler.NumRedirects + 1, cancelRequestNum);
Assert.That(handler.Calls, Is.EqualTo(expectedCalls));
}
}
Index: Src/GoogleApis/Apis/Http/ConfigurableMessageHandler.cs
===================================================================
--- a/Src/GoogleApis/Apis/Http/ConfigurableMessageHandler.cs
+++ b/Src/GoogleApis/Apis/Http/ConfigurableMessageHandler.cs
@@ -92,6 +92,11 @@
/// or which handles the
/// abnormal Http response or exception, before being terminated.
/// Set 1 for not retrying requests. The default value is 3".
+ ///
+ /// The number of allowed redirects (3xx) is defined by . This property defines
+ /// only the allowed tries for >=400 responses, or in a case an exception was thrown. For example you set
+ /// to 1 and to 5, a retry will happen only for 5 redirects.
+ ///
///
public int NumTries
{
@@ -106,6 +111,26 @@
}
}
+ /// Number of redirects allowed. Default is 10.
+ private int numRedirect = 10;
+
+ ///
+ /// Gets or sets the number of redirects that will be allowed to execute. The default value is 10.
+ /// See for more information.
+ ///
+ public int NumRedirects
+ {
+ get { return numRedirect; }
+ set
+ {
+ if (value > MaxAllowedNumTries || value < 1)
+ {
+ throw new ArgumentOutOfRangeException("NumRedirects");
+ }
+ numRedirect = value;
+ }
+ }
+
///
/// Gets or sets whether the handler should follow a redirect when a redirect response is received. Default
/// value is true.
@@ -138,6 +163,8 @@
var loggable = IsLoggingEnabled && Logger.IsDebugEnabled;
int triesRemaining = NumTries;
+ int redirectRemaining = NumRedirects;
+
Exception lastException = null;
// set User-Agent header
@@ -177,7 +204,10 @@
}
// decrease the number of retries
- triesRemaining--;
+ if (response == null || (int)response.StatusCode >= 400)
+ {
+ triesRemaining--;
+ }
// exception was thrown , try to handle it
if (response == null)
@@ -237,6 +267,11 @@
{
if (HandleRedirect(response))
{
+ if (redirectRemaining-- == 0)
+ {
+ triesRemaining = 0;
+ }
+
errorHandled = true;
if (loggable)
{
Index: Src/GoogleApis/Apis/[Media]/Download/MediaDownloader.cs
===================================================================
--- a/Src/GoogleApis/Apis/[Media]/Download/MediaDownloader.cs
+++ b/Src/GoogleApis/Apis/[Media]/Download/MediaDownloader.cs
@@ -15,6 +15,7 @@
*/
using System;
+using System.Linq;
using System.IO;
using System.Net.Http.Headers;
using System.Threading;
@@ -189,7 +190,26 @@
throw new ArgumentException("stream doesn't support write operations");
}
- var builder = new RequestBuilder() { BaseUri = new Uri(url) };
+ RequestBuilder builder = null;
+
+ var uri = new Uri(url);
+ if (string.IsNullOrEmpty(uri.Query))
+ {
+ builder = new RequestBuilder() { BaseUri = new Uri(url) };
+ }
+ else
+ {
+ builder = new RequestBuilder() { BaseUri = new Uri(url.Substring(0, url.IndexOf("?"))) };
+ // remove '?' at the beginning
+ var query = uri.Query.Substring(1);
+ var pairs = from parameter in query.Split('&')
+ select parameter.Split('=');
+ // add each query parameter. each pair contains the key [0] and then its value [1]
+ foreach (var p in pairs)
+ {
+ builder.AddParameter(RequestParameterType.Query, p[0], p[1]);
+ }
+ }
builder.AddParameter(RequestParameterType.Query, "alt", "media");
long currentRequestFirstBytePos = 0;
@@ -219,8 +239,20 @@
// read the headers and check if all the media content was already downloaded
var contentRange = response.Content.Headers.ContentRange;
- currentRequestFirstBytePos = contentRange.To.Value + 1;
- long mediaContentLength = contentRange.Length.Value;
+ long mediaContentLength;
+
+ if (contentRange == null)
+ {
+ // content range is null when the server doesn't adhere the media download protocol, in
+ // that case we got all the media in one chunk
+ currentRequestFirstBytePos = mediaContentLength =
+ response.Content.Headers.ContentLength.Value;
+ }
+ else
+ {
+ currentRequestFirstBytePos = contentRange.To.Value + 1;
+ mediaContentLength = contentRange.Length.Value;
+ }
if (currentRequestFirstBytePos == mediaContentLength)
{