Skip to content
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
52 changes: 52 additions & 0 deletions src/SoapCore.Tests/HeadersHelperThreadingTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;
using System.ServiceModel.Channels;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace SoapCore.Tests
{
[TestClass]
public class HeadersHelperThreadingTest
{
[TestMethod]
public void GetSoapAction_IsThreadSafe()
{
// Arrange
const int totalOperations = 200_000;
var exceptions = new System.Collections.Concurrent.ConcurrentQueue<Exception>();

// Act
Parallel.For(0, totalOperations, (i, loopState) =>
{
try
{
var httpContext = new DefaultHttpContext();

// Add variability to the action to ensure the parsing logic is stressed
var action = $"http://test-uri.org/IService/TestAction{i % 100}";
httpContext.Request.Headers["Content-Type"] = $"application/soap+xml; charset=utf-8; action=\"{action}\"";
var message = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, "action");

// The ref message is modified by GetSoapAction, so we need to pass it in a way that can be updated.
var messageRef = new Message[1] { message };
var soapAction = HeadersHelper.GetSoapAction(httpContext, ref messageRef[0]);

// Assert
Assert.AreEqual(action, soapAction);
}
catch (Exception ex)
{
exceptions.Enqueue(ex);
loopState.Stop(); // Stop on first error to fail fast
}
});

// Assert
if (!exceptions.IsEmpty)
{
throw new AggregateException("One or more exceptions were thrown in worker threads.", exceptions);
}
}
}
}
74 changes: 43 additions & 31 deletions src/SoapCore/HeadersHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace SoapCore
{
internal static class HeadersHelper
public static class HeadersHelper
{
private static readonly char[] ContentTypeSeparators = new[] { ';' };

Expand Down Expand Up @@ -47,49 +47,61 @@ public static string GetSoapAction(HttpContext httpContext, ref Message message)
}

var buff = ArrayPool<Range>.Shared.Rent(nInstances);
var rangeSpan = new Span<Range>(buff);
var nItems = itemSpan.Split(rangeSpan, ContentTypeSeparators, StringSplitOptions.RemoveEmptyEntries);

for (int i = 0; i < nItems; i++)
try
{
var headerItem = itemSpan[rangeSpan[i]].TrimStart();
if (headerItem.Length < 6)
{
continue;
}
var rangeSpan = new Span<Range>(buff);
var nItems = itemSpan.Split(rangeSpan, ContentTypeSeparators, StringSplitOptions.RemoveEmptyEntries);
bool found = false;

if (!headerItem.StartsWith("action".AsSpan(), StringComparison.OrdinalIgnoreCase))
for (int i = 0; i < nItems; i++)
{
continue;
}
var headerItem = itemSpan[rangeSpan[i]].TrimStart();
if (headerItem.Length < 6)
{
continue;
}

headerItem = headerItem.Slice(6).TrimStart();
if (!headerItem.StartsWith("action".AsSpan(), StringComparison.OrdinalIgnoreCase))
{
continue;
}

if (headerItem[0] != '=')
{
continue;
}
headerItem = headerItem.Slice(6).TrimStart();

headerItem = headerItem.Slice(1).TrimStart();
if (headerItem.Length == 0 || headerItem[0] != '=')
{
continue;
}

if (headerItem[0] != '"')
{
continue;
}
headerItem = headerItem.Slice(1).TrimStart();

headerItem = headerItem.Slice(1).TrimStart();
if (headerItem.Length == 0 || headerItem[0] != '"')
{
continue;
}

var quoteIndex = headerItem.IndexOf('"');
if (quoteIndex < 0)
{
continue;
headerItem = headerItem.Slice(1).TrimStart();

var quoteIndex = headerItem.IndexOf('"');
if (quoteIndex < 0)
{
continue;
}

soapAction = headerItem.Slice(0, quoteIndex);
found = true;
break;
}

if (found)
{
break;
}
}
finally
{
ArrayPool<Range>.Shared.Return(buff);
soapAction = headerItem.Slice(0, quoteIndex);
}

ArrayPool<Range>.Shared.Return(buff);
}

#else
Expand Down
5 changes: 2 additions & 3 deletions src/SoapCore/SoapEndpointMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -537,10 +537,9 @@ private async Task<Message> ProcessMessage(Message requestMessage, SoapMessageEn
reader?.Dispose();
}

// Execute response message filters
foreach (var messageFilter in asyncMessageFilters.Reverse())
for (var i = asyncMessageFilters.Length - 1; i >= 0; i--)
{
await messageFilter.OnResponseExecuting(responseMessage);
await asyncMessageFilters[i].OnResponseExecuting(responseMessage);
}

SetHttpResponse(httpContext, responseMessage);
Expand Down
Loading