Skip to content

[#6883] Capture x-ms-correlation-id and use in all outgoing requests #6893

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

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions libraries/Microsoft.Bot.Builder.Dialogs/SkillDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ private async Task<Activity> SendToSkillAsync(ITurnContext context, Activity act
await DialogOptions.ConversationState.SaveChangesAsync(context, true, cancellationToken).ConfigureAwait(false);

var skillInfo = DialogOptions.Skill;

DialogOptions.SkillClient.AddDefaultHeaders();

var response = await DialogOptions.SkillClient.PostActivityAsync<ExpectedReplies>(DialogOptions.BotId, skillInfo.AppId, skillInfo.SkillEndpoint, DialogOptions.SkillHostEndpoint, skillConversationId, activity, cancellationToken).ConfigureAwait(false);

// Inspect the skill response status
Expand Down
2 changes: 1 addition & 1 deletion libraries/Microsoft.Bot.Builder/CloudAdapterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ protected virtual ConnectorFactory GetStreamingConnectorFactory(Activity activit
/// <param name="callback">The method to call for the resulting bot turn.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that represents the work queued to execute.</returns>
protected async Task ProcessProactiveAsync(ClaimsIdentity claimsIdentity, Activity continuationActivity, string audience, BotCallbackHandler callback, CancellationToken cancellationToken)
protected virtual async Task ProcessProactiveAsync(ClaimsIdentity claimsIdentity, Activity continuationActivity, string audience, BotCallbackHandler callback, CancellationToken cancellationToken)
{
Logger.LogInformation($"ProcessProactiveAsync for Conversation Id: {continuationActivity.Conversation.Id}");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ public async override Task<InvokeResponse<T>> PostActivityAsync<T>(string fromBo
}
}

public override void AddDefaultHeaders()
{
ConnectorClient.AddDefaultRequestHeaders(_httpClient);
}

protected override void Dispose(bool disposing)
{
if (_disposed)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ public async virtual Task<InvokeResponse> PostActivityAsync(string fromBotId, st
/// <returns>Async task with optional invokeResponse.</returns>
public abstract Task<InvokeResponse<T>> PostActivityAsync<T>(string fromBotId, string toBotId, Uri toUrl, Uri serviceUrl, string conversationId, Activity activity, CancellationToken cancellationToken = default);

/// <summary>
/// Allows to add default headers to the HTTP client after the creation of the instance.
/// </summary>
public virtual void AddDefaultHeaders()
{
}

/// <inheritdoc/>
public void Dispose()
{
Expand Down
13 changes: 13 additions & 0 deletions libraries/Microsoft.Bot.Connector/ConnectorClientEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,19 @@ public static void AddDefaultRequestHeaders(HttpClient httpClient)
}
}

var headersToPropagate = HeaderPropagation.HeadersToPropagate;

if (headersToPropagate != null && headersToPropagate.Count > 0)
{
foreach (var header in headersToPropagate)
{
if (!httpClient.DefaultRequestHeaders.Contains(header.Key))
{
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value.ToArray());
}
}
}

httpClient.DefaultRequestHeaders.ExpectContinue = false;

var jsonAcceptHeader = new MediaTypeWithQualityHeaderValue("*/*");
Expand Down
88 changes: 88 additions & 0 deletions libraries/Microsoft.Bot.Connector/HeaderPropagation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Extensions.Primitives;

namespace Microsoft.Bot.Connector
{
/// <summary>
/// Class to handle header propagation from incoming request to outgoing request.
/// </summary>
public static class HeaderPropagation
{
private static readonly AsyncLocal<IDictionary<string, StringValues>> _requestHeaders = new ();

private static readonly AsyncLocal<IDictionary<string, StringValues>> _headersToPropagate = new ();

/// <summary>
/// Gets or sets the headers from an incoming request.
/// </summary>
/// <value>The headers from an incoming request.</value>
public static IDictionary<string, StringValues> RequestHeaders
{
get => _requestHeaders.Value ??= new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
set => _requestHeaders.Value = value;
}

/// <summary>
/// Gets or sets the selected headers for propagation.
/// </summary>
/// <value>The selected headers for propagation.</value>
public static IDictionary<string, StringValues> HeadersToPropagate
{
get => _headersToPropagate.Value ??= new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
set => _headersToPropagate.Value = value;
}

/// <summary>
/// Filters the request's headers to include only those relevant for propagation.
/// </summary>
/// <param name="headerFilter">The chosen headers to propagate.</param>
/// <returns>The filtered headers.</returns>
public static IDictionary<string, StringValues> FilterHeaders(HeaderPropagationEntryCollection headerFilter)
{
// We propagate the X-Ms-Correlation-Id header by default.
headerFilter.Propagate("X-Ms-Correlation-Id");

var filteredHeaders = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);

foreach (var filter in headerFilter.Entries)
{
if (RequestHeaders.TryGetValue(filter.Key, out var value))
{
switch (filter.Action)
{
case HeaderPropagationEntryAction.Add:
break;
case HeaderPropagationEntryAction.Append:
filteredHeaders[filter.Key] = StringValues.Concat(value, filter.Value);
break;
case HeaderPropagationEntryAction.Override:
filteredHeaders.Add(filter.Key, filter.Value);
break;
case HeaderPropagationEntryAction.Propagate:
filteredHeaders.Add(filter.Key, value);
break;
}
}
else
{
switch (filter.Action)
{
case HeaderPropagationEntryAction.Add:
filteredHeaders.Add(filter.Key, filter.Value);
break;
case HeaderPropagationEntryAction.Override:
filteredHeaders.Add(filter.Key, filter.Value);
break;
}
}
}

return filteredHeaders;
}
}
}
62 changes: 62 additions & 0 deletions libraries/Microsoft.Bot.Connector/HeaderPropagationEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Runtime.Serialization;
using Microsoft.Extensions.Primitives;

namespace Microsoft.Bot.Connector
{
/// <summary>
/// Represents the action to perform with the header entry.
/// </summary>
public enum HeaderPropagationEntryAction
{
/// <summary>
/// Adds a new header entry to the outgoing request.
/// </summary>
[EnumMember(Value = "add")]
Add,

/// <summary>
/// Appends a new header value to an existing key in the outgoing request.
/// </summary>
[EnumMember(Value = "append")]
Append,

/// <summary>
/// Propagates the header entry from the incoming request to the outgoing request without modifications.
/// </summary>
[EnumMember(Value = "propagate")]
Propagate,

/// <summary>
/// Overrides an existing header entry in the outgoing request.
/// </summary>
[EnumMember(Value = "override")]
Override
}

/// <summary>
/// Represents a single header entry used for header propagation.
/// </summary>
public class HeaderPropagationEntry
{
/// <summary>
/// Gets or sets the key of the header entry.
/// </summary>
/// <value>Key of the header entry.</value>
public string Key { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the value of the header entry.
/// </summary>
/// <value>Value of the header entry.</value>
public StringValues Value { get; set; } = new StringValues(string.Empty);

/// <summary>
/// Gets or sets the action of the header entry (Add, Append, Override or Propagate).
/// </summary>
/// <value>Action of the header entry.</value>
public HeaderPropagationEntryAction Action { get; set; } = HeaderPropagationEntryAction.Propagate;
}
}
102 changes: 102 additions & 0 deletions libraries/Microsoft.Bot.Connector/HeaderPropagationEntryCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Primitives;

namespace Microsoft.Bot.Connector
{
/// <summary>
/// Represents a collection of the header entries configured to be propagated to outgoing requests.
/// </summary>
public class HeaderPropagationEntryCollection
{
private readonly Dictionary<string, HeaderPropagationEntry> _entries = new (StringComparer.OrdinalIgnoreCase);

/// <summary>
/// Gets the collection of header entries to be propagated to outgoing requests.
/// </summary>
/// <value>The collection of header entries.</value>
public List<HeaderPropagationEntry> Entries => _entries.Values.ToList();

/// <summary>
/// Attempts to add a new header entry to the collection.
/// </summary>
/// <remarks>
/// If the key already exists, it will be ignored.
/// </remarks>
/// <param name="key">The key of the element to add.</param>
/// <param name="value">The value to add for the specified key.</param>
public void Add(string key, StringValues value)
{
_entries[key] = new HeaderPropagationEntry
{
Key = key,
Value = value,
Action = HeaderPropagationEntryAction.Add
};
}

/// <summary>
/// Appends a new header value to an existing key.
/// </summary>
/// <remarks>
/// If the key does not exist, it will be ignored.
/// </remarks>
/// <param name="key">The key of the element to append the value.</param>
/// <param name="value">The value to append for the specified key.</param>
public void Append(string key, StringValues value)
{
StringValues newValue;

if (_entries.TryGetValue(key, out var entry))
{
// If the key already exists, append the new value to the existing one.
newValue = StringValues.Concat(entry.Value, value);
}

_entries[key] = new HeaderPropagationEntry
{
Key = key,
Value = !StringValues.IsNullOrEmpty(newValue) ? newValue : value,
Action = HeaderPropagationEntryAction.Append
};
}

/// <summary>
/// Propagates the incoming request header value to outgoing requests without modifications.
/// </summary>
/// <remarks>
/// If the key does not exist, it will be ignored.
/// </remarks>
/// <param name="key">The key of the element to propagate.</param>
public void Propagate(string key)
{
_entries[key] = new HeaderPropagationEntry
{
Key = key,
Action = HeaderPropagationEntryAction.Propagate
};
}

/// <summary>
/// Overrides the header value of an existing key.
/// </summary>
/// <remarks>
/// If the key does not exist, it will add it.
/// </remarks>
/// <param name="key">The key of the element to override.</param>
/// <param name="value">The value to override in the specified key.</param>
public void Override(string key, StringValues value)
{
_entries[key] = new HeaderPropagationEntry
{
Key = key,
Value = value,
Action = HeaderPropagationEntryAction.Override
};
}
}
}
31 changes: 31 additions & 0 deletions libraries/Microsoft.Bot.Connector/Teams/TeamsHeaderPropagation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Extensions.Primitives;

namespace Microsoft.Bot.Connector.Teams
{
/// <summary>
/// Instantiate this class to set the headers to propagate from incoming request to outgoing request.
/// </summary>
public static class TeamsHeaderPropagation
{
/// <summary>
/// Returns the headers to propagate from incoming request to outgoing request.
/// </summary>
/// <returns>The collection of headers to propagate.</returns>
public static HeaderPropagationEntryCollection GetHeadersToPropagate()
{
// Propagate headers to the outgoing request by adding them to the HeaderPropagationEntryCollection.
// For example:
var headersToPropagate = new HeaderPropagationEntryCollection();

//headersToPropagate.Propagate("X-Ms-Teams-Id");
//headersToPropagate.Add("X-Ms-Teams-Custom", new StringValues("Custom-Value"));
//headersToPropagate.Append("X-Ms-Teams-Channel", new StringValues("-SubChannel-Id"));
//headersToPropagate.Override("X-Ms-Other", new StringValues("new-value"));

return headersToPropagate;
}
}
}
Loading