Skip to content

Commit c76ce40

Browse files
Improves support for mock headers
1 parent e020e5b commit c76ce40

File tree

15 files changed

+106
-91
lines changed

15 files changed

+106
-91
lines changed

dev-proxy-abstractions/GraphBatchResponsePayload.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class GraphBatchResponsePayloadResponse
2020
[JsonPropertyName("body")]
2121
public dynamic? Body { get; set; }
2222
[JsonPropertyName("headers")]
23-
public List<KeyValuePair<string, string>>? Headers { get; set; }
23+
public List<MockResponseHeader>? Headers { get; set; }
2424
}
2525

2626
public class GraphBatchResponsePayloadResponseBody

dev-proxy-plugins/MockResponses/MockResponse.cs renamed to dev-proxy-abstractions/MockResponse.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
// Copyright (c) Microsoft Corporation.
1+
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

44
using System.Text.Json.Serialization;
55

6-
namespace Microsoft.DevProxy.Plugins.MockResponses;
6+
namespace Microsoft.DevProxy.Abstractions;
77

88
public class MockResponse
99
{
@@ -30,5 +30,23 @@ public class MockResponseResponse
3030
[JsonPropertyName("body")]
3131
public dynamic? Body { get; set; }
3232
[JsonPropertyName("headers")]
33-
public List<KeyValuePair<string, string>>? Headers { get; set; }
33+
public List<MockResponseHeader>? Headers { get; set; }
34+
}
35+
36+
public class MockResponseHeader
37+
{
38+
[JsonPropertyName("name")]
39+
public string Name { get; set; } = string.Empty;
40+
[JsonPropertyName("value")]
41+
public string Value { get; set; } = string.Empty;
42+
43+
public MockResponseHeader()
44+
{
45+
}
46+
47+
public MockResponseHeader(string name, string value)
48+
{
49+
Name = name;
50+
Value = value;
51+
}
3452
}

dev-proxy-abstractions/ProxyUtils.cs

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33

44
using System.Reflection;
55
using System.Text.RegularExpressions;
6-
using Microsoft.Data.Sqlite;
76
using Titanium.Web.Proxy.Http;
8-
using Titanium.Web.Proxy.Models;
97

108
namespace Microsoft.DevProxy.Abstractions;
119

@@ -61,28 +59,28 @@ public static bool IsGraphBetaUrl(Uri uri) =>
6159
/// <param name="request">The http request for which response headers are being constructed</param>
6260
/// <param name="requestId">string a guid representing the a unique identifier for the request</param>
6361
/// <param name="requestDate">string representation of the date and time the request was made</param>
64-
/// <returns>IList<HttpHeader> with defaults consistent with Microsoft Graph. Automatically adds CORS headers when the Origin header is present</returns>
65-
public static IList<HttpHeader> BuildGraphResponseHeaders(Request request, string requestId, string requestDate)
62+
/// <returns>IList<MockResponseHeader> with defaults consistent with Microsoft Graph. Automatically adds CORS headers when the Origin header is present</returns>
63+
public static IList<MockResponseHeader> BuildGraphResponseHeaders(Request request, string requestId, string requestDate)
6664
{
6765
if (!IsGraphRequest(request))
6866
{
69-
return new List<HttpHeader>();
67+
return new List<MockResponseHeader>();
7068
}
7169

72-
var headers = new List<HttpHeader>
70+
var headers = new List<MockResponseHeader>
7371
{
74-
new HttpHeader("Cache-Control", "no-store"),
75-
new HttpHeader("x-ms-ags-diagnostic", ""),
76-
new HttpHeader("Strict-Transport-Security", ""),
77-
new HttpHeader("request-id", requestId),
78-
new HttpHeader("client-request-id", requestId),
79-
new HttpHeader("Date", requestDate),
80-
new HttpHeader("Content-Type", "application/json")
72+
new ("Cache-Control", "no-store"),
73+
new ("x-ms-ags-diagnostic", ""),
74+
new ("Strict-Transport-Security", ""),
75+
new ("request-id", requestId),
76+
new ("client-request-id", requestId),
77+
new ("Date", requestDate),
78+
new ("Content-Type", "application/json")
8179
};
8280
if (request.Headers.FirstOrDefault((h) => h.Name.Equals("Origin", StringComparison.OrdinalIgnoreCase)) is not null)
8381
{
84-
headers.Add(new HttpHeader("Access-Control-Allow-Origin", "*"));
85-
headers.Add(new HttpHeader("Access-Control-Expose-Headers", "ETag, Location, Preference-Applied, Content-Range, request-id, client-request-id, ReadWriteConsistencyToken, SdkVersion, WWW-Authenticate, x-ms-client-gcc-tenant, Retry-After"));
82+
headers.Add(new("Access-Control-Allow-Origin", "*"));
83+
headers.Add(new("Access-Control-Expose-Headers", "ETag, Location, Preference-Applied, Content-Range, request-id, client-request-id, ReadWriteConsistencyToken, SdkVersion, WWW-Authenticate, x-ms-client-gcc-tenant, Retry-After"));
8684
}
8785
return headers;
8886
}
@@ -303,7 +301,7 @@ public static string ProductVersion
303301
}
304302
}
305303

306-
public static void MergeHeaders(IList<HttpHeader> allHeaders, IList<HttpHeader> headersToAdd)
304+
public static void MergeHeaders(IList<MockResponseHeader> allHeaders, IList<MockResponseHeader> headersToAdd)
307305
{
308306
foreach (var header in headersToAdd)
309307
{
@@ -316,7 +314,7 @@ public static void MergeHeaders(IList<HttpHeader> allHeaders, IList<HttpHeader>
316314
var newValues = header.Value.Split(',').Select(v => v.Trim());
317315
var allValues = existingValues.Union(newValues).Distinct();
318316
allHeaders.Remove(existingHeader);
319-
allHeaders.Add(new HttpHeader(header.Name, string.Join(", ", allValues)));
317+
allHeaders.Add(new(header.Name, string.Join(", ", allValues)));
320318
continue;
321319
}
322320

dev-proxy-plugins/Behavior/RateLimitingPlugin.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ private ThrottlingInfo ShouldThrottle(Request request, string throttlingKey)
6161

6262
private void UpdateProxyResponse(ProxyHttpEventArgsBase e, HttpStatusCode errorStatus)
6363
{
64-
var headers = new List<HttpHeader>();
64+
var headers = new List<MockResponseHeader>();
6565
var body = string.Empty;
6666
var request = e.Session.HttpClient.Request;
6767
var response = e.Session.HttpClient.Response;
@@ -89,21 +89,21 @@ private void UpdateProxyResponse(ProxyHttpEventArgsBase e, HttpStatusCode errorS
8989
);
9090
}
9191

92-
headers.Add(new HttpHeader(_configuration.HeaderRetryAfter, _configuration.RetryAfterSeconds.ToString()));
92+
headers.Add(new(_configuration.HeaderRetryAfter, _configuration.RetryAfterSeconds.ToString()));
9393

94-
e.Session.GenericResponse(body ?? string.Empty, errorStatus, headers);
94+
e.Session.GenericResponse(body ?? string.Empty, errorStatus, headers.Select(h => new HttpHeader(h.Name, h.Value)).ToArray());
9595
return;
9696
}
9797

9898
if (e.PluginData.TryGetValue(Name, out var pluginData) &&
99-
pluginData is List<HttpHeader> rateLimitingHeaders)
99+
pluginData is List<MockResponseHeader> rateLimitingHeaders)
100100
{
101101
ProxyUtils.MergeHeaders(headers, rateLimitingHeaders);
102102
}
103103

104104
// add headers to the original API response, avoiding duplicates
105105
headers.ForEach(h => e.Session.HttpClient.Response.Headers.RemoveHeader(h.Name));
106-
e.Session.HttpClient.Response.Headers.AddHeaders(headers);
106+
e.Session.HttpClient.Response.Headers.AddHeaders(headers.Select(h => new HttpHeader(h.Name, h.Value)).ToArray());
107107
}
108108
private static string BuildApiErrorMessage(Request r) => $"Some error was generated by the proxy. {(ProxyUtils.IsGraphRequest(r) ? ProxyUtils.IsSdkRequest(r) ? "" : String.Join(' ', MessageUtils.BuildUseSdkForErrorsMessage(r)) : "")}";
109109

@@ -203,7 +203,7 @@ _urlsToWatch is null ||
203203
if (_configuration.CustomResponse is not null)
204204
{
205205
var headers = _configuration.CustomResponse.Headers is not null ?
206-
_configuration.CustomResponse.Headers.Select(kvp => new HttpHeader(kvp.Key, kvp.Value)).ToArray():
206+
_configuration.CustomResponse.Headers.Select(h => new HttpHeader(h.Name, h.Value)).ToArray() :
207207
Array.Empty<HttpHeader>();
208208

209209
// allow custom throttling response
@@ -242,31 +242,31 @@ private void StoreRateLimitingHeaders(ProxyRequestArgs e)
242242
return;
243243
}
244244

245-
var headers = new List<HttpHeader>();
245+
var headers = new List<MockResponseHeader>();
246246
var reset = _configuration.ResetFormat == RateLimitResetFormat.SecondsLeft ?
247247
(_resetTime - DateTime.Now).TotalSeconds.ToString("N0") : // drop decimals
248248
new DateTimeOffset(_resetTime).ToUnixTimeSeconds().ToString();
249-
headers.AddRange(new List<HttpHeader>
249+
headers.AddRange(new List<MockResponseHeader>
250250
{
251-
new HttpHeader(_configuration.HeaderLimit, _configuration.RateLimit.ToString()),
252-
new HttpHeader(_configuration.HeaderRemaining, _resourcesRemaining.ToString()),
253-
new HttpHeader(_configuration.HeaderReset, reset)
251+
new(_configuration.HeaderLimit, _configuration.RateLimit.ToString()),
252+
new(_configuration.HeaderRemaining, _resourcesRemaining.ToString()),
253+
new(_configuration.HeaderReset, reset)
254254
});
255255

256256
ExposeRateLimitingForCors(headers, e);
257257

258258
e.PluginData.Add(Name, headers);
259259
}
260260

261-
private void ExposeRateLimitingForCors(IList<HttpHeader> headers, ProxyRequestArgs e)
261+
private void ExposeRateLimitingForCors(IList<MockResponseHeader> headers, ProxyRequestArgs e)
262262
{
263263
var request = e.Session.HttpClient.Request;
264264
if (request.Headers.FirstOrDefault((h) => h.Name.Equals("Origin", StringComparison.OrdinalIgnoreCase)) is null)
265265
{
266266
return;
267267
}
268268

269-
headers.Add(new HttpHeader("Access-Control-Allow-Origin", "*"));
270-
headers.Add(new HttpHeader("Access-Control-Expose-Headers", $"{_configuration.HeaderLimit}, {_configuration.HeaderRemaining}, {_configuration.HeaderReset}, {_configuration.HeaderRetryAfter}"));
269+
headers.Add(new("Access-Control-Allow-Origin", "*"));
270+
headers.Add(new("Access-Control-Expose-Headers", $"{_configuration.HeaderLimit}, {_configuration.HeaderRemaining}, {_configuration.HeaderReset}, {_configuration.HeaderRetryAfter}"));
271271
}
272272
}

dev-proxy-plugins/Behavior/RetryAfterPlugin.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ private void ThrottleIfNecessary(ProxyRequestArgs e)
7070

7171
private void UpdateProxyResponse(ProxyRequestArgs e, ThrottlingInfo throttlingInfo, string message)
7272
{
73-
var headers = new List<HttpHeader>();
73+
var headers = new List<MockResponseHeader>();
7474
var body = string.Empty;
7575
var request = e.Session.HttpClient.Request;
7676

@@ -95,9 +95,9 @@ private void UpdateProxyResponse(ProxyRequestArgs e, ThrottlingInfo throttlingIn
9595
);
9696
}
9797

98-
headers.Add(new HttpHeader(throttlingInfo.RetryAfterHeaderName, throttlingInfo.ThrottleForSeconds.ToString()));
98+
headers.Add(new(throttlingInfo.RetryAfterHeaderName, throttlingInfo.ThrottleForSeconds.ToString()));
9999

100-
e.Session.GenericResponse(body ?? string.Empty, HttpStatusCode.TooManyRequests, headers);
100+
e.Session.GenericResponse(body ?? string.Empty, HttpStatusCode.TooManyRequests, headers.Select(h => new HttpHeader(h.Name, h.Value)));
101101
e.ResponseState.HasBeenSet = true;
102102
}
103103

dev-proxy-plugins/GenericErrorResponse.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System.Text.Json.Serialization;
5+
using Microsoft.DevProxy.Abstractions;
56

67
namespace Microsoft.DevProxy.Plugins;
78

@@ -10,7 +11,7 @@ public class GenericErrorResponse
1011
[JsonPropertyName("statusCode")]
1112
public int StatusCode { get; set; }
1213
[JsonPropertyName("headers")]
13-
public Dictionary<string, string>? Headers { get; set; }
14+
public List<MockResponseHeader>? Headers { get; set; }
1415
[JsonPropertyName("body")]
1516
public dynamic? Body { get; set; }
1617
}

dev-proxy-plugins/Inspection/DevToolsPlugin.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ private async Task BeforeRequest(object sender, ProxyRequestArgs e)
160160
}
161161

162162
var requestId = GetRequestId(e.Session.HttpClient.Request);
163-
var headers = e.Session.HttpClient.Request.Headers.Select(h =>
164-
new KeyValuePair<string, string>(h.Name, h.Value)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
163+
var headers = e.Session.HttpClient.Request.Headers
164+
.ToDictionary(h => h.Name, h => h.Value);
165165

166166
var requestWillBeSentMessage = new RequestWillBeSentMessage
167167
{
@@ -240,8 +240,8 @@ private async Task AfterResponse(object sender, ProxyResponseArgs e)
240240
Url = e.Session.HttpClient.Request.Url,
241241
Status = e.Session.HttpClient.Response.StatusCode,
242242
StatusText = e.Session.HttpClient.Response.StatusDescription,
243-
Headers = e.Session.HttpClient.Response.Headers.Select(h =>
244-
new KeyValuePair<string, string>(h.Name, h.Value)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value),
243+
Headers = e.Session.HttpClient.Response.Headers
244+
.ToDictionary(h => h.Name, h => h.Value),
245245
MimeType = e.Session.HttpClient.Response.ContentType
246246
}
247247
}

dev-proxy-plugins/MockResponses/GraphMockResponsePlugin.cs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,19 @@ protected override async Task OnRequest(object? sender, ProxyRequestArgs e)
4646
.BuildGraphResponseHeaders(e.Session.HttpClient.Request, requestId, requestDate);
4747

4848
if (e.PluginData.TryGetValue(nameof(RateLimitingPlugin), out var pluginData) &&
49-
pluginData is List<HttpHeader> rateLimitingHeaders)
49+
pluginData is List<MockResponseHeader> rateLimitingHeaders)
5050
{
5151
ProxyUtils.MergeHeaders(headers, rateLimitingHeaders);
5252
}
5353

54-
var headersDictionary = headers.ToDictionary(h => h.Name, h => h.Value);
55-
5654
var mockResponse = GetMatchingMockResponse(request, e.Session.HttpClient.Request.RequestUri);
5755
if (mockResponse == null)
5856
{
5957
response = new GraphBatchResponsePayloadResponse
6058
{
6159
Id = request.Id,
6260
Status = (int)HttpStatusCode.BadGateway,
63-
Headers = headersDictionary.Select(x => new KeyValuePair<string, string>(x.Key, x.Value)).ToList(),
61+
Headers = headers.ToList(),
6462
Body = new GraphBatchResponsePayloadResponseBody
6563
{
6664
Error = new GraphBatchResponsePayloadResponseBodyError
@@ -84,17 +82,17 @@ protected override async Task OnRequest(object? sender, ProxyRequestArgs e)
8482

8583
if (mockResponse.Response?.Headers is not null)
8684
{
87-
//Add all the mocked headers into the response we want
88-
foreach (var kvp in mockResponse.Response.Headers)
85+
// add all the mocked headers into the response we want
86+
foreach (var header in mockResponse.Response.Headers)
8987
{
90-
headersDictionary.Add(kvp.Key, kvp.Value);
88+
headers.Add(header);
9189
}
9290
}
9391

9492
// default the content type to application/json unless set in the mock response
95-
if (!headersDictionary.Any(h => h.Key.Equals("content-type", StringComparison.OrdinalIgnoreCase)))
93+
if (!headers.Any(h => h.Name.Equals("content-type", StringComparison.OrdinalIgnoreCase)))
9694
{
97-
headersDictionary.Add("content-type", "application/json");
95+
headers.Add(new("content-type", "application/json"));
9896
}
9997

10098
if (mockResponse.Response?.Body is not null)
@@ -129,7 +127,7 @@ protected override async Task OnRequest(object? sender, ProxyRequestArgs e)
129127
{
130128
Id = request.Id,
131129
Status = (int)statusCode,
132-
Headers = headersDictionary.Select(x => new KeyValuePair<string, string>(x.Key, x.Value)).ToList(),
130+
Headers = headers.ToList(),
133131
Body = body
134132
};
135133

@@ -146,7 +144,7 @@ protected override async Task OnRequest(object? sender, ProxyRequestArgs e)
146144
{
147145
Responses = responses.ToArray()
148146
};
149-
e.Session.GenericResponse(JsonSerializer.Serialize(batchResponse), HttpStatusCode.OK, batchHeaders);
147+
e.Session.GenericResponse(JsonSerializer.Serialize(batchResponse), HttpStatusCode.OK, batchHeaders.Select(h => new HttpHeader(h.Name, h.Value)));
150148
_logger?.LogRequest([$"200 {e.Session.HttpClient.Request.RequestUri}"], MessageType.Mocked, new LoggingContext(e.Session));
151149
e.ResponseState.HasBeenSet = true;
152150
}

dev-proxy-plugins/MockResponses/MockResponsePlugin.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,20 +216,20 @@ private void ProcessMockResponse(ProxyRequestArgs e, MockResponse matchingRespon
216216

217217
if (matchingResponse.Response?.Headers is not null)
218218
{
219-
foreach (HttpHeader headerToAdd in matchingResponse.Response.Headers.Select(kvp => new HttpHeader(kvp.Key, kvp.Value)))
219+
foreach (var header in matchingResponse.Response.Headers)
220220
{
221-
headers.Add(headerToAdd);
221+
headers.Add(header);
222222
}
223223
}
224224

225225
// default the content type to application/json unless set in the mock response
226226
if (!headers.Any(h => h.Name.Equals("content-type", StringComparison.OrdinalIgnoreCase)))
227227
{
228-
headers.Add(new HttpHeader("content-type", "application/json"));
228+
headers.Add(new("content-type", "application/json"));
229229
}
230230

231231
if (e.PluginData.TryGetValue(nameof(RateLimitingPlugin), out var pluginData) &&
232-
pluginData is List<HttpHeader> rateLimitingHeaders)
232+
pluginData is List<MockResponseHeader> rateLimitingHeaders)
233233
{
234234
ProxyUtils.MergeHeaders(headers, rateLimitingHeaders);
235235
}
@@ -254,7 +254,7 @@ private void ProcessMockResponse(ProxyRequestArgs e, MockResponse matchingRespon
254254
else
255255
{
256256
var bodyBytes = File.ReadAllBytes(filePath);
257-
e.Session.GenericResponse(bodyBytes, statusCode, headers);
257+
e.Session.GenericResponse(bodyBytes, statusCode, headers.Select(h => new HttpHeader(h.Name, h.Value)));
258258
_logger?.LogRequest([$"{matchingResponse.Response.StatusCode ?? 200} {matchingResponse.Request?.Url}"], MessageType.Mocked, new LoggingContext(e.Session));
259259
return;
260260
}
@@ -264,7 +264,7 @@ private void ProcessMockResponse(ProxyRequestArgs e, MockResponse matchingRespon
264264
body = bodyString;
265265
}
266266
}
267-
e.Session.GenericResponse(body ?? string.Empty, statusCode, headers);
267+
e.Session.GenericResponse(body ?? string.Empty, statusCode, headers.Select(h => new HttpHeader(h.Name, h.Value)));
268268

269269
_logger?.LogRequest([$"{matchingResponse.Response?.StatusCode ?? 200} {matchingResponse.Request?.Url}"], MessageType.Mocked, new LoggingContext(e.Session));
270270
}

0 commit comments

Comments
 (0)