using System; using System.Collections.Generic; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using InfluxDB.Client.Core; using InfluxDB.Client.Core.Exceptions; using InfluxDB.Client.Core.Flux.Domain; using InfluxDB.Client.Core.Flux.Internal; using InfluxDB.Client.Core.Internal; using RestSharp; namespace InfluxDB.Client.Flux { public interface IFluxClient : IDisposable { /// /// Executes the Flux query against the InfluxDB and asynchronously map whole response to . /// /// /// NOTE: This method is not intended for large query results. /// Use for large data streaming. /// /// the flux query to execute /// Token that enables callers to cancel the request. /// which are matched the query Task> QueryAsync(string query, CancellationToken cancellationToken = default); /// /// Executes the Flux query against the InfluxDB and asynchronously map whole response to list of object with /// given type. /// /// NOTE: This method is not intended for large query results. /// Use for large data streaming. /// /// /// the flux query to execute /// Token that enables callers to cancel the request. /// the type of measurement /// which are matched the query Task> QueryAsync(string query, CancellationToken cancellationToken = default); /// /// Executes the Flux query against the InfluxDB and asynchronously stream to consumer. /// /// the flux query to execute /// the callback to consume the FluxRecord result /// the callback to consume any error notification /// the callback to consume a notification about successfully end of stream /// Token that enables callers to cancel the request. /// async task Task QueryAsync(string query, Action onNext, Action onError = null, Action onComplete = null, CancellationToken cancellationToken = default); /// /// Executes the Flux query against the InfluxDB and asynchronously stream result as POCO. /// /// the flux query to execute /// the callback to consume the FluxRecord result /// the callback to consume any error notification /// the callback to consume a notification about successfully end of stream /// Token that enables callers to cancel the request. /// the type of measurement /// async task Task QueryAsync(string query, Action onNext, Action onError = null, Action onComplete = null, CancellationToken cancellationToken = default); /// /// Executes the Flux query against the InfluxDB and synchronously map whole response to result. /// /// NOTE: This method is not intended for large responses, that do not fit into memory. /// Use /// /// /// the flux query to execute> /// Dialect is an object defining the options to use when encoding the response. /// See dialect SPEC. /// Token that enables callers to cancel the request. /// the raw response that matched the query Task QueryRawAsync(string query, string dialect = null, CancellationToken cancellationToken = default); /// /// Executes the Flux query against the InfluxDB and asynchronously stream response (line by line) to . /// /// the flux query to execute /// the callback to consume the response line by line /// Dialect is an object defining the options to use when encoding the response. See dialect SPEC. /// the callback to consume any error notification /// the callback to consume a notification about successfully end of stream /// Token that enables callers to cancel the request. /// async task Task QueryRawAsync(string query, Action onResponse, string dialect = null, Action onError = null, Action onComplete = null, CancellationToken cancellationToken = default); /// /// Check the status of InfluxDB Server. /// /// Cancellation token /// true if server is healthy otherwise return false Task PingAsync(CancellationToken cancellationToken = default); /// /// Return the version of the connected InfluxDB Server. /// /// Cancellation token /// the version String, otherwise unknown /// throws when request did not succesfully ends Task VersionAsync(CancellationToken cancellationToken = default); /// /// Set the log level for the request and response information. /// /// the log level to set void SetLogLevel(LogLevel logLevel); /// /// Set the that is used for logging requests and responses. /// /// Log Level LogLevel GetLogLevel(); } public class FluxClient : AbstractQueryClient, IFluxClient { private readonly LoggingHandler _loggingHandler; /// /// Create a instance of the Flux client. The url could be a connection string with various configurations. /// /// e.g.: "http://localhost:8086?timeout=5000&logLevel=BASIC /// The following options are supported: /// /// org - default destination organization for writes and queries /// bucket - default destination bucket for writes /// token - the token to use for the authorization /// logLevel (default - NONE) - rest client verbosity level /// timeout (default - 10000) - The timespan to wait before the HTTP request times out in milliseconds /// allowHttpRedirects (default - false) - Configure automatically following HTTP 3xx redirects /// verifySsl (default - true) - Ignore Certificate Validation Errors when false /// /// Options for logLevel: /// /// Basic - Logs request and response lines. /// Body - Logs request and response lines including headers and body (if present). Note that applying the `Body` LogLevel will disable chunking while streaming and will load the whole response into memory. /// Headers - Logs request and response lines including headers. /// None - Disable logging. /// /// /// /// the connectionString to connect to InfluxDB public FluxClient(string connectionString) : this(new FluxConnectionOptions(connectionString)) { } /// /// Create a instance of the Flux client. /// /// the connection configuration public FluxClient(FluxConnectionOptions options) : base(new FluxResultMapper()) { _loggingHandler = new LoggingHandler(LogLevel.None); var version = AssemblyHelper.GetVersion(typeof(FluxClient)); var restClientOptions = new RestClientOptions(options.Url) { MaxTimeout = (int)options.Timeout.TotalMilliseconds, UserAgent = $"influxdb-client-csharp/{version}", Proxy = options.WebProxy }; RestClient = new RestClient(restClientOptions); RestClient.AddDefaultHeader("Accept", "application/json"); if (!string.IsNullOrEmpty(options.Username)) { if (FluxConnectionOptions.AuthenticationType.BasicAuthentication.Equals(options.Authentication)) { var auth = Encoding.UTF8.GetBytes(options.Username + ":" + new string(options.Password)); RestClient.AddDefaultHeader("Authorization", "Basic " + Convert.ToBase64String(auth)); } else { RestClient.AddDefaultQueryParameter("u", options.Username); RestClient.AddDefaultQueryParameter("p", new string(options.Password)); } } } /// /// Executes the Flux query against the InfluxDB and asynchronously map whole response to . /// /// /// NOTE: This method is not intended for large query results. /// Use for large data streaming. /// /// the flux query to execute /// Token that enables callers to cancel the request. /// which are matched the query public async Task> QueryAsync(string query, CancellationToken cancellationToken = default) { Arguments.CheckNonEmptyString(query, "query"); var consumer = new FluxCsvParser.FluxResponseConsumerTable(); await QueryAsync(query, GetDefaultDialect(), consumer, ErrorConsumer, EmptyAction, cancellationToken) .ConfigureAwait(false); return consumer.Tables; } /// /// Executes the Flux query against the InfluxDB and asynchronously map whole response to list of object with /// given type. /// /// NOTE: This method is not intended for large query results. /// Use for large data streaming. /// /// /// the flux query to execute /// Token that enables callers to cancel the request. /// the type of measurement /// which are matched the query public async Task> QueryAsync(string query, CancellationToken cancellationToken = default) { var measurements = new List(); var consumer = new FluxResponseConsumerPoco((poco) => { measurements.Add(poco); }, Mapper); await QueryAsync(query, GetDefaultDialect(), consumer, ErrorConsumer, EmptyAction, cancellationToken) .ConfigureAwait(false); return measurements; } /// /// Executes the Flux query against the InfluxDB and asynchronously stream to consumer. /// /// the flux query to execute /// the callback to consume the FluxRecord result /// the callback to consume any error notification /// the callback to consume a notification about successfully end of stream /// Token that enables callers to cancel the request. /// async task public Task QueryAsync(string query, Action onNext, Action onError = null, Action onComplete = null, CancellationToken cancellationToken = default) { Arguments.CheckNonEmptyString(query, "query"); Arguments.CheckNotNull(onNext, "onNext"); var consumer = new FluxResponseConsumerRecord(onNext); return QueryAsync(query, GetDefaultDialect(), consumer, onError, onComplete, cancellationToken); } /// /// Executes the Flux query against the InfluxDB and asynchronously stream result as POCO. /// /// the flux query to execute /// the callback to consume the FluxRecord result /// the callback to consume any error notification /// the callback to consume a notification about successfully end of stream /// Token that enables callers to cancel the request. /// the type of measurement /// async task public Task QueryAsync(string query, Action onNext, Action onError = null, Action onComplete = null, CancellationToken cancellationToken = default) { Arguments.CheckNonEmptyString(query, "query"); Arguments.CheckNotNull(onNext, "onNext"); var consumer = new FluxResponseConsumerPoco(onNext, Mapper); return QueryAsync(query, GetDefaultDialect(), consumer, onError, onComplete, cancellationToken); } /// /// Executes the Flux query against the InfluxDB and synchronously map whole response to result. /// /// NOTE: This method is not intended for large responses, that do not fit into memory. /// Use /// /// /// the flux query to execute> /// Dialect is an object defining the options to use when encoding the response. /// See dialect SPEC. /// Token that enables callers to cancel the request. /// the raw response that matched the query public async Task QueryRawAsync(string query, string dialect = null, CancellationToken cancellationToken = default) { Arguments.CheckNonEmptyString(query, "query"); var rows = new List(); void Consumer(string row) { rows.Add(row); } await QueryRawAsync(query, Consumer, dialect, ErrorConsumer, EmptyAction, cancellationToken) .ConfigureAwait(false); return string.Join("\n", rows); } /// /// Executes the Flux query against the InfluxDB and asynchronously stream response (line by line) to . /// /// the flux query to execute /// the callback to consume the response line by line /// Dialect is an object defining the options to use when encoding the response. See dialect SPEC. /// the callback to consume any error notification /// the callback to consume a notification about successfully end of stream /// Token that enables callers to cancel the request. /// async task public Task QueryRawAsync(string query, Action onResponse, string dialect = null, Action onError = null, Action onComplete = null, CancellationToken cancellationToken = default) { Arguments.CheckNonEmptyString(query, "query"); Arguments.CheckNotNull(onResponse, "onNext"); var message = QueryRequest(CreateBody(dialect, query)); return QueryRaw(message, onResponse, onError ?? ErrorConsumer, onComplete ?? EmptyAction, cancellationToken); } /// /// Check the status of InfluxDB Server. /// /// Cancellation token /// true if server is healthy otherwise return false public async Task PingAsync(CancellationToken cancellationToken = default) { var request = ExecuteAsync(PingRequest(), cancellationToken); return await PingAsync(request); } /// /// Return the version of the connected InfluxDB Server. /// /// Cancellation token /// the version String, otherwise unknown /// throws when request did not succesfully ends public async Task VersionAsync(CancellationToken cancellationToken = default) { var request = ExecuteAsync(PingRequest(), cancellationToken); return await VersionAsync(request); } /// /// Set the log level for the request and response information. /// /// the log level to set public void SetLogLevel(LogLevel logLevel) { Arguments.CheckNotNull(logLevel, nameof(logLevel)); _loggingHandler.Level = logLevel; } /// /// Set the that is used for logging requests and responses. /// /// Log Level public LogLevel GetLogLevel() { return _loggingHandler.Level; } public void Dispose() { // // Dispose HttpClient // RestClient.Dispose(); } private Task QueryAsync(string query, string dialect, FluxCsvParser.IFluxResponseConsumer responseConsumer, Action onError = null, Action onComplete = null, CancellationToken cancellationToken = default) { var message = QueryRequest(CreateBody(dialect, query)); return Query(message, responseConsumer, onError ?? ErrorConsumer, onComplete ?? EmptyAction, cancellationToken); } private async Task ExecuteAsync(RestRequest request, CancellationToken cancellationToken = default) { BeforeIntercept(request); var response = await RestClient.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); RaiseForInfluxError(response, response.Content); AfterIntercept( (int)response.StatusCode, () => response.Headers, response.Content); return response; } protected override void BeforeIntercept(RestRequest request) { _loggingHandler.BeforeIntercept(request); } protected override T AfterIntercept(int statusCode, Func> headers, T body) { return (T)_loggingHandler.AfterIntercept(statusCode, headers, body); } private RestRequest PingRequest() { return new RestRequest("ping"); } private Func, RestRequest> QueryRequest(string query) { return advancedResponseWriter => new RestRequest("api/v2/query", Method.Post) .AddAdvancedResponseHandler(advancedResponseWriter) .AddParameter(new BodyParameter("application/json", query, "application/json")); } } }