Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: custom headers can be specified for queries/writes #90

Merged
merged 5 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/SUPPORT.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ body:
WOAHH, hold up. This isn't the best place for support questions.
You can get a faster response on slack or forums:

Please redirect any QUESTIONS about Telegraf usage to
Please redirect any QUESTIONS about Client usage to
- InfluxData Slack Channel: https://app.slack.com/huddle/TH8RGQX5Z/C02UDUPLQKA
- InfluxData Community Site: https://community.influxdata.com

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## 0.6.0 [unreleased]

### Features

1. [#90](https://github.com/InfluxCommunity/influxdb3-csharp/pull/90): Custom `HTTP/gRPC` headers can be specified globally by config or per request

## 0.5.0 [2024-03-01]

### Features
Expand Down
35 changes: 33 additions & 2 deletions Client.Test/InfluxDBClientQueryTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public async Task PassNamedParametersToFlightClient()
var mockFlightSqlClient = new Mock<IFlightSqlClient>();
mockFlightSqlClient
.Setup(m => m.Execute(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<QueryType>(),
It.IsAny<Dictionary<string, object>>()))
It.IsAny<Dictionary<string, object>>(), It.IsAny<Dictionary<string, string>>()))
.Returns(new List<RecordBatch>().ToAsyncEnumerable());

//
Expand All @@ -76,7 +76,7 @@ public async Task PassNamedParametersToFlightClient()

_ = await _client.QueryPoints(query, database: "my-db", queryType: queryType, namedParameters: namedParameters)
.ToListAsync();
mockFlightSqlClient.Verify(m => m.Execute(query, "my-db", queryType, namedParameters), Times.Exactly(1));
mockFlightSqlClient.Verify(m => m.Execute(query, "my-db", queryType, namedParameters, new Dictionary<string, string>()), Times.Exactly(1));
}

[Test]
Expand All @@ -99,4 +99,35 @@ public void NotSupportedQueryParameterType()
Is.EqualTo(
"The parameter 'location' has unsupported type 'System.DateTime'. The supported types are 'string', 'bool', 'int' and 'float'."));
}

[Test]
public async Task PassHeadersToFlightClient()
{
//
// Mock the FlightSqlClient
//
var mockFlightSqlClient = new Mock<IFlightSqlClient>();
mockFlightSqlClient
.Setup(m => m.Execute(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<QueryType>(),
It.IsAny<Dictionary<string, object>>(), It.IsAny<Dictionary<string, string>>()))
.Returns(new List<RecordBatch>().ToAsyncEnumerable());

//
// Setup the client with the mocked FlightSqlClient
//
_client = new InfluxDBClient(MockServerUrl);
_client.FlightSqlClient.Dispose();
_client.FlightSqlClient = mockFlightSqlClient.Object;

const string query = "select * from cpu";
const QueryType queryType = QueryType.SQL;

var headers = new Dictionary<string, string>{
{
"X-Tracing-Id", "123"
}};
_ = await _client.QueryPoints(query, database: "my-db", queryType: queryType, headers: headers)
.ToListAsync();
mockFlightSqlClient.Verify(m => m.Execute(query, "my-db", queryType, new Dictionary<string, object>(), headers), Times.Exactly(1));
}
}
71 changes: 69 additions & 2 deletions Client.Test/InfluxDBClientWriteTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ public async Task Proxy()
Token = "my-token",
Organization = "my-org",
Database = "my-database",
Proxy = new System.Net.WebProxy
Proxy = new WebProxy
{
Address = new Uri(MockProxyUrl),
BypassProxyOnLocal = false
Expand Down Expand Up @@ -333,7 +333,74 @@ public async Task CustomHeader()
await _client.WritePointAsync(point);

var requests = MockServer.LogEntries.ToList();
Assert.That(requests[0].RequestMessage.BodyData?.BodyAsString, Is.EqualTo("h2o,location=europe level=2i 123000000000"));
Assert.That(requests, Has.Count.EqualTo(1));
Assert.Multiple(() =>
{
Assert.That(requests[0].RequestMessage.BodyData?.BodyAsString, Is.EqualTo("h2o,location=europe level=2i 123000000000"));
Assert.That(requests[0].RequestMessage.Headers?["X-device"].First(), Is.EqualTo("ab-01"));
});
}

[Test]
public async Task CustomHeaderFromRequest()
{
_client = new InfluxDBClient(new ClientConfig
{
Host = MockServerUrl,
Token = "my-token",
Organization = "my-org",
Database = "my-database"
});
MockServer
.Given(Request.Create().WithPath("/api/v2/write").WithHeader("X-Tracing-ID", "123").UsingPost())
.RespondWith(Response.Create().WithStatusCode(204));

var point = PointData.Measurement("h2o")
.SetTag("location", "europe")
.SetField("level", 2)
.SetTimestamp(123_000_000_000L);

await _client.WritePointAsync(point, headers: new Dictionary<string, string>
{
{ "X-Tracing-ID", "123" },
});

var requests = MockServer.LogEntries.ToList();
Assert.That(requests, Has.Count.EqualTo(1));
Assert.That(requests[0].RequestMessage.Headers?["X-Tracing-ID"].First(), Is.EqualTo("123"));
}

[Test]
public async Task CustomHeaderFromRequestArePreferred()
{
_client = new InfluxDBClient(new ClientConfig
{
Host = MockServerUrl,
Token = "my-token",
Organization = "my-org",
Database = "my-database",
Headers = new Dictionary<string, string>
{
{ "X-Client-ID", "123" },
}
});
MockServer
.Given(Request.Create().WithPath("/api/v2/write").WithHeader("X-Client-ID", "456").UsingPost())
.RespondWith(Response.Create().WithStatusCode(204));

var point = PointData.Measurement("h2o")
.SetTag("location", "europe")
.SetField("level", 2)
.SetTimestamp(123_000_000_000L);

await _client.WritePointAsync(point, headers: new Dictionary<string, string>
{
{ "X-Client-ID", "456" },
});

var requests = MockServer.LogEntries.ToList();
Assert.That(requests, Has.Count.EqualTo(1));
Assert.That(requests[0].RequestMessage.Headers?["X-Client-ID"].First(), Is.EqualTo("456"));
}

private async Task WriteData()
Expand Down
77 changes: 75 additions & 2 deletions Client.Test/Internal/FlightSqlClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ public void SetUp()
_flightSqlClient = new FlightSqlClient(config, InfluxDBClient.CreateAndConfigureHttpClient(config));
}

[TearDownAttribute]
public void TearDownAttribute()
[TearDown]
public new void TearDown()
{
_flightSqlClient.Dispose();
}
Expand Down Expand Up @@ -73,4 +73,77 @@ public void PrepareFlightTicketNamedParameters()
Assert.That(Encoding.UTF8.GetString(prepareFlightTicket.Ticket.ToByteArray()), Is.EqualTo(ticket));
});
}

[Test]
public void HeadersMetadataFromRequest()
{
var prepareHeadersMetadata =
_flightSqlClient.PrepareHeadersMetadata(new Dictionary<string, string> { { "X-Tracing-Id", "987" } });

Assert.Multiple(() =>
{
Assert.That(prepareHeadersMetadata, Is.Not.Null);
Assert.That(prepareHeadersMetadata, Has.Count.EqualTo(1));
Assert.That(prepareHeadersMetadata[0].Key, Is.EqualTo("x-tracing-id"));
Assert.That(prepareHeadersMetadata[0].Value, Is.EqualTo("987"));
});
}

[Test]
public void HeadersMetadataFromConfig()
{
_flightSqlClient.Dispose();

var config = new ClientConfig
{
Host = MockServerUrl,
Timeout = TimeSpan.FromSeconds(45),
Headers = new Dictionary<string, string>
{
{ "X-Global-Tracing-Id", "123" }
}
};

_flightSqlClient = new FlightSqlClient(config, InfluxDBClient.CreateAndConfigureHttpClient(config));

var prepareHeadersMetadata =
_flightSqlClient.PrepareHeadersMetadata(new Dictionary<string, string>());

Assert.Multiple(() =>
{
Assert.That(prepareHeadersMetadata, Is.Not.Null);
Assert.That(prepareHeadersMetadata, Has.Count.EqualTo(1));
Assert.That(prepareHeadersMetadata[0].Key, Is.EqualTo("x-global-tracing-id"));
Assert.That(prepareHeadersMetadata[0].Value, Is.EqualTo("123"));
});
}

[Test]
public void HeadersMetadataFromRequestArePreferred()
{
_flightSqlClient.Dispose();

var config = new ClientConfig
{
Host = MockServerUrl,
Timeout = TimeSpan.FromSeconds(45),
Headers = new Dictionary<string, string>
{
{ "X-Tracing-Id", "ABC" }
}
};

_flightSqlClient = new FlightSqlClient(config, InfluxDBClient.CreateAndConfigureHttpClient(config));

var prepareHeadersMetadata =
_flightSqlClient.PrepareHeadersMetadata(new Dictionary<string, string> { { "X-Tracing-Id", "258" } });

Assert.Multiple(() =>
{
Assert.That(prepareHeadersMetadata, Is.Not.Null);
Assert.That(prepareHeadersMetadata, Has.Count.EqualTo(1));
Assert.That(prepareHeadersMetadata[0].Key, Is.EqualTo("x-tracing-id"));
Assert.That(prepareHeadersMetadata[0].Value, Is.EqualTo("258"));
});
}
}
2 changes: 1 addition & 1 deletion Client.Test/MockServerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public void OneTimeSetUp()
MockProxyUrl = MockProxy.Urls[0];
}

[OneTimeTearDownAttribute]
[OneTimeTearDown]
public void OneTimeTearDownAttribute()
{
MockServer.Dispose();
Expand Down
19 changes: 17 additions & 2 deletions Client/Config/ClientConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,24 @@ public string Host
public string? Database { get; set; }

/// <summary>
/// The set of HTTP headers to be included in requests.
/// The custom headers that will be added to requests. This is useful for adding custom headers to requests,
/// such as tracing headers. To add custom headers use following code:
///
/// <code>
/// using var client = new InfluxDBClient(new ClientConfig
/// {
/// Host = "https://us-east-1-1.aws.cloud2.influxdata.com",
/// Token = "my-token",
/// Organization = "my-org",
/// Database = "my-database",
/// Headers = new Dictionary&lt;string, string&gt;
/// {
/// { "X-Tracing-Id", "123" },
/// }
/// });
/// </code>
/// </summary>
public Dictionary<String, String>? Headers { get; set; }
public Dictionary<string, string>? Headers { get; set; }

/// <summary>
/// Timeout to wait before the HTTP request times out. Default to '10 seconds'.
Expand Down
Loading
Loading