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
170 changes: 14 additions & 156 deletions src/Dax.Formatter/Client/Http/DaxFormatterHttpClient.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
namespace Dax.Formatter.Client.Http
namespace Dax.Formatter.Client.Http
{
using Dax.Formatter.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
Expand All @@ -18,17 +17,11 @@ internal sealed class DaxFormatterHttpClient : IDaxFormatterHttpClient, IDisposa
private const string MediaTypeNamesApplicationJson = "application/json";
private const int DaxFormatterTimeoutSeconds = 60;

private readonly HashSet<HttpStatusCode> _locationChangedStatusCodes;
private readonly JsonSerializerOptions _serializerOptions;
private readonly SemaphoreSlim _initializeServiceUriSemaphore;
private readonly SemaphoreSlim _formatSemaphore;
private readonly HttpClient _httpClient;
private readonly string? _application;
private readonly string? _version;

private Uri? _daxTextFormatSingleServiceUri;
private Uri? _daxTextFormatMultiServiceUri;

public DaxFormatterHttpClient(string? application, string? version)
{
_application = application;
Expand All @@ -41,185 +34,50 @@ public DaxFormatterHttpClient(string? application, string? version)
_httpClient.DefaultRequestHeaders.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNamesApplicationJson));
_httpClient.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue(nameof(DecompressionMethods.GZip)));

_initializeServiceUriSemaphore = new SemaphoreSlim(1);
_formatSemaphore = new SemaphoreSlim(1);

_serializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web);
_serializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;

_locationChangedStatusCodes = new HashSet<HttpStatusCode>
{
HttpStatusCode.Moved,
HttpStatusCode.MovedPermanently,
HttpStatusCode.Found,
HttpStatusCode.Redirect,
HttpStatusCode.RedirectMethod,
HttpStatusCode.SeeOther,
HttpStatusCode.RedirectKeepVerb,
HttpStatusCode.TemporaryRedirect
};
}

public async Task<DaxFormatterResponse?> FormatAsync(DaxFormatterSingleRequest request, CancellationToken cancellationToken)
{
#if NETSTANDARD
await _formatSemaphore.WaitAsync().ConfigureAwait(false);
#elif NET6_0_OR_GREATER
await _formatSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
#endif
try
{
request.CallerApp = _application;
request.CallerVersion = _version;

var message = await FormatImplAsync(request, cancellationToken).ConfigureAwait(false);
var result = JsonSerializer.Deserialize<DaxFormatterResponse>(message, _serializerOptions);
request.CallerApp = _application;
request.CallerVersion = _version;

return result;
}
finally
{
_formatSemaphore.Release();
}
var message = await FormatImplAsync(request, cancellationToken).ConfigureAwait(false);
return JsonSerializer.Deserialize<DaxFormatterResponse>(message, _serializerOptions);
}

public async Task<IReadOnlyList<DaxFormatterResponse>> FormatAsync(DaxFormatterMultipleRequest request, CancellationToken cancellationToken)
{
#if NETSTANDARD
await _formatSemaphore.WaitAsync().ConfigureAwait(false);
#elif NET6_0_OR_GREATER
await _formatSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
#endif
try
{
request.CallerApp = _application;
request.CallerVersion = _version;

var message = await FormatImplAsync(request, cancellationToken).ConfigureAwait(false);
var result = JsonSerializer.Deserialize<IReadOnlyList<DaxFormatterResponse>>(message, _serializerOptions);
request.CallerApp = _application;
request.CallerVersion = _version;

return result ?? Array.Empty<DaxFormatterResponse>();
}
finally
{
_formatSemaphore.Release();
}
var message = await FormatImplAsync(request, cancellationToken).ConfigureAwait(false);
var result = JsonSerializer.Deserialize<IReadOnlyList<DaxFormatterResponse>>(message, _serializerOptions);
return result ?? Array.Empty<DaxFormatterResponse>();
}

private async Task<string> FormatImplAsync<T>(T request, CancellationToken cancellationToken) where T : DaxFormatterRequest
{
cancellationToken.ThrowIfCancellationRequested();

var json = JsonSerializer.Serialize(request, _serializerOptions);
var uri = await GetServiceUriAsync(request, cancellationToken).ConfigureAwait(false);
var uri = request.DaxTextFormatUri;

using var content = new StringContent(json, Encoding.UTF8, MediaTypeNamesApplicationJson);
using var response = await _httpClient.PostAsync(uri, content, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();

#if NETSTANDARD
using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
#elif NET6_0_OR_GREATER
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
return await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
#endif
using var reader = new StreamReader(stream);
var message = reader.ReadToEnd();

return message;
}

private async Task<Uri> GetServiceUriAsync(DaxFormatterRequest request, CancellationToken cancellationToken)
{
if (request is DaxFormatterMultipleRequest)
{
if (_daxTextFormatMultiServiceUri == null)
_daxTextFormatMultiServiceUri = await InitializeMultiServiceUriAsync().ConfigureAwait(false);

return _daxTextFormatMultiServiceUri;
}
else if (request is DaxFormatterSingleRequest)
{
if (_daxTextFormatSingleServiceUri == null)
_daxTextFormatSingleServiceUri = await InitializeSingleServiceUriAsync().ConfigureAwait(false);

return _daxTextFormatSingleServiceUri;
}
else
{
throw new NotSupportedException($"Uri not supported for { request.GetType().Name } request");
}

async Task<Uri> InitializeSingleServiceUriAsync()
{
if (_daxTextFormatSingleServiceUri == null)
{
await _initializeServiceUriSemaphore.WaitAsync(CancellationToken.None).ConfigureAwait(false);
try
{
if (_daxTextFormatSingleServiceUri == null)
{
using var response = await _httpClient.GetAsync(request.DaxTextFormatUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
var uri = request.DaxTextFormatUri;

if (_locationChangedStatusCodes.Contains(response.StatusCode) && response.Headers.Location != null)
{
uri = response.Headers.Location;
}
else
{
response.EnsureSuccessStatusCode();
}

_daxTextFormatSingleServiceUri = uri;
}
}
finally
{
_initializeServiceUriSemaphore.Release();
}
}

return _daxTextFormatSingleServiceUri;
}

async Task<Uri> InitializeMultiServiceUriAsync()
{
if (_daxTextFormatMultiServiceUri == null)
{
await _initializeServiceUriSemaphore.WaitAsync(CancellationToken.None).ConfigureAwait(false);
try
{
if (_daxTextFormatMultiServiceUri == null)
{
using var response = await _httpClient.GetAsync(request.DaxTextFormatUri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
var uri = request.DaxTextFormatUri;

if (_locationChangedStatusCodes.Contains(response.StatusCode) && response.Headers.Location != null)
{
uri = response.Headers.Location;
}
else
{
response.EnsureSuccessStatusCode();
}

_daxTextFormatMultiServiceUri = uri;
}
}
finally
{
_initializeServiceUriSemaphore.Release();
}
}

return _daxTextFormatMultiServiceUri;
}
}

public void Dispose()
{
_initializeServiceUriSemaphore.Dispose();
_formatSemaphore.Dispose();
_httpClient.Dispose();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ internal class DaxFormatterHttpClientMessageHandler : HttpClientHandler
{
public DaxFormatterHttpClientMessageHandler()
{
AllowAutoRedirect = false;
AutomaticDecompression = DecompressionMethods.GZip;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Dax.Formatter/Models/DaxFormatterMultipleRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal static DaxFormatterMultipleRequest CreateFrom(IEnumerable<string> expre
return request;
}

internal override Uri DaxTextFormatUri { get; } = new Uri("https://www.daxformatter.com/api/daxformatter/daxtextformatmulti");
internal override Uri DaxTextFormatUri { get; } = new Uri("https://api.daxformatter.com/api/daxtextformatmulti");

public List<string> Dax { get; set; } = new List<string>();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Dax.Formatter/Models/DaxFormatterSingleRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal static DaxFormatterSingleRequest CreateFrom(string expression)
return request;
}

internal override Uri DaxTextFormatUri { get; } = new Uri("https://www.daxformatter.com/api/daxformatter/daxtextformat");
internal override Uri DaxTextFormatUri { get; } = new Uri("https://api.daxformatter.com/api/daxtextformat");

public string? Dax { get; set; }
}
Expand Down
Loading