/* * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. * * 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 RestSharp; using QuantConnect.Logging; using System.Threading.Tasks; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Collections.Generic; using QuantConnect.Util; using System.IO; using System.Threading; namespace QuantConnect.Api { /// /// API Connection and Hash Manager /// public class ApiConnection : IDisposable { /// /// Authorized client to use for requests. /// private HttpClient _httpClient; /// /// Authorized client to use for requests. /// [Obsolete("RestSharp is deprecated and will be removed in a future release. Please use the SetClient method or the request methods that take an HttpRequestMessage")] public RestClient Client { get; set; } // Authorization Credentials private readonly string _userId; private readonly string _token; private LeanAuthenticator _authenticator; /// /// Create a new Api Connection Class. /// /// User Id number from QuantConnect.com account. Found at www.quantconnect.com/account /// Access token for the QuantConnect account. Found at www.quantconnect.com/account public ApiConnection(int userId, string token) : this(userId, token, null) { } /// /// Create a new Api Connection Class. /// /// User Id number from QuantConnect.com account. Found at www.quantconnect.com/account /// Access token for the QuantConnect account. Found at www.quantconnect.com/account /// The client's base address /// Default headers for the client /// The client timeout in seconds public ApiConnection(int userId, string token, string baseUrl = null, Dictionary defaultHeaders = null, int timeout = 0) { _token = token; _userId = userId.ToStringInvariant(); SetClient(!string.IsNullOrEmpty(baseUrl) ? baseUrl : Globals.Api, defaultHeaders, timeout); } /// /// Return true if connected successfully. /// public bool Connected { get { using var request = new HttpRequestMessage(HttpMethod.Get, "authenticate"); return TryRequest(request, out AuthenticationResponse response) && response.Success; } } /// /// Overrides the current client /// /// The client's base address /// Default headers for the client /// The client timeout in seconds public void SetClient(string baseUrl, Dictionary defaultHeaders = null, int timeout = 0) { if (_httpClient != null) { _httpClient.DisposeSafely(); } _httpClient = new HttpClient() { BaseAddress = new Uri($"{baseUrl.TrimEnd('/')}/") }; Client = new RestClient(baseUrl); if (defaultHeaders != null) { foreach (var header in defaultHeaders) { _httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); } Client.AddDefaultHeaders(defaultHeaders); } if (timeout > 0) { _httpClient.Timeout = TimeSpan.FromSeconds(timeout); Client.Timeout = timeout * 1000; } else { _httpClient.Timeout = Timeout.InfiniteTimeSpan; } } /// /// Disposes of the HTTP client /// public void Dispose() { _httpClient.Dispose(); } /// /// Place a secure request and get back an object of type T. /// /// /// /// Result object from the /// T typed object response [Obsolete("RestSharp is deprecated and will be removed in a future release. Please use the TryRequest(HttpRequestMessage)")] public bool TryRequest(RestRequest request, out T result) where T : RestResponse { var resultTuple = TryRequestAsync(request).SynchronouslyAwaitTaskResult(); result = resultTuple.Item2; return resultTuple.Item1; } /// /// Place a secure request and get back an object of type T. /// /// /// /// Result object from the /// Timeout for the request /// T typed object response public bool TryRequest(HttpRequestMessage request, out T result, TimeSpan? timeout = null) where T : RestResponse { var resultTuple = TryRequestAsync(request, timeout).SynchronouslyAwaitTaskResult(); result = resultTuple.Item2; return resultTuple.Item1; } /// /// Place a secure request and get back an object of type T. /// /// /// /// T typed object response [Obsolete("RestSharp is deprecated and will be removed in a future release. Please use the TryRequestAsync(HttpRequestMessage)")] public async Task> TryRequestAsync(RestRequest request) where T : RestResponse { var responseContent = string.Empty; T result; try { SetAuthenticator(request); // Execute the authenticated REST API Call var restsharpResponse = await Client.ExecuteAsync(request).ConfigureAwait(false); //Verify success if (restsharpResponse.ErrorException != null) { Log.Error($"ApiConnection.TryRequest({request.Resource}): Error: {restsharpResponse.ErrorException.Message}"); return new Tuple(false, null); } if (!restsharpResponse.IsSuccessful) { Log.Error($"ApiConnect.TryRequest({request.Resource}): Content: {restsharpResponse.Content}"); } responseContent = restsharpResponse.Content; result = responseContent.DeserializeJson(); if (result == null || !result.Success) { Log.Debug($"ApiConnection.TryRequest({request.Resource}): Raw response: '{responseContent}'"); return new Tuple(false, result); } } catch (Exception err) { Log.Error($"ApiConnection.TryRequest({request.Resource}): Error: {err.Message}, Response content: {responseContent}"); return new Tuple(false, null); } return new Tuple(true, result); } /// /// Place a secure request and get back an object of type T. /// /// /// /// Timeout for the request /// T typed object response public async Task> TryRequestAsync(HttpRequestMessage request, TimeSpan? timeout = null) where T : RestResponse { HttpResponseMessage response = null; Stream responseContentStream = null; T result = null; // Default to 100 seconds (since we disabled the default client timeout) timeout ??= TimeSpan.FromSeconds(100); try { if (request.RequestUri.OriginalString.StartsWith('/')) { request.RequestUri = new Uri(request.RequestUri.ToString().TrimStart('/'), UriKind.Relative); } SetAuthenticator(request); // Execute the authenticated REST API Call using var cancellationTokenSource = new CancellationTokenSource(timeout.Value); response = await _httpClient.SendAsync(request, cancellationTokenSource.Token).ConfigureAwait(false); responseContentStream = await response.Content.ReadAsStreamAsync(cancellationTokenSource.Token).ConfigureAwait(false); result = responseContentStream.DeserializeJson(leaveOpen: true); if (!response.IsSuccessStatusCode) { Log.Error($"ApiConnect.TryRequest({request.RequestUri}): HTTP Error: {(int)response.StatusCode} {response.ReasonPhrase}. " + $"Content: {GetRawResponseContent(responseContentStream)}"); } if (result == null || !result.Success) { if (Log.DebuggingEnabled) { Log.Debug($"ApiConnection.TryRequest({request.RequestUri}): Raw response: '{GetRawResponseContent(responseContentStream)}'"); } return new Tuple(false, result); } } catch (Exception err) { Log.Error($"ApiConnection.TryRequest({request.RequestUri}): Error: {err.Message}, Response content: {GetRawResponseContent(responseContentStream)}"); return new Tuple(false, null); } finally { response?.DisposeSafely(); responseContentStream?.DisposeSafely(); } return new Tuple(true, result); } private static string GetRawResponseContent(Stream stream) { if (stream == null) { return string.Empty; } try { stream.Position = 0; using var reader = new StreamReader(stream, leaveOpen: true); return reader.ReadToEnd(); } catch (Exception) { return string.Empty; } } private void SetAuthenticator(RestRequest request) { var base64EncodedAuthenticationString = GetAuthenticatorHeader(out var timeStamp); request.AddOrUpdateHeader("Authorization", $"Basic {base64EncodedAuthenticationString}"); request.AddOrUpdateHeader("Timestamp", timeStamp); } private void SetAuthenticator(HttpRequestMessage request) { request.Headers.Remove("Authorization"); request.Headers.Remove("Timestamp"); var base64EncodedAuthenticationString = GetAuthenticatorHeader(out var timeStamp); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodedAuthenticationString); request.Headers.Add("Timestamp", timeStamp); } private string GetAuthenticatorHeader(out string timeStamp) { var newTimeStamp = (int)Time.TimeStamp(); var currentAuth = _authenticator; if (currentAuth == null || newTimeStamp - currentAuth.TimeStamp > 7000) { // Generate the hash each request // Add the UTC timestamp to the request header. // Timestamps older than 7200 seconds will not work. var hash = Api.CreateSecureHash(newTimeStamp, _token); var authenticationString = $"{_userId}:{hash}"; var base64EncodedAuthenticationString = Convert.ToBase64String(Encoding.UTF8.GetBytes(authenticationString)); _authenticator = currentAuth = new LeanAuthenticator(newTimeStamp, base64EncodedAuthenticationString); } timeStamp = currentAuth.TimeStampStr; return currentAuth.Base64EncodedAuthenticationString; } private class LeanAuthenticator { public int TimeStamp { get; } public string TimeStampStr { get; } public string Base64EncodedAuthenticationString { get; } public LeanAuthenticator(int timeStamp, string base64EncodedAuthenticationString) { TimeStamp = timeStamp; TimeStampStr = timeStamp.ToStringInvariant(); Base64EncodedAuthenticationString = base64EncodedAuthenticationString; } } } }