Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ public void RecordMatcherThrowsExceptionsWithDetails()
TestRecordingMismatchException exception = Assert.Throws<TestRecordingMismatchException>(() => matcher.FindMatch(requestEntry, entries));
Assert.Equal(
"Unable to find a record for the request HEAD http://localhost/" + Environment.NewLine +
"Remaining entry: http://remote-host" + Environment.NewLine +
"Method doesn't match, request <HEAD> record <PUT>" + Environment.NewLine +
"Uri doesn't match:" + Environment.NewLine +
" request <http://localhost/>" + Environment.NewLine +
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Azure.Core;
Expand Down Expand Up @@ -127,7 +127,7 @@ public virtual RecordEntry FindMatch(RecordEntry request, IList<RecordEntry> ent
}
}

throw new TestRecordingMismatchException(GenerateException(request, bestScoreEntry));
throw new TestRecordingMismatchException(GenerateException(request, bestScoreEntry, entries));
}

public virtual int CompareBodies(byte[] requestBody, byte[] recordBody, StringBuilder descriptionBuilder = null)
Expand Down Expand Up @@ -213,11 +213,19 @@ private string NormalizeUri(string uriToNormalize)
return req.ToUri().ToString();
}

private string GenerateException(RecordEntry request, RecordEntry bestScoreEntry)
private string GenerateException(RecordEntry request, RecordEntry bestScoreEntry, IList<RecordEntry> entries = null)
{
StringBuilder builder = new StringBuilder();
builder.AppendLine($"Unable to find a record for the request {request.RequestMethod} {request.RequestUri}");

if (entries != null)
{
foreach (var entry in entries)
{
builder.AppendLine($"Remaining entry: {entry.RequestUri}");
}
}

if (bestScoreEntry == null)
{
builder.AppendLine("No records to match.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public void Record(RecordEntry entry)
}
}

public RecordEntry Lookup(RecordEntry requestEntry, RecordMatcher matcher, IEnumerable<RecordedTestSanitizer> sanitizers, bool remove = true)
public RecordEntry Lookup(RecordEntry requestEntry, RecordMatcher matcher, IEnumerable<RecordedTestSanitizer> sanitizers, bool remove = true, string sessionId = null)
{
foreach (RecordedTestSanitizer sanitizer in sanitizers)
{
Expand All @@ -91,24 +91,19 @@ public RecordEntry Lookup(RecordEntry requestEntry, RecordMatcher matcher, IEnum
// normalize request body with STJ using relaxed escaping to match behavior when Deserializing from session files
RecordEntry.NormalizeJsonBody(requestEntry.Request);

lock (Entries)
RecordEntry entry = matcher.FindMatch(requestEntry, Entries);
if (remove)
{
RecordEntry entry = matcher.FindMatch(requestEntry, Entries);
if (remove)
{
Entries.Remove(entry);
}

return entry;
Entries.Remove(entry);
DebugLogger.LogInformation($"We successfully matched and popped request URI {entry.RequestUri} for recordingId {sessionId}");
}

return entry;
}

public void Remove(RecordEntry entry)
{
lock (Entries)
{
Entries.Remove(entry);
}
Entries.Remove(entry);
}

public void Sanitize(RecordedTestSanitizer sanitizer)
Expand Down
99 changes: 66 additions & 33 deletions tools/test-proxy/Azure.Sdk.Tools.TestProxy/RecordingHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ public async Task StartPlaybackAsync(string sessionId, HttpResponse outgoingResp
{
Path = path
};
DebugLogger.LogInformation($"Recording file {assetsPath} is associated with recording id {id}");
}

if (!PlaybackSessions.TryAdd(id, session))
Expand All @@ -410,6 +411,7 @@ public async Task StartPlaybackAsync(string sessionId, HttpResponse outgoingResp
// Write to the response
await outgoingResponse.WriteAsync(json);


DebugLogger.LogTrace($"PLAYBACK START END {id}.");
}

Expand Down Expand Up @@ -470,50 +472,53 @@ public async Task HandlePlaybackRequest(string recordingId, HttpRequest incoming

var entry = (await CreateEntryAsync(incomingRequest).ConfigureAwait(false)).Item1;

// Session may be removed later, but only after response has been fully written
var match = session.Session.Lookup(entry, session.CustomMatcher ?? Matcher, sanitizers, remove: false);

foreach (ResponseTransform transform in Transforms.Concat(session.AdditionalTransforms))
lock (session.Session.Entries)
{
transform.Transform(incomingRequest, match);
}
// Session may be removed later, but only after response has been fully written
var match = session.Session.Lookup(entry, session.CustomMatcher ?? Matcher, sanitizers, remove: false, sessionId: recordingId);

outgoingResponse.StatusCode = match.StatusCode;
foreach (ResponseTransform transform in Transforms.Concat(session.AdditionalTransforms))
{
transform.Transform(incomingRequest, match);
}

foreach (var header in match.Response.Headers)
{
outgoingResponse.Headers.Add(header.Key, header.Value.ToArray());
}
outgoingResponse.StatusCode = match.StatusCode;

outgoingResponse.Headers.Remove("Transfer-Encoding");
foreach (var header in match.Response.Headers)
{
outgoingResponse.Headers.Add(header.Key, header.Value.ToArray());
}

if (match.Response.Body?.Length > 0)
{
var bodyData = CompressionUtilities.CompressBody(match.Response.Body, match.Response.Headers);
outgoingResponse.Headers.Remove("Transfer-Encoding");

if (match.Response.Headers.ContainsKey("Content-Length"))
if (match.Response.Body?.Length > 0)
{
outgoingResponse.ContentLength = bodyData.Length;
}
var bodyData = CompressionUtilities.CompressBody(match.Response.Body, match.Response.Headers);

await WriteBodyBytes(bodyData, session.PlaybackResponseTime, outgoingResponse);
}
if (match.Response.Headers.ContainsKey("Content-Length"))
{
outgoingResponse.ContentLength = bodyData.Length;
}

Interlocked.Increment(ref Startup.RequestsPlayedBack);
WriteBodyBytes(bodyData, session.PlaybackResponseTime, outgoingResponse);
}

// Only remove session once body has been written, to minimize probability client retries but test-proxy has already removed the session
var remove = true;
Interlocked.Increment(ref Startup.RequestsPlayedBack);

// If request contains "x-recording-remove: false", then request is not removed from session after playback.
// Used by perf tests to play back the same request multiple times.
if (incomingRequest.Headers.TryGetValue("x-recording-remove", out var removeHeader))
{
remove = bool.Parse(removeHeader);
}
// Only remove session once body has been written, to minimize probability client retries but test-proxy has already removed the session
var remove = true;

if (remove)
{
session.Session.Remove(match);
// If request contains "x-recording-remove: false", then request is not removed from session after playback.
// Used by perf tests to play back the same request multiple times.
if (incomingRequest.Headers.TryGetValue("x-recording-remove", out var removeHeader))
{
remove = bool.Parse(removeHeader);
}

if (remove)
{
session.Session.Remove(match);
}
}
}

Expand Down Expand Up @@ -543,7 +548,35 @@ public byte[][] GetBatches(byte[] bodyData, int batchCount)
return batches;
}

public async Task WriteBodyBytes(byte[] bodyData, int playbackResponseTime, HttpResponse outgoingResponse)
public void WriteBodyBytes(byte[] bodyData, int playbackResponseTime, HttpResponse outgoingResponse)
{
if (playbackResponseTime > 0)
{
int batchCount = 10;
int sleepLength = playbackResponseTime / batchCount;

byte[][] chunks = GetBatches(bodyData, batchCount);

for (int i = 0; i < chunks.Length; i++)
{
var chunk = chunks[i];

outgoingResponse.Body.Write(chunk, 0, chunk.Length);

if (i != chunks.Length - 1)
{
Thread.Sleep(sleepLength);
}
}

}
else
{
outgoingResponse.Body.Write(bodyData, 0, bodyData.Length);
}
}

public async Task WriteBodyBytesAsync(byte[] bodyData, int playbackResponseTime, HttpResponse outgoingResponse)
{
if (playbackResponseTime > 0)
{
Expand Down
5 changes: 5 additions & 0 deletions tools/test-proxy/Azure.Sdk.Tools.TestProxy/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ public void ConfigureServices(IServiceCollection services)
});
});

services.Configure<KestrelServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});

services.AddControllers(options =>
{
options.InputFormatters.Add(new EmptyBodyFormatter());
Expand Down