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
9 changes: 9 additions & 0 deletions src/Facility.CodeGen.CSharp/CSharpGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ public static int GenerateCSharp(CSharpGeneratorSettings settings) =>
/// </summary>
public bool UseNullableReferences { get; set; }

/// <summary>
/// True if the code should compress HTTP requests.
/// </summary>
public bool CompressRequests { get; set; }

/// <summary>
/// True if C# names should automatically use PascalCase instead of snake case.
/// </summary>
Expand Down Expand Up @@ -132,6 +137,7 @@ public override void ApplySettings(FileGeneratorSettings settings)
NamespaceName = csharpSettings.NamespaceName;
DefaultNamespaceName = csharpSettings.DefaultNamespaceName;
UseNullableReferences = csharpSettings.UseNullableReferences;
CompressRequests = csharpSettings.CompressRequests;
FixSnakeCase = csharpSettings.FixSnakeCase;
SupportMessagePack = csharpSettings.SupportMessagePack;
SupportJsonSourceGeneration = csharpSettings.SupportJsonSourceGeneration;
Expand Down Expand Up @@ -1159,6 +1165,9 @@ private CodeGenFile GenerateHttpClient(HttpServiceInfo httpServiceInfo, Context
code.WriteLine($"BaseUri = new Uri({CSharpUtility.CreateString(url)}),");

WriteContentSerializerPropertyInitializer(code, fullServiceName);

if (CompressRequests)
code.WriteLine("CompressRequests = true,");
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/Facility.CodeGen.CSharp/CSharpGeneratorSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public sealed class CSharpGeneratorSettings : FileGeneratorSettings
/// </summary>
public bool UseNullableReferences { get; set; }

/// <summary>
/// True if the code should compress HTTP requests.
/// </summary>
public bool CompressRequests { get; set; }

/// <summary>
/// True if C# names should automatically use PascalCase instead of snake case.
/// </summary>
Expand Down
68 changes: 68 additions & 0 deletions src/Facility.Core/Http/HttpClientService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.IO.Compression;
using System.Net;
using System.Net.Http.Headers;
using System.Net.ServerSentEvents;
using System.Runtime.CompilerServices;
Expand All @@ -20,6 +22,7 @@ protected HttpClientService(HttpClientServiceSettings? settings, HttpClientServi

m_httpClient = settings.HttpClient ?? s_defaultHttpClient;
m_aspects = settings.Aspects;
m_enableRequestCompression = settings.CompressRequests ?? defaults.CompressRequests;
m_synchronous = settings.Synchronous;
m_skipRequestValidation = settings.SkipRequestValidation;
m_skipResponseValidation = settings.SkipResponseValidation;
Expand Down Expand Up @@ -101,6 +104,8 @@ protected HttpClientService(HttpClientServiceSettings? settings, Uri? defaultBas
{
var contentType = mapping.RequestBodyContentType ?? requestHeaders?.GetContentType();
httpRequest.Content = GetHttpContentSerializer(requestBody.GetType()).CreateHttpContent(requestBody, contentType);
if (m_enableRequestCompression)
httpRequest.Content = new CompressingHttpContent(httpRequest.Content);
if (m_disableChunkedTransfer)
await httpRequest.Content.LoadIntoBufferAsync().ConfigureAwait(false);
}
Expand Down Expand Up @@ -468,10 +473,73 @@ private HttpContentSerializer GetHttpContentSerializer(Type objectType) =>
HttpServiceUtility.UsesTextSerializer(objectType) ? TextSerializer :
ContentSerializer;

private sealed class CompressingHttpContent : HttpContent
{
public CompressingHttpContent(HttpContent baseContent)
{
m_baseContent = baseContent;
foreach (var header in baseContent.Headers)
{
// remove Content-Length header, if present, as it will change
if (!header.Key.Equals("Content-Length", StringComparison.OrdinalIgnoreCase))
Headers.TryAddWithoutValidation(header.Key, header.Value);
}

Headers.ContentEncoding.Clear();
Headers.ContentEncoding.Add("gzip");
}

protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) =>
DoSerializeToStreamAsync(stream, CancellationToken.None);

#if NET8_0_OR_GREATER
protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cancellationToken) =>
DoSerializeToStreamAsync(stream, cancellationToken);
#endif

private async Task DoSerializeToStreamAsync(Stream stream, CancellationToken cancellationToken)
{
#if NET5_0_OR_GREATER
using var baseContentStream = await m_baseContent.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
#else
using var baseContentStream = await m_baseContent.ReadAsStreamAsync().ConfigureAwait(false);
#endif
using var gzipStream = new GZipStream(stream, CompressionLevel.Optimal, leaveOpen: true);
using var bufferedStream = new BufferedStream(gzipStream, 8192);
#if NETCOREAPP2_1_OR_GREATER
await baseContentStream.CopyToAsync(bufferedStream, cancellationToken).ConfigureAwait(false);
#else
await baseContentStream.CopyToAsync(bufferedStream, 8192, cancellationToken).ConfigureAwait(false);
#endif
}

protected override bool TryComputeLength(out long length)
{
length = -1L;
return false;
}

protected override void Dispose(bool disposing)
{
try
{
if (disposing)
m_baseContent.Dispose();
}
finally
{
base.Dispose(disposing);
}
}

private readonly HttpContent m_baseContent;
}

private static readonly HttpClient s_defaultHttpClient = HttpServiceUtility.CreateHttpClient();

private readonly HttpClient m_httpClient;
private readonly IReadOnlyList<HttpClientServiceAspect>? m_aspects;
private readonly bool m_enableRequestCompression;
private readonly bool m_synchronous;
private readonly bool m_skipRequestValidation;
private readonly bool m_skipResponseValidation;
Expand Down
5 changes: 5 additions & 0 deletions src/Facility.Core/Http/HttpClientServiceDefaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ public sealed class HttpClientServiceDefaults
/// The default content serializer.
/// </summary>
public HttpContentSerializer? ContentSerializer { get; set; }

/// <summary>
/// True to compress all requests by default.
/// </summary>
public bool CompressRequests { get; set; }
}
7 changes: 7 additions & 0 deletions src/Facility.Core/Http/HttpClientServiceSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ public sealed class HttpClientServiceSettings
/// </summary>
public HttpContentSerializer? TextSerializer { get; set; }

/// <summary>
/// True to compress HTTP request bodies; false to not compress. The default is API-specific.
/// </summary>
/// <remarks>Request bodies will be compressed with <c>Content-Encoding: gzip</c>.</remarks>
public bool? CompressRequests { get; set; }

/// <summary>
/// True to disable chunked transfer encoding (default false).
/// </summary>
Expand Down Expand Up @@ -66,6 +72,7 @@ public sealed class HttpClientServiceSettings
ContentSerializer = ContentSerializer,
BytesSerializer = BytesSerializer,
TextSerializer = TextSerializer,
CompressRequests = CompressRequests,
DisableChunkedTransfer = DisableChunkedTransfer,
Aspects = Aspects?.ToList(),
Synchronous = Synchronous,
Expand Down
3 changes: 3 additions & 0 deletions src/fsdgencsharp/FsdGenCSharpApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public sealed class FsdGenCSharpApp : CodeGeneratorApp
" The namespace used by the generated C# if not specified by FSD.",
" --nullable",
" Use nullable reference syntax in the generated C#.",
" --compress-requests",
" Compress HTTP requests by default in the generated C#.",
" --fix-snake-case",
" Replace snake_case with PascalCase.",
" --msgpack",
Expand All @@ -43,6 +45,7 @@ protected override FileGeneratorSettings CreateSettings(ArgsReader args) =>
NamespaceName = args.ReadOption("namespace"),
DefaultNamespaceName = args.ReadOption("default-namespace"),
UseNullableReferences = args.ReadFlag("nullable"),
CompressRequests = args.ReadFlag("compress-requests"),
FixSnakeCase = args.ReadFlag("fix-snake-case"),
SupportMessagePack = args.ReadFlag("msgpack"),
SupportJsonSourceGeneration = args.ReadFlag("json-source-gen"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public async Task CloneSettings()
ContentSerializer = HttpContentSerializer.Create(SystemTextJsonServiceSerializer.Instance),
BytesSerializer = BytesHttpContentSerializer.Instance,
TextSerializer = TextHttpContentSerializer.Instance,
CompressRequests = true,
DisableChunkedTransfer = true,
Aspects = [],
Synchronous = true,
Expand Down