<commit_before>using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using NLog;
using Thinktecture.Relay.Server.OnPremise;
using Thinktecture.Relay.Server.SignalR;

namespace Thinktecture.Relay.Server.Http
{
	internal class OnPremiseRequestBuilder : IOnPremiseRequestBuilder
	{
		private static readonly string[] _ignoredHeaders = { "Host", "Connection" };

		private readonly IPostDataTemporaryStore _postDataTemporaryStore;
		private readonly ILogger _logger;

		public OnPremiseRequestBuilder(ILogger logger, IPostDataTemporaryStore postDataTemporaryStore)
		{
			_postDataTemporaryStore = postDataTemporaryStore ?? throw new ArgumentNullException(nameof(postDataTemporaryStore));
			_logger = logger;
		}

		public async Task<IOnPremiseConnectorRequest> BuildFromHttpRequest(HttpRequestMessage message, Guid originId, string pathWithoutUserName)
		{
			var request = new OnPremiseConnectorRequest
			{
				RequestId = Guid.NewGuid().ToString(),
				HttpMethod = message.Method.Method,
				Url = pathWithoutUserName + message.RequestUri.Query,
				HttpHeaders = message.Headers.ToDictionary(kvp => kvp.Key, kvp => CombineMultipleHttpHeaderValuesIntoOneCommaSeperatedValue(kvp.Value), StringComparer.OrdinalIgnoreCase),
				OriginId = originId,
				RequestStarted = DateTime.UtcNow
			};

			if (message.Content.Headers.ContentLength.GetValueOrDefault(0x10000) >= 0x10000)
			{
				var contentStream = await message.Content.ReadAsStreamAsync().ConfigureAwait(false);

				using (var storeStream = _postDataTemporaryStore.CreateRequestStream(request.RequestId))
				{
					await contentStream.CopyToAsync(storeStream).ConfigureAwait(false);
					if (storeStream.Length < 0x10000)
					{
						if (storeStream.Length == 0)
						{
							// no body available (e.g. GET request)
						}
						else
						{
							// the body is small enough to be used directly
							request.Body = new byte[storeStream.Length];
							storeStream.Position = 0;
							await storeStream.ReadAsync(request.Body, 0, (int)storeStream.Length).ConfigureAwait(false);
						}

						// TODO delete obsolete file now
					}
					else
					{
						// a length of 0 indicates that there is a larger body available on the server
						request.Body = new byte[0];
					}

					request.ContentLength = storeStream.Length;
				}
			}


			request.HttpHeaders = message.Headers
				.Union(message.Content.Headers)
				.Where(kvp => _ignoredHeaders.All(name => name != kvp.Key))
				.Select(kvp => new { Name = kvp.Key, Value = CombineMultipleHttpHeaderValuesIntoOneCommaSeperatedValue(kvp.Value) })
				.ToDictionary(header => header.Name, header => header.Value);

			return request;
		}

		internal string CombineMultipleHttpHeaderValuesIntoOneCommaSeperatedValue(IEnumerable<string> headers)
		{
			// HTTP RFC2616 says, that multiple headers can be combined into a comma-separated single header
			return headers.Aggregate(String.Empty, (s, v) => s + (String.IsNullOrWhiteSpace(s) ? String.Empty : ", ") + v);
		}
	}
}<commit_msg>Fixed issue that discarded small POST/PUT/PATCH request bodies<commit_after>using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using NLog;
using Thinktecture.Relay.Server.OnPremise;
using Thinktecture.Relay.Server.SignalR;

namespace Thinktecture.Relay.Server.Http
{
	internal class OnPremiseRequestBuilder : IOnPremiseRequestBuilder
	{
		private static readonly string[] _ignoredHeaders = { "Host", "Connection" };

		private readonly IPostDataTemporaryStore _postDataTemporaryStore;
		private readonly ILogger _logger;

		public OnPremiseRequestBuilder(ILogger logger, IPostDataTemporaryStore postDataTemporaryStore)
		{
			_postDataTemporaryStore = postDataTemporaryStore ?? throw new ArgumentNullException(nameof(postDataTemporaryStore));
			_logger = logger;
		}

		public async Task<IOnPremiseConnectorRequest> BuildFromHttpRequest(HttpRequestMessage message, Guid originId, string pathWithoutUserName)
		{
			var request = new OnPremiseConnectorRequest
			{
				RequestId = Guid.NewGuid().ToString(),
				HttpMethod = message.Method.Method,
				Url = pathWithoutUserName + message.RequestUri.Query,
				HttpHeaders = message.Headers.ToDictionary(kvp => kvp.Key, kvp => CombineMultipleHttpHeaderValuesIntoOneCommaSeperatedValue(kvp.Value), StringComparer.OrdinalIgnoreCase),
				OriginId = originId,
				RequestStarted = DateTime.UtcNow
			};

			if (message.Content.Headers.ContentLength.GetValueOrDefault(0x10000) >= 0x10000)
			{
				var contentStream = await message.Content.ReadAsStreamAsync().ConfigureAwait(false);

				using (var storeStream = _postDataTemporaryStore.CreateRequestStream(request.RequestId))
				{
					await contentStream.CopyToAsync(storeStream).ConfigureAwait(false);
					if (storeStream.Length < 0x10000)
					{
						if (storeStream.Length == 0)
						{
							// no body available (e.g. GET request)
						}
						else
						{
							// the body is small enough to be used directly
							request.Body = new byte[storeStream.Length];
							storeStream.Position = 0;
							await storeStream.ReadAsync(request.Body, 0, (int)storeStream.Length).ConfigureAwait(false);
						}

						// TODO delete obsolete file now
					}
					else
					{
						// a length of 0 indicates that there is a larger body available on the server
						request.Body = new byte[0];
					}

					request.ContentLength = storeStream.Length;
				}
			}
			else if (message.Content.Headers.ContentLength.GetValueOrDefault(0) > 0)
			{
				// we have a body, and it is small enough to be transmitted directly
				var contentStream = await message.Content.ReadAsStreamAsync().ConfigureAwait(false);

				// ReSharper disable once PossibleInvalidOperationException
				request.Body = new byte[message.Content.Headers.ContentLength.Value];
				await contentStream.ReadAsync(request.Body, 0, (int)message.Content.Headers.ContentLength.Value).ConfigureAwait(false);
			}

			request.HttpHeaders = message.Headers
				.Union(message.Content.Headers)
				.Where(kvp => _ignoredHeaders.All(name => name != kvp.Key))
				.Select(kvp => new { Name = kvp.Key, Value = CombineMultipleHttpHeaderValuesIntoOneCommaSeperatedValue(kvp.Value) })
				.ToDictionary(header => header.Name, header => header.Value);

			return request;
		}

		internal string CombineMultipleHttpHeaderValuesIntoOneCommaSeperatedValue(IEnumerable<string> headers)
		{
			// HTTP RFC2616 says, that multiple headers can be combined into a comma-separated single header
			return headers.Aggregate(String.Empty, (s, v) => s + (String.IsNullOrWhiteSpace(s) ? String.Empty : ", ") + v);
		}
	}
}
{'fraction_non_alphanumeric': 0.09374533512464547, 'fraction_numerical': 0.00850873264666368, 'mean_word_length': 4.087319665907366, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 26, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2557315', 'n_tokens_mistral': 2411, 'n_tokens_neox': 1942, 'n_words': 427}