Google Api
This commit is contained in:
parent
e307811d89
commit
83f9fc1bd8
123 changed files with 12961 additions and 60 deletions
186
Yavsc/GoogleApiSupport/Google.Apis.Core/Http/BackOffHandler.cs
Normal file
186
Yavsc/GoogleApiSupport/Google.Apis.Core/Http/BackOffHandler.cs
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
Copyright 2013 Google Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Logging;
|
||||
using Google.Apis.Util;
|
||||
|
||||
namespace Google.Apis.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// A thread-safe back-off handler which handles an abnormal HTTP response or an exception with
|
||||
/// <see cref="Google.Apis.Util.IBackOff"/>.
|
||||
/// </summary>
|
||||
public class BackOffHandler : IHttpUnsuccessfulResponseHandler, IHttpExceptionHandler
|
||||
{
|
||||
private static readonly ILogger Logger = ApplicationContext.Logger.ForType<BackOffHandler>();
|
||||
|
||||
/// <summary>An initializer class to initialize a back-off handler.</summary>
|
||||
public class Initializer
|
||||
{
|
||||
/// <summary>Gets the back-off policy used by this back-off handler.</summary>
|
||||
public IBackOff BackOff { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum time span to wait. If the back-off instance returns a greater time span than
|
||||
/// this value, this handler returns <c>false</c> to both <c>HandleExceptionAsync</c> and
|
||||
/// <c>HandleResponseAsync</c>. Default value is 16 seconds per a retry request.
|
||||
/// </summary>
|
||||
public TimeSpan MaxTimeSpan { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate function which indicates whether this back-off handler should handle an
|
||||
/// abnormal HTTP response. The default is <see cref="DefaultHandleUnsuccessfulResponseFunc"/>.
|
||||
/// </summary>
|
||||
public Func<HttpResponseMessage, bool> HandleUnsuccessfulResponseFunc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate function which indicates whether this back-off handler should handle an
|
||||
/// exception. The default is <see cref="DefaultHandleExceptionFunc"/>.
|
||||
/// </summary>
|
||||
public Func<Exception, bool> HandleExceptionFunc { get; set; }
|
||||
|
||||
/// <summary>Default function which handles server errors (503).</summary>
|
||||
public static readonly Func<HttpResponseMessage, bool> DefaultHandleUnsuccessfulResponseFunc =
|
||||
(r) => (int)r.StatusCode == 503;
|
||||
|
||||
/// <summary>
|
||||
/// Default function which handles exception which aren't
|
||||
/// <see cref="System.Threading.Tasks.TaskCanceledException"/> or
|
||||
/// <see cref="System.OperationCanceledException"/>. Those exceptions represent a task or an operation
|
||||
/// which was canceled and shouldn't be retried.
|
||||
/// </summary>
|
||||
public static readonly Func<Exception, bool> DefaultHandleExceptionFunc =
|
||||
(ex) => !(ex is TaskCanceledException || ex is OperationCanceledException);
|
||||
|
||||
/// <summary>Constructs a new initializer by the given back-off.</summary>
|
||||
public Initializer(IBackOff backOff)
|
||||
{
|
||||
BackOff = backOff;
|
||||
HandleExceptionFunc = DefaultHandleExceptionFunc;
|
||||
HandleUnsuccessfulResponseFunc = DefaultHandleUnsuccessfulResponseFunc;
|
||||
MaxTimeSpan = TimeSpan.FromSeconds(16);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the back-off policy used by this back-off handler.</summary>
|
||||
public IBackOff BackOff { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum time span to wait. If the back-off instance returns a greater time span, the handle method
|
||||
/// returns <c>false</c>. Default value is 16 seconds per a retry request.
|
||||
/// </summary>
|
||||
public TimeSpan MaxTimeSpan { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a delegate function which indicates whether this back-off handler should handle an abnormal HTTP
|
||||
/// response. The default is <see cref="Initializer.DefaultHandleUnsuccessfulResponseFunc"/>.
|
||||
/// </summary>
|
||||
public Func<HttpResponseMessage, bool> HandleUnsuccessfulResponseFunc { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a delegate function which indicates whether this back-off handler should handle an exception. The
|
||||
/// default is <see cref="Initializer.DefaultHandleExceptionFunc"/>.
|
||||
/// </summary>
|
||||
public Func<Exception, bool> HandleExceptionFunc { get; private set; }
|
||||
|
||||
/// <summary>Constructs a new back-off handler with the given back-off.</summary>
|
||||
/// <param name="backOff">The back-off policy.</param>
|
||||
public BackOffHandler(IBackOff backOff)
|
||||
: this(new Initializer(backOff))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>Constructs a new back-off handler with the given initializer.</summary>
|
||||
public BackOffHandler(Initializer initializer)
|
||||
{
|
||||
BackOff = initializer.BackOff;
|
||||
MaxTimeSpan = initializer.MaxTimeSpan;
|
||||
HandleExceptionFunc = initializer.HandleExceptionFunc;
|
||||
HandleUnsuccessfulResponseFunc = initializer.HandleUnsuccessfulResponseFunc;
|
||||
}
|
||||
|
||||
#region IHttpUnsuccessfulResponseHandler
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<bool> HandleResponseAsync(HandleUnsuccessfulResponseArgs args)
|
||||
{
|
||||
// if the func returns true try to handle this current failed try
|
||||
if (HandleUnsuccessfulResponseFunc != null && HandleUnsuccessfulResponseFunc(args.Response))
|
||||
{
|
||||
return await HandleAsync(args.SupportsRetry, args.CurrentFailedTry, args.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IHttpExceptionHandler
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual async Task<bool> HandleExceptionAsync(HandleExceptionArgs args)
|
||||
{
|
||||
// if the func returns true try to handle this current failed try
|
||||
if (HandleExceptionFunc != null && HandleExceptionFunc(args.Exception))
|
||||
{
|
||||
return await HandleAsync(args.SupportsRetry, args.CurrentFailedTry, args.CancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Handles back-off. In case the request doesn't support retry or the back-off time span is greater than the
|
||||
/// maximum time span allowed for a request, the handler returns <c>false</c>. Otherwise the current thread
|
||||
/// will block for x milliseconds (x is defined by the <see cref="BackOff"/> instance), and this handler
|
||||
/// returns <c>true</c>.
|
||||
/// </summary>
|
||||
private async Task<bool> HandleAsync(bool supportsRetry, int currentFailedTry,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!supportsRetry || BackOff.MaxNumOfRetries < currentFailedTry)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TimeSpan ts = BackOff.GetNextBackOff(currentFailedTry);
|
||||
if (ts > MaxTimeSpan || ts < TimeSpan.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
await Wait(ts, cancellationToken).ConfigureAwait(false);
|
||||
Logger.Debug("Back-Off handled the error. Waited {0}ms before next retry...", ts.TotalMilliseconds);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Waits the given time span. Overriding this method is recommended for mocking purposes.</summary>
|
||||
/// <param name="ts">TimeSpan to wait (and block the current thread).</param>
|
||||
/// <param name="cancellationToken">The cancellation token in case the user wants to cancel the operation in
|
||||
/// the middle.</param>
|
||||
protected virtual async Task Wait(TimeSpan ts, CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Delay(ts, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
Copyright 2013 Google Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Google.Apis.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Configurable HTTP client inherits from <see cref="System.Net.Http.HttpClient"/> and contains a reference to
|
||||
/// <see cref="Google.Apis.Http.ConfigurableMessageHandler"/>.
|
||||
/// </summary>
|
||||
public class ConfigurableHttpClient : HttpClient
|
||||
{
|
||||
/// <summary>Gets the configurable message handler.</summary>
|
||||
public ConfigurableMessageHandler MessageHandler { get; private set; }
|
||||
|
||||
/// <summary>Constructs a new HTTP client.</summary>
|
||||
public ConfigurableHttpClient(ConfigurableMessageHandler handler)
|
||||
: base(handler)
|
||||
{
|
||||
MessageHandler = handler;
|
||||
DefaultRequestHeaders.ExpectContinue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,593 @@
|
|||
/*
|
||||
Copyright 2013 Google Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Logging;
|
||||
using Google.Apis.Testing;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace Google.Apis.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// A message handler which contains the main logic of our HTTP requests. It contains a list of
|
||||
/// <see cref="IHttpUnsuccessfulResponseHandler"/>s for handling abnormal responses, a list of
|
||||
/// <see cref="IHttpExceptionHandler"/>s for handling exception in a request and a list of
|
||||
/// <see cref="IHttpExecuteInterceptor"/>s for intercepting a request before it has been sent to the server.
|
||||
/// It also contains important properties like number of tries, follow redirect, etc.
|
||||
/// </summary>
|
||||
public class ConfigurableMessageHandler : DelegatingHandler
|
||||
{
|
||||
/// <summary>The class logger.</summary>
|
||||
private static readonly ILogger Logger = ApplicationContext.Logger.ForType<ConfigurableMessageHandler>();
|
||||
|
||||
/// <summary>Maximum allowed number of tries.</summary>
|
||||
[VisibleForTestOnly]
|
||||
public const int MaxAllowedNumTries = 20;
|
||||
|
||||
/// <summary>The current API version of this client library.</summary>
|
||||
private static readonly string ApiVersion = Google.Apis.Util.Utilities.GetLibraryVersion();
|
||||
|
||||
/// <summary>The User-Agent suffix header which contains the <see cref="ApiVersion"/>.</summary>
|
||||
private static readonly string UserAgentSuffix = "google-api-dotnet-client/" + ApiVersion + " (gzip)";
|
||||
|
||||
#region IHttpUnsuccessfulResponseHandler, IHttpExceptionHandler and IHttpExecuteInterceptor lists
|
||||
|
||||
#region Lock objects
|
||||
|
||||
// The following lock objects are used to lock the list of handlers and interceptors in order to be able to
|
||||
// iterate over them from several threads and to keep this class thread-safe.
|
||||
private readonly object unsuccessfulResponseHandlersLock = new object();
|
||||
private readonly object exceptionHandlersLock = new object();
|
||||
private readonly object executeInterceptorsLock = new object();
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>A list of <see cref="IHttpUnsuccessfulResponseHandler"/>.</summary>
|
||||
private readonly IList<IHttpUnsuccessfulResponseHandler> unsuccessfulResponseHandlers =
|
||||
new List<IHttpUnsuccessfulResponseHandler>();
|
||||
|
||||
/// <summary>A list of <see cref="IHttpExceptionHandler"/>.</summary>
|
||||
private readonly IList<IHttpExceptionHandler> exceptionHandlers =
|
||||
new List<IHttpExceptionHandler>();
|
||||
|
||||
/// <summary>A list of <see cref="IHttpExecuteInterceptor"/>.</summary>
|
||||
private readonly IList<IHttpExecuteInterceptor> executeInterceptors =
|
||||
new List<IHttpExecuteInterceptor>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="IHttpUnsuccessfulResponseHandler"/>s.
|
||||
/// <remarks>
|
||||
/// Since version 1.10, <see cref="AddUnsuccessfulResponseHandler"/> and
|
||||
/// <see cref="RemoveUnsuccessfulResponseHandler"/> were added in order to keep this class thread-safe.
|
||||
/// More information is available on
|
||||
/// <a href="https://github.com/google/google-api-dotnet-client/issues/592">#592</a>.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
[Obsolete("Use AddUnsuccessfulResponseHandler or RemoveUnsuccessfulResponseHandler instead.")]
|
||||
public IList<IHttpUnsuccessfulResponseHandler> UnsuccessfulResponseHandlers
|
||||
{
|
||||
get { return unsuccessfulResponseHandlers; }
|
||||
}
|
||||
|
||||
/// <summary>Adds the specified handler to the list of unsuccessful response handlers.</summary>
|
||||
public void AddUnsuccessfulResponseHandler(IHttpUnsuccessfulResponseHandler handler)
|
||||
{
|
||||
lock (unsuccessfulResponseHandlersLock)
|
||||
{
|
||||
unsuccessfulResponseHandlers.Add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified handler from the list of unsuccessful response handlers.</summary>
|
||||
public void RemoveUnsuccessfulResponseHandler(IHttpUnsuccessfulResponseHandler handler)
|
||||
{
|
||||
lock (unsuccessfulResponseHandlersLock)
|
||||
{
|
||||
unsuccessfulResponseHandlers.Remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="IHttpExceptionHandler"/>s.
|
||||
/// <remarks>
|
||||
/// Since version 1.10, <see cref="AddExceptionHandler"/> and <see cref="RemoveExceptionHandler"/> were added
|
||||
/// in order to keep this class thread-safe. More information is available on
|
||||
/// <a href="https://github.com/google/google-api-dotnet-client/issues/592">#592</a>.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
[Obsolete("Use AddExceptionHandler or RemoveExceptionHandler instead.")]
|
||||
public IList<IHttpExceptionHandler> ExceptionHandlers
|
||||
{
|
||||
get { return exceptionHandlers; }
|
||||
}
|
||||
|
||||
/// <summary>Adds the specified handler to the list of exception handlers.</summary>
|
||||
public void AddExceptionHandler(IHttpExceptionHandler handler)
|
||||
{
|
||||
lock (exceptionHandlersLock)
|
||||
{
|
||||
exceptionHandlers.Add(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified handler from the list of exception handlers.</summary>
|
||||
public void RemoveExceptionHandler(IHttpExceptionHandler handler)
|
||||
{
|
||||
lock (exceptionHandlersLock)
|
||||
{
|
||||
exceptionHandlers.Remove(handler);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of <see cref="IHttpExecuteInterceptor"/>s.
|
||||
/// <remarks>
|
||||
/// Since version 1.10, <see cref="AddExecuteInterceptor"/> and <see cref="RemoveExecuteInterceptor"/> were
|
||||
/// added in order to keep this class thread-safe. More information is available on
|
||||
/// <a href="https://github.com/google/google-api-dotnet-client/issues/592">#592</a>.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
[Obsolete("Use AddExecuteInterceptor or RemoveExecuteInterceptor instead.")]
|
||||
public IList<IHttpExecuteInterceptor> ExecuteInterceptors
|
||||
{
|
||||
get { return executeInterceptors; }
|
||||
}
|
||||
|
||||
/// <summary>Adds the specified interceptor to the list of execute interceptors.</summary>
|
||||
public void AddExecuteInterceptor(IHttpExecuteInterceptor interceptor)
|
||||
{
|
||||
lock (executeInterceptorsLock)
|
||||
{
|
||||
executeInterceptors.Add(interceptor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Removes the specified interceptor from the list of execute interceptors.</summary>
|
||||
public void RemoveExecuteInterceptor(IHttpExecuteInterceptor interceptor)
|
||||
{
|
||||
lock (executeInterceptorsLock)
|
||||
{
|
||||
executeInterceptors.Remove(interceptor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private int _loggingRequestId = 0;
|
||||
|
||||
private ILogger _instanceLogger = Logger;
|
||||
|
||||
/// <summary>
|
||||
/// For testing only.
|
||||
/// This defaults to the static <see cref="Logger"/>, but can be overridden for fine-grain testing.
|
||||
/// </summary>
|
||||
internal ILogger InstanceLogger
|
||||
{
|
||||
get { return _instanceLogger; }
|
||||
set { _instanceLogger = value.ForType<ConfigurableMessageHandler>(); }
|
||||
}
|
||||
|
||||
/// <summary>Number of tries. Default is <c>3</c>.</summary>
|
||||
private int numTries = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of tries that will be allowed to execute. Retries occur as a result of either
|
||||
/// <see cref="IHttpUnsuccessfulResponseHandler"/> or <see cref="IHttpExceptionHandler"/> which handles the
|
||||
/// abnormal HTTP response or exception before being terminated.
|
||||
/// Set <c>1</c> for not retrying requests. The default value is <c>3</c>.
|
||||
/// <remarks>
|
||||
/// The number of allowed redirects (3xx) is defined by <see cref="NumRedirects"/>. This property defines
|
||||
/// only the allowed tries for >=400 responses, or when an exception is thrown. For example if you set
|
||||
/// <see cref="NumTries"/> to 1 and <see cref="NumRedirects"/> to 5, the library will send up to five redirect
|
||||
/// requests, but will not send any retry requests due to an error HTTP status code.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public int NumTries
|
||||
{
|
||||
get { return numTries; }
|
||||
set
|
||||
{
|
||||
if (value > MaxAllowedNumTries || value < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("NumTries");
|
||||
}
|
||||
numTries = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Number of redirects allowed. Default is <c>10</c>.</summary>
|
||||
private int numRedirects = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of redirects that will be allowed to execute. The default value is <c>10</c>.
|
||||
/// See <see cref="NumTries"/> for more information.
|
||||
/// </summary>
|
||||
public int NumRedirects
|
||||
{
|
||||
get { return numRedirects; }
|
||||
set
|
||||
{
|
||||
if (value > MaxAllowedNumTries || value < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("NumRedirects");
|
||||
}
|
||||
numRedirects = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the handler should follow a redirect when a redirect response is received. Default
|
||||
/// value is <c>true</c>.
|
||||
/// </summary>
|
||||
public bool FollowRedirect { get; set; }
|
||||
|
||||
/// <summary>Gets or sets whether logging is enabled. Default value is <c>true</c>.</summary>
|
||||
public bool IsLoggingEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the type(s) of request/response events to log.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum LogEventType
|
||||
{
|
||||
/// <summary>
|
||||
/// Log no request/response information.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Log the request URI.
|
||||
/// </summary>
|
||||
RequestUri = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Log the request headers.
|
||||
/// </summary>
|
||||
RequestHeaders = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Log the request body. The body is assumed to be ASCII, and non-printable charaters are replaced by '.'.
|
||||
/// Warning: This causes the body content to be buffered in memory, so use with care for large requests.
|
||||
/// </summary>
|
||||
RequestBody = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Log the response status.
|
||||
/// </summary>
|
||||
ResponseStatus = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Log the response headers.
|
||||
/// </summary>
|
||||
ResponseHeaders = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Log the response body. The body is assumed to be ASCII, and non-printable characters are replaced by '.'.
|
||||
/// Warning: This causes the body content to be buffered in memory, so use with care for large responses.
|
||||
/// </summary>
|
||||
ResponseBody = 32,
|
||||
|
||||
/// <summary>
|
||||
/// Log abnormal response messages.
|
||||
/// </summary>
|
||||
ResponseAbnormal = 64,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The request/response types to log.
|
||||
/// </summary>
|
||||
public LogEventType LogEvents { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the application name which will be used on the User-Agent header.</summary>
|
||||
public string ApplicationName { get; set; }
|
||||
|
||||
/// <summary>Constructs a new configurable message handler.</summary>
|
||||
public ConfigurableMessageHandler(HttpMessageHandler httpMessageHandler)
|
||||
: base(httpMessageHandler)
|
||||
{
|
||||
// set default values
|
||||
FollowRedirect = true;
|
||||
IsLoggingEnabled = true;
|
||||
LogEvents = LogEventType.RequestUri | LogEventType.ResponseStatus | LogEventType.ResponseAbnormal;
|
||||
}
|
||||
|
||||
private void LogHeaders(string initialText, HttpHeaders headers1, HttpHeaders headers2)
|
||||
{
|
||||
var headers = (headers1 ?? Enumerable.Empty<KeyValuePair<string, IEnumerable<string>>>())
|
||||
.Concat(headers2 ?? Enumerable.Empty<KeyValuePair<string, IEnumerable<string>>>()).ToList();
|
||||
var args = new object[headers.Count * 2];
|
||||
var fmt = new StringBuilder(headers.Count * 32);
|
||||
fmt.Append(initialText);
|
||||
var argBuilder = new StringBuilder();
|
||||
for (int i = 0; i < headers.Count; i++)
|
||||
{
|
||||
fmt.Append($"\n [{{{i * 2}}}] '{{{1 + i * 2}}}'");
|
||||
args[i * 2] = headers[i].Key;
|
||||
argBuilder.Clear();
|
||||
args[1 + i * 2] = string.Join("; ", headers[i].Value);
|
||||
}
|
||||
InstanceLogger.Debug(fmt.ToString(), args);
|
||||
}
|
||||
|
||||
private async Task LogBody(string fmtText, HttpContent content)
|
||||
{
|
||||
// This buffers the body content within the HttpContent if required.
|
||||
var bodyBytes = content != null ? await content.ReadAsByteArrayAsync() : new byte[0];
|
||||
char[] bodyChars = new char[bodyBytes.Length];
|
||||
for (int i = 0; i < bodyBytes.Length; i++)
|
||||
{
|
||||
var b = bodyBytes[i];
|
||||
bodyChars[i] = b >= 32 && b <= 126 ? (char)b : '.';
|
||||
}
|
||||
InstanceLogger.Debug(fmtText, new string(bodyChars));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The main logic of sending a request to the server. This send method adds the User-Agent header to a request
|
||||
/// with <see cref="ApplicationName"/> and the library version. It also calls interceptors before each attempt,
|
||||
/// and unsuccessful response handler or exception handlers when abnormal response or exception occurred.
|
||||
/// </summary>
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var loggable = IsLoggingEnabled && InstanceLogger.IsDebugEnabled;
|
||||
string loggingRequestId = "";
|
||||
if (loggable)
|
||||
{
|
||||
loggingRequestId = Interlocked.Increment(ref _loggingRequestId).ToString("X8");
|
||||
}
|
||||
|
||||
int triesRemaining = NumTries;
|
||||
int redirectRemaining = NumRedirects;
|
||||
|
||||
Exception lastException = null;
|
||||
|
||||
// Set User-Agent header.
|
||||
var userAgent = (ApplicationName == null ? "" : ApplicationName + " ") + UserAgentSuffix;
|
||||
// TODO: setting the User-Agent won't work on Silverlight. We may need to create a special callback here to
|
||||
// set it correctly.
|
||||
request.Headers.Add("User-Agent", userAgent);
|
||||
|
||||
HttpResponseMessage response = null;
|
||||
do // While (triesRemaining > 0)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
response.Dispose();
|
||||
response = null;
|
||||
}
|
||||
lastException = null;
|
||||
|
||||
// We keep a local list of the interceptors, since we can't call await inside lock.
|
||||
IEnumerable<IHttpExecuteInterceptor> interceptors;
|
||||
lock (executeInterceptorsLock)
|
||||
{
|
||||
interceptors = executeInterceptors.ToList();
|
||||
}
|
||||
|
||||
// Intercept the request.
|
||||
foreach (var interceptor in interceptors)
|
||||
{
|
||||
await interceptor.InterceptAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
if (loggable)
|
||||
{
|
||||
if ((LogEvents & LogEventType.RequestUri) != 0)
|
||||
{
|
||||
InstanceLogger.Debug("Request[{0}] (triesRemaining={1}) URI: '{2}'", loggingRequestId, triesRemaining, request.RequestUri);
|
||||
}
|
||||
if ((LogEvents & LogEventType.RequestHeaders) != 0)
|
||||
{
|
||||
LogHeaders($"Request[{loggingRequestId}] Headers:", request.Headers, request.Content?.Headers);
|
||||
}
|
||||
if ((LogEvents & LogEventType.RequestBody) != 0)
|
||||
{
|
||||
await LogBody($"Request[{loggingRequestId}] Body: '{{0}}'", request.Content);
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
// Send the request!
|
||||
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
lastException = ex;
|
||||
}
|
||||
|
||||
// Decrease the number of retries.
|
||||
if (response == null || ((int)response.StatusCode >= 400 || (int)response.StatusCode < 200))
|
||||
{
|
||||
triesRemaining--;
|
||||
}
|
||||
|
||||
// Exception was thrown, try to handle it.
|
||||
if (response == null)
|
||||
{
|
||||
var exceptionHandled = false;
|
||||
|
||||
// We keep a local list of the handlers, since we can't call await inside lock.
|
||||
IEnumerable<IHttpExceptionHandler> handlers;
|
||||
lock (exceptionHandlersLock)
|
||||
{
|
||||
handlers = exceptionHandlers.ToList();
|
||||
}
|
||||
|
||||
// Try to handle the exception with each handler.
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
exceptionHandled |= await handler.HandleExceptionAsync(new HandleExceptionArgs
|
||||
{
|
||||
Request = request,
|
||||
Exception = lastException,
|
||||
TotalTries = NumTries,
|
||||
CurrentFailedTry = NumTries - triesRemaining,
|
||||
CancellationToken = cancellationToken
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!exceptionHandled)
|
||||
{
|
||||
InstanceLogger.Error(lastException,
|
||||
"Response[{0}] Exception was thrown while executing a HTTP request and it wasn't handled", loggingRequestId);
|
||||
throw lastException;
|
||||
}
|
||||
else if (loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0)
|
||||
{
|
||||
InstanceLogger.Debug("Response[{0}] Exception {1} was thrown, but it was handled by an exception handler",
|
||||
loggingRequestId, lastException.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (loggable)
|
||||
{
|
||||
if ((LogEvents & LogEventType.ResponseStatus) != 0)
|
||||
{
|
||||
InstanceLogger.Debug("Response[{0}] Response status: {1} '{2}'", loggingRequestId, response.StatusCode, response.ReasonPhrase);
|
||||
}
|
||||
if ((LogEvents & LogEventType.ResponseHeaders) != 0)
|
||||
{
|
||||
LogHeaders($"Response[{loggingRequestId}] Headers:", response.Headers, response.Content?.Headers);
|
||||
}
|
||||
if ((LogEvents & LogEventType.ResponseBody) != 0)
|
||||
{
|
||||
await LogBody($"Response[{loggingRequestId}] Body: '{{0}}'", response.Content);
|
||||
}
|
||||
}
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
// No need to retry, the response was successful.
|
||||
triesRemaining = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool errorHandled = false;
|
||||
|
||||
// We keep a local list of the handlers, since we can't call await inside lock.
|
||||
IEnumerable<IHttpUnsuccessfulResponseHandler> handlers;
|
||||
lock (unsuccessfulResponseHandlersLock)
|
||||
{
|
||||
handlers = unsuccessfulResponseHandlers.ToList();
|
||||
}
|
||||
// Try to handle the abnormal HTTP response with each handler.
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
errorHandled |= await handler.HandleResponseAsync(new HandleUnsuccessfulResponseArgs
|
||||
{
|
||||
Request = request,
|
||||
Response = response,
|
||||
TotalTries = NumTries,
|
||||
CurrentFailedTry = NumTries - triesRemaining,
|
||||
CancellationToken = cancellationToken
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!errorHandled)
|
||||
{
|
||||
if (FollowRedirect && HandleRedirect(response))
|
||||
{
|
||||
if (redirectRemaining-- == 0)
|
||||
{
|
||||
triesRemaining = 0;
|
||||
}
|
||||
|
||||
errorHandled = true;
|
||||
if (loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0)
|
||||
{
|
||||
InstanceLogger.Debug("Response[{0}] Redirect response was handled successfully. Redirect to {1}",
|
||||
loggingRequestId, response.Headers.Location);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0)
|
||||
{
|
||||
InstanceLogger.Debug("Response[{0}] An abnormal response wasn't handled. Status code is {1}",
|
||||
loggingRequestId, response.StatusCode);
|
||||
}
|
||||
|
||||
// No need to retry, because no handler handled the abnormal response.
|
||||
triesRemaining = 0;
|
||||
}
|
||||
}
|
||||
else if (loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0)
|
||||
{
|
||||
InstanceLogger.Debug("Response[{0}] An abnormal response was handled by an unsuccessful response handler. " +
|
||||
"Status Code is {1}", loggingRequestId, response.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (triesRemaining > 0); // Not a successful status code but it was handled.
|
||||
|
||||
// If the response is null, we should throw the last exception.
|
||||
if (response == null)
|
||||
{
|
||||
InstanceLogger.Error(lastException, "Request[{0}] Exception was thrown while executing a HTTP request", loggingRequestId);
|
||||
throw lastException;
|
||||
}
|
||||
else if (!response.IsSuccessStatusCode && loggable && (LogEvents & LogEventType.ResponseAbnormal) != 0)
|
||||
{
|
||||
InstanceLogger.Debug("Response[{0}] Abnormal response is being returned. Status Code is {1}", loggingRequestId, response.StatusCode);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles redirect if the response's status code is redirect, redirects are turned on, and the header has
|
||||
/// a location.
|
||||
/// When the status code is <c>303</c> the method on the request is changed to a GET as per the RFC2616
|
||||
/// specification. On a redirect, it also removes the <c>Authorization</c> and all <c>If-*</c> request headers.
|
||||
/// </summary>
|
||||
/// <returns> Whether this method changed the request and handled redirect successfully. </returns>
|
||||
private bool HandleRedirect(HttpResponseMessage message)
|
||||
{
|
||||
// TODO(peleyal): think if it's better to move that code to RedirectUnsucessfulResponseHandler
|
||||
var uri = message.Headers.Location;
|
||||
if (!message.IsRedirectStatusCode() || uri == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var request = message.RequestMessage;
|
||||
request.RequestUri = new Uri(request.RequestUri, uri);
|
||||
// Status code for a resource that has moved to a new URI and should be retrieved using GET.
|
||||
if (message.StatusCode == HttpStatusCode.SeeOther)
|
||||
{
|
||||
request.Method = HttpMethod.Get;
|
||||
}
|
||||
// Clear Authorization and If-* headers.
|
||||
request.Headers.Remove("Authorization");
|
||||
request.Headers.IfMatch.Clear();
|
||||
request.Headers.IfNoneMatch.Clear();
|
||||
request.Headers.IfModifiedSince = null;
|
||||
request.Headers.IfUnmodifiedSince = null;
|
||||
request.Headers.Remove("If-Range");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
Copyright 2013 Google Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace Google.Apis.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates if exponential back-off is used automatically on exceptions in a service requests and \ or when 503
|
||||
/// responses is returned form the server.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ExponentialBackOffPolicy
|
||||
{
|
||||
/// <summary>Exponential back-off is disabled.</summary>
|
||||
None = 0,
|
||||
/// <summary>Exponential back-off is enabled only for exceptions.</summary>
|
||||
Exception = 1,
|
||||
/// <summary>Exponential back-off is enabled only for 503 HTTP Status code.</summary>
|
||||
UnsuccessfulResponse503 = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An initializer which adds exponential back-off as exception handler and \ or unsuccessful response handler by
|
||||
/// the given <see cref="ExponentialBackOffPolicy"/>.
|
||||
/// </summary>
|
||||
public class ExponentialBackOffInitializer : IConfigurableHttpClientInitializer
|
||||
{
|
||||
/// <summary>Gets or sets the used back-off policy.</summary>
|
||||
private ExponentialBackOffPolicy Policy { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the back-off handler creation function.</summary>
|
||||
private Func<BackOffHandler> CreateBackOff { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new back-off initializer with the given policy and back-off handler create function.
|
||||
/// </summary>
|
||||
public ExponentialBackOffInitializer(ExponentialBackOffPolicy policy, Func<BackOffHandler> createBackOff)
|
||||
{
|
||||
Policy = policy;
|
||||
CreateBackOff = createBackOff;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Initialize(ConfigurableHttpClient httpClient)
|
||||
{
|
||||
var backOff = CreateBackOff();
|
||||
|
||||
// Add exception handler and \ or unsuccessful response handler.
|
||||
if ((Policy & ExponentialBackOffPolicy.Exception) == ExponentialBackOffPolicy.Exception)
|
||||
{
|
||||
httpClient.MessageHandler.AddExceptionHandler(backOff);
|
||||
}
|
||||
|
||||
if ((Policy & ExponentialBackOffPolicy.UnsuccessfulResponse503) ==
|
||||
ExponentialBackOffPolicy.UnsuccessfulResponse503)
|
||||
{
|
||||
httpClient.MessageHandler.AddUnsuccessfulResponseHandler(backOff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
Copyright 2013 Google Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
using System.Net.Http;
|
||||
|
||||
using Google.Apis.Logging;
|
||||
|
||||
namespace Google.Apis.Http
|
||||
{
|
||||
/// <summary>The default implementation of the HTTP client factory.</summary>
|
||||
public class HttpClientFactory : IHttpClientFactory
|
||||
{
|
||||
/// <summary>The class logger.</summary>
|
||||
private static readonly ILogger Logger = ApplicationContext.Logger.ForType<HttpClientFactory>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args)
|
||||
{
|
||||
// Create the handler.
|
||||
var handler = CreateHandler(args);
|
||||
var configurableHandler = new ConfigurableMessageHandler(handler)
|
||||
{
|
||||
ApplicationName = args.ApplicationName
|
||||
};
|
||||
|
||||
// Create the client.
|
||||
var client = new ConfigurableHttpClient(configurableHandler);
|
||||
foreach (var initializer in args.Initializers)
|
||||
{
|
||||
initializer.Initialize(client);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/// <summary>Creates a HTTP message handler. Override this method to mock a message handler.</summary>
|
||||
protected virtual HttpMessageHandler CreateHandler(CreateHttpClientArgs args)
|
||||
{
|
||||
var handler = new HttpClientHandler();
|
||||
|
||||
// If the framework supports redirect configuration, set it to false, because ConfigurableMessageHandler
|
||||
// handles redirect.
|
||||
if (handler.SupportsRedirectConfiguration)
|
||||
{
|
||||
handler.AllowAutoRedirect = false;
|
||||
}
|
||||
|
||||
// If the framework supports automatic decompression and GZip is enabled, set automatic decompression.
|
||||
if (handler.SupportsAutomaticDecompression && args.GZipEnabled)
|
||||
{
|
||||
handler.AutomaticDecompression = System.Net.DecompressionMethods.GZip |
|
||||
System.Net.DecompressionMethods.Deflate;
|
||||
}
|
||||
|
||||
Logger.Debug("Handler was created. SupportsRedirectConfiguration={0}, SupportsAutomaticDecompression={1}",
|
||||
handler.SupportsRedirectConfiguration, handler.SupportsAutomaticDecompression);
|
||||
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Yavsc/GoogleApiSupport/Google.Apis.Core/Http/HttpConsts.cs
Normal file
37
Yavsc/GoogleApiSupport/Google.Apis.Core/Http/HttpConsts.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
Copyright 2013 Google Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google.Apis.Http
|
||||
{
|
||||
/// <summary>HTTP constants.</summary>
|
||||
public static class HttpConsts
|
||||
{
|
||||
/// <summary>Http GET request</summary>
|
||||
public const string Get = "GET";
|
||||
|
||||
/// <summary>Http DELETE request</summary>
|
||||
public const string Delete = "DELETE";
|
||||
|
||||
/// <summary>Http PUT request</summary>
|
||||
public const string Put = "PUT";
|
||||
|
||||
/// <summary>Http POST request</summary>
|
||||
public const string Post = "POST";
|
||||
|
||||
/// <summary>Http PATCH request</summary>
|
||||
public const string Patch = "PATCH";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
Copyright 2013 Google Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Google.Apis.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods to <see cref="System.Net.Http.HttpRequestMessage"/> and
|
||||
/// <see cref="System.Net.Http.HttpResponseMessage"/>.
|
||||
/// </summary>
|
||||
public static class HttpExtenstions
|
||||
{
|
||||
/// <summary>Returns <c>true</c> if the response contains one of the redirect status codes.</summary>
|
||||
internal static bool IsRedirectStatusCode(this HttpResponseMessage message)
|
||||
{
|
||||
switch (message.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.Moved:
|
||||
case HttpStatusCode.Redirect:
|
||||
case HttpStatusCode.RedirectMethod:
|
||||
case HttpStatusCode.TemporaryRedirect:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>A Google.Apis utility method for setting an empty HTTP content.</summary>
|
||||
public static HttpContent SetEmptyContent(this HttpRequestMessage request)
|
||||
{
|
||||
request.Content = new ByteArrayContent(new byte[0]);
|
||||
request.Content.Headers.ContentLength = 0;
|
||||
return request.Content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright 2013 Google Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
namespace Google.Apis.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// HTTP client initializer for changing the default behavior of HTTP client.
|
||||
/// Use this initializer to change default values like timeout and number of tries.
|
||||
/// You can also set different handlers and interceptors like <see cref="IHttpUnsuccessfulResponseHandler"/>s,
|
||||
/// <see cref="IHttpExceptionHandler"/>s and <see cref="IHttpExecuteInterceptor"/>s.
|
||||
/// </summary>
|
||||
public interface IConfigurableHttpClientInitializer
|
||||
{
|
||||
/// <summary>Initializes a HTTP client after it was created.</summary>
|
||||
void Initialize(ConfigurableHttpClient httpClient);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
Copyright 2013 Google Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Google.Apis.Http
|
||||
{
|
||||
/// <summary>Arguments for creating a HTTP client.</summary>
|
||||
public class CreateHttpClientArgs
|
||||
{
|
||||
/// <summary>Gets or sets whether GZip is enabled.</summary>
|
||||
public bool GZipEnabled { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the application name that is sent in the User-Agent header.</summary>
|
||||
public string ApplicationName { get; set; }
|
||||
|
||||
/// <summary>Gets a list of initializers to initialize the HTTP client instance.</summary>
|
||||
public IList<IConfigurableHttpClientInitializer> Initializers { get; private set; }
|
||||
|
||||
/// <summary>Constructs a new argument instance.</summary>
|
||||
public CreateHttpClientArgs()
|
||||
{
|
||||
Initializers = new List<IConfigurableHttpClientInitializer>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HTTP client factory creates configurable HTTP clients. A unique HTTP client should be created for each service.
|
||||
/// </summary>
|
||||
public interface IHttpClientFactory
|
||||
{
|
||||
/// <summary>Creates a new configurable HTTP client.</summary>
|
||||
ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
Copyright 2013 Google Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Google.Apis.Http
|
||||
{
|
||||
/// <summary>Argument class to <see cref="IHttpExceptionHandler.HandleExceptionAsync"/>.</summary>
|
||||
public class HandleExceptionArgs
|
||||
{
|
||||
/// <summary>Gets or sets the sent request.</summary>
|
||||
public HttpRequestMessage Request { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the exception which occurred during sending the request.</summary>
|
||||
public Exception Exception { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the total number of tries to send the request.</summary>
|
||||
public int TotalTries { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the current failed try.</summary>
|
||||
public int CurrentFailedTry { get; set; }
|
||||
|
||||
/// <summary>Gets an indication whether a retry will occur if the handler returns <c>true</c>.</summary>
|
||||
public bool SupportsRetry
|
||||
{
|
||||
get { return TotalTries - CurrentFailedTry > 0; }
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the request's cancellation token.</summary>
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>Exception handler is invoked when an exception is thrown during a HTTP request.</summary>
|
||||
public interface IHttpExceptionHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles an exception thrown when sending a HTTP request.
|
||||
/// A simple rule must be followed, if you modify the request object in a way that the exception can be
|
||||
/// resolved, you must return <c>true</c>.
|
||||
/// </summary>
|
||||
/// <param name="args">
|
||||
/// Handle exception argument which properties such as the request, exception, current failed try.
|
||||
/// </param>
|
||||
/// <returns>Whether this handler has made a change that requires the request to be resent.</returns>
|
||||
Task<bool> HandleExceptionAsync(HandleExceptionArgs args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
Copyright 2013 Google Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Google.Apis.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// HTTP request execute interceptor to intercept a <see cref="System.Net.Http.HttpRequestMessage"/> before it has
|
||||
/// been sent. Sample usage is attaching "Authorization" header to a request.
|
||||
/// </summary>
|
||||
public interface IHttpExecuteInterceptor
|
||||
{
|
||||
/// <summary>
|
||||
/// <summary>Invoked before the request is being sent.</summary>
|
||||
/// </summary>
|
||||
/// <param name="request">The HTTP request message.</param>
|
||||
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
|
||||
Task InterceptAsync(HttpRequestMessage request, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright 2013 Google Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Google.Apis.Http
|
||||
{
|
||||
/// <summary>Argument class to <see cref="IHttpUnsuccessfulResponseHandler.HandleResponseAsync"/>.</summary>
|
||||
public class HandleUnsuccessfulResponseArgs
|
||||
{
|
||||
/// <summary>Gets or sets the sent request.</summary>
|
||||
public HttpRequestMessage Request { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the abnormal response.</summary>
|
||||
public HttpResponseMessage Response { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the total number of tries to send the request.</summary>
|
||||
public int TotalTries { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the current failed try.</summary>
|
||||
public int CurrentFailedTry { get; set; }
|
||||
|
||||
/// <summary>Gets an indication whether a retry will occur if the handler returns <c>true</c>.</summary>
|
||||
public bool SupportsRetry
|
||||
{
|
||||
get { return TotalTries - CurrentFailedTry > 0; }
|
||||
}
|
||||
|
||||
/// <summary>Gets or sets the request's cancellation token.</summary>
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsuccessful response handler which is invoked when an abnormal HTTP response is returned when sending a HTTP
|
||||
/// request.
|
||||
/// </summary>
|
||||
public interface IHttpUnsuccessfulResponseHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles an abnormal response when sending a HTTP request.
|
||||
/// A simple rule must be followed, if you modify the request object in a way that the abnormal response can
|
||||
/// be resolved, you must return <c>true</c>.
|
||||
/// </summary>
|
||||
/// <param name="args">
|
||||
/// Handle response argument which contains properties such as the request, response, current failed try.
|
||||
/// </param>
|
||||
/// <returns>Whether this handler has made a change that requires the request to be resent.</returns>
|
||||
Task<bool> HandleResponseAsync(HandleUnsuccessfulResponseArgs args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright 2013 Google Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Google.Apis.Testing;
|
||||
|
||||
namespace Google.Apis.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Intercepts HTTP GET requests with a URLs longer than a specified maximum number of characters.
|
||||
/// The interceptor will change such requests as follows:
|
||||
/// <list type="bullet">
|
||||
/// <item>The request's method will be changed to POST</item>
|
||||
/// <item>A <c>X-HTTP-Method-Override</c> header will be added with the value <c>GET</c></item>
|
||||
/// <item>Any query parameters from the URI will be moved into the body of the request.</item>
|
||||
/// <item>If query parameters are moved, the content type is set to <c>application/x-www-form-urlencoded</c></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
[VisibleForTestOnly]
|
||||
public class MaxUrlLengthInterceptor : IHttpExecuteInterceptor
|
||||
{
|
||||
private readonly uint maxUrlLength;
|
||||
|
||||
///<summary>Constructs a new Max URL length interceptor with the given max length.</summary>
|
||||
public MaxUrlLengthInterceptor(uint maxUrlLength)
|
||||
{
|
||||
this.maxUrlLength = maxUrlLength;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task InterceptAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (request.Method != HttpMethod.Get || request.RequestUri.AbsoluteUri.Length <= maxUrlLength)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
// Change the method to POST.
|
||||
request.Method = HttpMethod.Post;
|
||||
var query = request.RequestUri.Query;
|
||||
if (!String.IsNullOrEmpty(query))
|
||||
{
|
||||
// Move query parameters to the body (without the "?").
|
||||
request.Content = new StringContent(query.Substring(1));
|
||||
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
|
||||
var requestString = request.RequestUri.ToString();
|
||||
// The new request URI is the old one minus the "?" and everything that follows, since we moved the
|
||||
// query params to the body. For example: "www.example.com/?q=foo" => "www.example.com/".
|
||||
request.RequestUri = new Uri(requestString.Remove(requestString.IndexOf("?")));
|
||||
}
|
||||
request.Headers.Add("X-HTTP-Method-Override", "GET");
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue