/*
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.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Google.Apis.Discovery;
using Google.Apis.Http;
using Google.Apis.Json;
using Google.Apis.Logging;
using Google.Apis.Requests;
using Google.Apis.Util;
using Google.Apis.Testing;
namespace Google.Apis.Services
{
///
/// A base class for a client service which provides common mechanism for all services, like
/// serialization and GZip support. It should be safe to use a single service instance to make server requests
/// concurrently from multiple threads.
/// This class adds a special to the
/// execute interceptor list, which uses the given
/// Authenticator. It calls to its applying authentication method, and injects the "Authorization" header in the
/// request.
/// If the given Authenticator implements , this
/// class adds the Authenticator to the 's unsuccessful
/// response handler list.
///
public abstract class BaseClientService : IClientService
{
/// The class logger.
private static readonly ILogger Logger = ApplicationContext.Logger.ForType();
/// The default maximum allowed length of a URL string for GET requests.
[VisibleForTestOnly]
public const uint DefaultMaxUrlLength = 2048;
#region Initializer
/// An initializer class for the client service.
public class Initializer
{
///
/// Gets or sets the factory for creating instance. If this
/// property is not set the service uses a new instance.
///
public IHttpClientFactory HttpClientFactory { get; set; }
///
/// Gets or sets a HTTP client initializer which is able to customize properties on
/// and
/// .
///
public IConfigurableHttpClientInitializer HttpClientInitializer { get; set; }
///
/// Get or sets the exponential back-off policy used by the service. Default value is
/// UnsuccessfulResponse503, which means that exponential back-off is used on 503 abnormal HTTP
/// response.
/// If the value is set to None, no exponential back-off policy is used, and it's up to the user to
/// configure the in an
/// to set a specific back-off
/// implementation (using ).
///
public ExponentialBackOffPolicy DefaultExponentialBackOffPolicy { get; set; }
/// Gets or sets whether this service supports GZip. Default value is true.
public bool GZipEnabled { get; set; }
///
/// Gets or sets the serializer. Default value is .
///
public ISerializer Serializer { get; set; }
/// Gets or sets the API Key. Default value is null.
public string ApiKey { get; set; }
///
/// Gets or sets Application name to be used in the User-Agent header. Default value is null.
///
public string ApplicationName { get; set; }
///
/// Maximum allowed length of a URL string for GET requests. Default value is 2048. If the value is
/// set to 0, requests will never be modified due to URL string length.
///
public uint MaxUrlLength { get; set; }
/// Constructs a new initializer with default values.
public Initializer()
{
GZipEnabled = true;
Serializer = new NewtonsoftJsonSerializer();
DefaultExponentialBackOffPolicy = ExponentialBackOffPolicy.UnsuccessfulResponse503;
MaxUrlLength = DefaultMaxUrlLength;
}
internal void Validate()
{
// TODO: Validate ApplicationName
}
}
#endregion
/// Constructs a new base client with the specified initializer.
protected BaseClientService(Initializer initializer)
{
initializer.Validate();
// Set the right properties by the initializer's properties.
GZipEnabled = initializer.GZipEnabled;
Serializer = initializer.Serializer;
ApiKey = initializer.ApiKey;
ApplicationName = initializer.ApplicationName;
if (ApplicationName == null)
{
Logger.Warning("Application name is not set. Please set Initializer.ApplicationName property");
}
HttpClientInitializer = initializer.HttpClientInitializer;
// Create a HTTP client for this service.
HttpClient = CreateHttpClient(initializer);
}
/// Returns true if this service contains the specified feature.
private bool HasFeature(Features feature)
{
return Features.Contains(Utilities.GetEnumStringValue(feature));
}
private ConfigurableHttpClient CreateHttpClient(Initializer initializer)
{
// If factory wasn't set use the default HTTP client factory.
var factory = initializer.HttpClientFactory ?? new HttpClientFactory();
var args = new CreateHttpClientArgs
{
GZipEnabled = GZipEnabled,
ApplicationName = ApplicationName,
};
// Add the user's input initializer.
if (HttpClientInitializer != null)
{
args.Initializers.Add(HttpClientInitializer);
}
// Add exponential back-off initializer if necessary.
if (initializer.DefaultExponentialBackOffPolicy != ExponentialBackOffPolicy.None)
{
args.Initializers.Add(new ExponentialBackOffInitializer(initializer.DefaultExponentialBackOffPolicy,
CreateBackOffHandler));
}
var httpClient = factory.CreateHttpClient(args);
if (initializer.MaxUrlLength > 0)
{
httpClient.MessageHandler.AddExecuteInterceptor(new MaxUrlLengthInterceptor(initializer.MaxUrlLength));
}
return httpClient;
}
///
/// Creates the back-off handler with .
/// Overrides this method to change the default behavior of back-off handler (e.g. you can change the maximum
/// waited request's time span, or create a back-off handler with you own implementation of
/// ).
///
protected virtual BackOffHandler CreateBackOffHandler()
{
// TODO(peleyal): consider return here interface and not the concrete class
return new BackOffHandler(new ExponentialBackOff());
}
#region IClientService Members
///
public ConfigurableHttpClient HttpClient { get; private set; }
///
public IConfigurableHttpClientInitializer HttpClientInitializer { get; private set; }
///
public bool GZipEnabled { get; private set; }
///
public string ApiKey { get; private set; }
///
public string ApplicationName { get; private set; }
///
public void SetRequestSerailizedContent(HttpRequestMessage request, object body)
{
request.SetRequestSerailizedContent(this, body, GZipEnabled);
}
#region Serialization
///
public ISerializer Serializer { get; private set; }
///
public virtual string SerializeObject(object obj)
{
if (HasFeature(Discovery.Features.LegacyDataResponse))
{
// Legacy path
var request = new StandardResponse