Skip to content

Commit

Permalink
Avoid roundtrip to string by deserializing directly from the HTTP res…
Browse files Browse the repository at this point in the history
…ponse stream
  • Loading branch information
0xced committed Mar 7, 2024
1 parent 8ed97ce commit 6a87013
Show file tree
Hide file tree
Showing 14 changed files with 129 additions and 140 deletions.
17 changes: 0 additions & 17 deletions src/Docker.DotNet/DockerApiResponse.cs

This file was deleted.

73 changes: 61 additions & 12 deletions src/Docker.DotNet/DockerClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -157,49 +158,92 @@ public void Dispose()
_client.Dispose();
}

internal Task<DockerApiResponse> MakeRequestAsync(
internal Task MakeRequestAsync(
IEnumerable<ApiResponseErrorHandlingDelegate> errorHandlers,
HttpMethod method,
string path,
CancellationToken token)
{
return MakeRequestAsync(errorHandlers, method, path, null, null, token);
return MakeRequestAsync<NoContent>(errorHandlers, method, path, null, null, token);
}

internal Task<DockerApiResponse> MakeRequestAsync(
internal Task<T> MakeRequestAsync<T>(
IEnumerable<ApiResponseErrorHandlingDelegate> errorHandlers,
HttpMethod method,
string path,
CancellationToken token)
{
return MakeRequestAsync<T>(errorHandlers, method, path, null, null, token);
}

internal Task MakeRequestAsync(
IEnumerable<ApiResponseErrorHandlingDelegate> errorHandlers,
HttpMethod method,
string path,
IQueryString queryString,
CancellationToken token)
{
return MakeRequestAsync<NoContent>(errorHandlers, method, path, queryString, null, token);
}

internal Task<T> MakeRequestAsync<T>(
IEnumerable<ApiResponseErrorHandlingDelegate> errorHandlers,
HttpMethod method,
string path,
IQueryString queryString,
CancellationToken token)
{
return MakeRequestAsync<T>(errorHandlers, method, path, queryString, null, token);
}

internal Task MakeRequestAsync(
IEnumerable<ApiResponseErrorHandlingDelegate> errorHandlers,
HttpMethod method,
string path,
IQueryString queryString,
IRequestContent body,
CancellationToken token)
{
return MakeRequestAsync<NoContent>(errorHandlers, method, path, queryString, body, null, token);
}

internal Task<T> MakeRequestAsync<T>(
IEnumerable<ApiResponseErrorHandlingDelegate> errorHandlers,
HttpMethod method,
string path,
IQueryString queryString,
IRequestContent body,
CancellationToken token)
{
return MakeRequestAsync(errorHandlers, method, path, queryString, null, token);
return MakeRequestAsync<T>(errorHandlers, method, path, queryString, body, null, token);
}

internal Task<DockerApiResponse> MakeRequestAsync(
internal Task<T> MakeRequestAsync<T>(
IEnumerable<ApiResponseErrorHandlingDelegate> errorHandlers,
HttpMethod method,
string path,
IQueryString queryString,
IRequestContent body,
IDictionary<string, string> headers,
CancellationToken token)
{
return MakeRequestAsync(errorHandlers, method, path, queryString, body, null, token);
return MakeRequestAsync<T>(errorHandlers, method, path, queryString, body, headers, DefaultTimeout, token);
}

internal Task<DockerApiResponse> MakeRequestAsync(
internal Task MakeRequestAsync(
IEnumerable<ApiResponseErrorHandlingDelegate> errorHandlers,
HttpMethod method,
string path,
IQueryString queryString,
IRequestContent body,
IDictionary<string, string> headers,
TimeSpan timeout,
CancellationToken token)
{
return MakeRequestAsync(errorHandlers, method, path, queryString, body, headers, DefaultTimeout, token);
return MakeRequestAsync<NoContent>(errorHandlers, method, path, queryString, body, headers, timeout, token);
}

internal async Task<DockerApiResponse> MakeRequestAsync(
internal async Task<T> MakeRequestAsync<T>(
IEnumerable<ApiResponseErrorHandlingDelegate> errorHandlers,
HttpMethod method,
string path,
Expand All @@ -217,10 +261,13 @@ public void Dispose()
await HandleIfErrorResponseAsync(response.StatusCode, response, errorHandlers)
.ConfigureAwait(false);

var responseBody = await response.Content.ReadAsStringAsync()
.ConfigureAwait(false);
if (typeof(T) == typeof(NoContent))
{
return default;
}

return new DockerApiResponse(response.StatusCode, responseBody);
return await JsonSerializer.DeserializeAsync<T>(response.Content, token)
.ConfigureAwait(false);
}
}

Expand Down Expand Up @@ -469,6 +516,8 @@ private async Task HandleIfErrorResponseAsync(HttpStatusCode statusCode, HttpRes
throw new DockerApiException(statusCode, responseBody);
}
}

private struct NoContent {}
}

internal delegate void ApiResponseErrorHandlingDelegate(HttpStatusCode statusCode, string responseBody);
Expand Down
9 changes: 3 additions & 6 deletions src/Docker.DotNet/Endpoints/ConfigsOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ internal ConfigOperations(DockerClient client)

async Task<IList<SwarmConfig>> IConfigOperations.ListConfigsAsync(CancellationToken cancellationToken)
{
var response = await this._client.MakeRequestAsync(this._client.NoErrorHandlers, HttpMethod.Get, "configs", cancellationToken).ConfigureAwait(false);
return this._client.JsonSerializer.DeserializeObject<IList<SwarmConfig>>(response.Body);
return await this._client.MakeRequestAsync<IList<SwarmConfig>>(this._client.NoErrorHandlers, HttpMethod.Get, "configs", cancellationToken).ConfigureAwait(false);
}

async Task<SwarmCreateConfigResponse> IConfigOperations.CreateConfigAsync(SwarmCreateConfigParameters body, CancellationToken cancellationToken)
Expand All @@ -30,8 +29,7 @@ async Task<SwarmCreateConfigResponse> IConfigOperations.CreateConfigAsync(SwarmC
}

var data = new JsonRequestContent<SwarmConfigSpec>(body.Config, this._client.JsonSerializer);
var response = await this._client.MakeRequestAsync(this._client.NoErrorHandlers, HttpMethod.Post, "configs/create", null, data, cancellationToken).ConfigureAwait(false);
return this._client.JsonSerializer.DeserializeObject<SwarmCreateConfigResponse>(response.Body);
return await this._client.MakeRequestAsync<SwarmCreateConfigResponse>(this._client.NoErrorHandlers, HttpMethod.Post, "configs/create", null, data, cancellationToken).ConfigureAwait(false);
}

async Task<SwarmConfig> IConfigOperations.InspectConfigAsync(string id, CancellationToken cancellationToken)
Expand All @@ -41,8 +39,7 @@ async Task<SwarmConfig> IConfigOperations.InspectConfigAsync(string id, Cancella
throw new ArgumentNullException(nameof(id));
}

var response = await this._client.MakeRequestAsync(this._client.NoErrorHandlers, HttpMethod.Get, $"configs/{id}", cancellationToken).ConfigureAwait(false);
return this._client.JsonSerializer.DeserializeObject<SwarmConfig>(response.Body);
return await this._client.MakeRequestAsync<SwarmConfig>(this._client.NoErrorHandlers, HttpMethod.Get, $"configs/{id}", cancellationToken).ConfigureAwait(false);
}

Task IConfigOperations.RemoveConfigAsync(string id, CancellationToken cancellationToken)
Expand Down
39 changes: 15 additions & 24 deletions src/Docker.DotNet/Endpoints/ContainerOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -43,8 +42,7 @@ public async Task<IList<ContainerListResponse>> ListContainersAsync(ContainersLi
}

IQueryString queryParameters = new QueryString<ContainersListParameters>(parameters);
var response = await this._client.MakeRequestAsync(this._client.NoErrorHandlers, HttpMethod.Get, "containers/json", queryParameters, cancellationToken).ConfigureAwait(false);
return this._client.JsonSerializer.DeserializeObject<ContainerListResponse[]>(response.Body);
return await this._client.MakeRequestAsync<ContainerListResponse[]>(this._client.NoErrorHandlers, HttpMethod.Get, "containers/json", queryParameters, cancellationToken).ConfigureAwait(false);
}

public async Task<CreateContainerResponse> CreateContainerAsync(CreateContainerParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
Expand All @@ -62,8 +60,7 @@ public async Task<CreateContainerResponse> CreateContainerAsync(CreateContainerP
}

var data = new JsonRequestContent<CreateContainerParameters>(parameters, this._client.JsonSerializer);
var response = await this._client.MakeRequestAsync(new[] { NoSuchImageHandler }, HttpMethod.Post, "containers/create", qs, data, cancellationToken).ConfigureAwait(false);
return this._client.JsonSerializer.DeserializeObject<CreateContainerResponse>(response.Body);
return await this._client.MakeRequestAsync<CreateContainerResponse>(new[] { NoSuchImageHandler }, HttpMethod.Post, "containers/create", qs, data, cancellationToken).ConfigureAwait(false);
}

public async Task<ContainerInspectResponse> InspectContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken))
Expand All @@ -73,8 +70,7 @@ public async Task<ContainerInspectResponse> InspectContainerAsync(string id, Can
throw new ArgumentNullException(nameof(id));
}

var response = await this._client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/json", cancellationToken).ConfigureAwait(false);
return this._client.JsonSerializer.DeserializeObject<ContainerInspectResponse>(response.Body);
return await this._client.MakeRequestAsync<ContainerInspectResponse>(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/json", cancellationToken).ConfigureAwait(false);
}

public async Task<ContainerProcessesResponse> ListProcessesAsync(string id, ContainerListProcessesParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
Expand All @@ -90,8 +86,7 @@ public async Task<ContainerProcessesResponse> ListProcessesAsync(string id, Cont
}

IQueryString queryParameters = new QueryString<ContainerListProcessesParameters>(parameters);
var response = await this._client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/top", queryParameters, cancellationToken).ConfigureAwait(false);
return this._client.JsonSerializer.DeserializeObject<ContainerProcessesResponse>(response.Body);
return await this._client.MakeRequestAsync<ContainerProcessesResponse>(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/top", queryParameters, cancellationToken).ConfigureAwait(false);
}

public Task<Stream> GetContainerLogsAsync(string id, ContainerLogsParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
Expand Down Expand Up @@ -145,8 +140,7 @@ public async Task<IList<ContainerFileSystemChangeResponse>> InspectChangesAsync(
throw new ArgumentNullException(nameof(id));
}

var response = await this._client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/changes", cancellationToken).ConfigureAwait(false);
return this._client.JsonSerializer.DeserializeObject<ContainerFileSystemChangeResponse[]>(response.Body);
return await this._client.MakeRequestAsync<ContainerFileSystemChangeResponse[]>(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/changes", cancellationToken).ConfigureAwait(false);
}

public Task<Stream> ExportContainerAsync(string id, CancellationToken cancellationToken)
Expand Down Expand Up @@ -208,8 +202,9 @@ public async Task<bool> StartContainerAsync(string id, ContainerStartParameters
}

var queryParams = parameters == null ? null : new QueryString<ContainerStartParameters>(parameters);
var response = await this._client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/start", queryParams, cancellationToken).ConfigureAwait(false);
return response.StatusCode != HttpStatusCode.NotModified;
bool? result = null;
await this._client.MakeRequestAsync(new[] { NoSuchContainerHandler, (statusCode, _) => result = statusCode != HttpStatusCode.NotModified }, HttpMethod.Post, $"containers/{id}/start", queryParams, cancellationToken).ConfigureAwait(false);
return result ?? throw new InvalidOperationException();
}

public async Task<bool> StopContainerAsync(string id, ContainerStopParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
Expand All @@ -227,8 +222,9 @@ public async Task<bool> StopContainerAsync(string id, ContainerStopParameters pa
IQueryString queryParameters = new QueryString<ContainerStopParameters>(parameters);
// since specified wait timespan can be greater than HttpClient's default, we set the
// client timeout to infinite and provide a cancellation token.
var response = await this._client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/stop", queryParameters, null, null, TimeSpan.FromMilliseconds(Timeout.Infinite), cancellationToken).ConfigureAwait(false);
return response.StatusCode != HttpStatusCode.NotModified;
bool? result = null;
await this._client.MakeRequestAsync(new[] { NoSuchContainerHandler, (statusCode, _) => result = statusCode != HttpStatusCode.NotModified }, HttpMethod.Post, $"containers/{id}/stop", queryParameters, null, null, TimeSpan.FromMilliseconds(Timeout.Infinite), cancellationToken).ConfigureAwait(false);
return result ?? throw new InvalidOperationException();
}

public Task RestartContainerAsync(string id, ContainerRestartParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
Expand Down Expand Up @@ -327,8 +323,7 @@ public async Task<ContainerWaitResponse> WaitContainerAsync(string id, Cancellat
throw new ArgumentNullException(nameof(id));
}

var response = await this._client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/wait", null, null, null, TimeSpan.FromMilliseconds(Timeout.Infinite), cancellationToken).ConfigureAwait(false);
return this._client.JsonSerializer.DeserializeObject<ContainerWaitResponse>(response.Body);
return await this._client.MakeRequestAsync<ContainerWaitResponse>(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/wait", null, null, null, TimeSpan.FromMilliseconds(Timeout.Infinite), cancellationToken).ConfigureAwait(false);
}

public Task RemoveContainerAsync(string id, ContainerRemoveParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
Expand Down Expand Up @@ -367,9 +362,7 @@ public async Task<GetArchiveFromContainerResponse> GetArchiveFromContainerAsync(

var bytes = Convert.FromBase64String(statHeader);

var stat = Encoding.UTF8.GetString(bytes, 0, bytes.Length);

var pathStat = this._client.JsonSerializer.DeserializeObject<ContainerPathStatResponse>(stat);
var pathStat = this._client.JsonSerializer.DeserializeObject<ContainerPathStatResponse>(bytes);

return new GetArchiveFromContainerResponse
{
Expand Down Expand Up @@ -399,8 +392,7 @@ public Task ExtractArchiveToContainerAsync(string id, ContainerPathStatParameter
public async Task<ContainersPruneResponse> PruneContainersAsync(ContainersPruneParameters parameters, CancellationToken cancellationToken)
{
var queryParameters = parameters == null ? null : new QueryString<ContainersPruneParameters>(parameters);
var response = await this._client.MakeRequestAsync(this._client.NoErrorHandlers, HttpMethod.Post, "containers/prune", queryParameters, cancellationToken).ConfigureAwait(false);
return this._client.JsonSerializer.DeserializeObject<ContainersPruneResponse>(response.Body);
return await this._client.MakeRequestAsync<ContainersPruneResponse>(this._client.NoErrorHandlers, HttpMethod.Post, "containers/prune", queryParameters, cancellationToken).ConfigureAwait(false);
}

public async Task<ContainerUpdateResponse> UpdateContainerAsync(string id, ContainerUpdateParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
Expand All @@ -416,8 +408,7 @@ public async Task<ContainerUpdateResponse> UpdateContainerAsync(string id, Conta
}

var data = new JsonRequestContent<ContainerUpdateParameters>(parameters, this._client.JsonSerializer);
var response = await this._client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/update", null, data, cancellationToken);
return this._client.JsonSerializer.DeserializeObject<ContainerUpdateResponse>(response.Body);
return await this._client.MakeRequestAsync<ContainerUpdateResponse>(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/update", null, data, cancellationToken);
}
}
}
6 changes: 2 additions & 4 deletions src/Docker.DotNet/Endpoints/ExecOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ public async Task<ContainerExecCreateResponse> ExecCreateContainerAsync(string i
}

var data = new JsonRequestContent<ContainerExecCreateParameters>(parameters, this._client.JsonSerializer);
var response = await this._client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/exec", null, data, cancellationToken).ConfigureAwait(false);
return this._client.JsonSerializer.DeserializeObject<ContainerExecCreateResponse>(response.Body);
return await this._client.MakeRequestAsync<ContainerExecCreateResponse>(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/exec", null, data, cancellationToken).ConfigureAwait(false);
}

public async Task<ContainerExecInspectResponse> InspectContainerExecAsync(string id, CancellationToken cancellationToken)
Expand All @@ -48,8 +47,7 @@ public async Task<ContainerExecInspectResponse> InspectContainerExecAsync(string
throw new ArgumentNullException(nameof(id));
}

var response = await this._client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"exec/{id}/json", null, cancellationToken).ConfigureAwait(false);
return this._client.JsonSerializer.DeserializeObject<ContainerExecInspectResponse>(response.Body);
return await this._client.MakeRequestAsync<ContainerExecInspectResponse>(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"exec/{id}/json", null, cancellationToken).ConfigureAwait(false);
}

public Task ResizeContainerExecTtyAsync(string id, ContainerResizeParameters parameters, CancellationToken cancellationToken)
Expand Down
Loading

0 comments on commit 6a87013

Please sign in to comment.