Skip to content

Commit 70d225a

Browse files
authored
fix: Add connection upgrade headers (#21)
1 parent 33dd1be commit 70d225a

File tree

14 files changed

+290
-244
lines changed

14 files changed

+290
-244
lines changed

src/Docker.DotNet/Docker.DotNet.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
1212
</ItemGroup>
1313
<ItemGroup Condition="$(TargetFrameworkIdentifier) == '.NETStandard'">
14-
<PackageReference Include="System.Buffers" Version="4.5.1" />
1514
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
1615
<PackageReference Include="System.Net.Http.Json" Version="8.0.1" />
1716
<PackageReference Include="System.Text.Json" Version="8.0.5" />

src/Docker.DotNet/DockerClient.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,20 @@ internal async Task<WriteClosableStream> MakeRequestForHijackedStreamAsync(
380380
TimeSpan timeout,
381381
CancellationToken cancellationToken)
382382
{
383+
// The Docker Engine API docs sounds like these headers are optional, but if they
384+
// aren't include in the request, the daemon doesn't set up the raw stream
385+
// correctly. Either the headers are always required, or they're necessary
386+
// specifically in Docker Desktop environments because of some internal communication
387+
// (using a proxy).
388+
389+
if (headers == null)
390+
{
391+
headers = new Dictionary<string, string>();
392+
}
393+
394+
headers.Add("Connection", "tcp");
395+
headers.Add("Upgrade", "Upgrade");
396+
383397
var response = await PrivateMakeRequestAsync(timeout, HttpCompletionOption.ResponseHeadersRead, method, path, queryString, headers, body, cancellationToken)
384398
.ConfigureAwait(false);
385399

@@ -455,7 +469,7 @@ private HttpRequestMessage PrepareRequest(HttpMethod method, string path, IQuery
455469

456470
private async Task HandleIfErrorResponseAsync(HttpStatusCode statusCode, HttpResponseMessage response, IEnumerable<ApiResponseErrorHandlingDelegate> handlers)
457471
{
458-
var isErrorResponse = statusCode < HttpStatusCode.OK || statusCode >= HttpStatusCode.BadRequest;
472+
var isErrorResponse = (statusCode < HttpStatusCode.OK || statusCode >= HttpStatusCode.BadRequest) && statusCode != HttpStatusCode.SwitchingProtocols;
459473

460474
string responseBody = null;
461475

src/Docker.DotNet/DockerPipeStream.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ namespace Docker.DotNet;
22

33
internal class DockerPipeStream : WriteClosableStream, IPeekableStream
44
{
5-
private readonly PipeStream _stream;
65
private readonly EventWaitHandle _event = new EventWaitHandle(false, EventResetMode.AutoReset);
6+
private readonly PipeStream _stream;
77

88
public DockerPipeStream(PipeStream stream)
99
{
@@ -26,7 +26,6 @@ public override long Length
2626
public override long Position
2727
{
2828
get { throw new NotImplementedException(); }
29-
3029
set { throw new NotImplementedException(); }
3130
}
3231

@@ -37,19 +36,15 @@ public override long Position
3736
private static extern int GetOverlappedResult(SafeHandle handle, ref NativeOverlapped overlapped, out int numBytesWritten, int wait);
3837

3938
[DllImport("kernel32.dll", SetLastError = true)]
40-
private static extern bool PeekNamedPipe(SafeHandle handle, byte[] buffer, uint nBufferSize, ref uint bytesRead, ref uint bytesAvail, ref uint BytesLeftThisMessage);
39+
private static extern bool PeekNamedPipe(SafeHandle handle, byte[] buffer, uint nBufferSize, ref uint bytesRead, ref uint bytesAvail, ref uint bytesLeftThisMessage);
4140

4241
public override void CloseWrite()
4342
{
4443
// The Docker daemon expects a write of zero bytes to signal the end of writes. Use native
4544
// calls to achieve this since CoreCLR ignores a zero-byte write.
4645
var overlapped = new NativeOverlapped();
4746

48-
#if NET45
49-
var handle = _event.SafeWaitHandle;
50-
#else
5147
var handle = _event.GetSafeWaitHandle();
52-
#endif
5348

5449
// Set the low bit to tell Windows not to send the result of this IO to the
5550
// completion port.

src/Docker.DotNet/Endpoints/ContainerOperations.cs

Lines changed: 41 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ internal ContainerOperations(DockerClient client)
2525
_client = client;
2626
}
2727

28-
public async Task<IList<ContainerListResponse>> ListContainersAsync(ContainersListParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
28+
public async Task<IList<ContainerListResponse>> ListContainersAsync(ContainersListParameters parameters, CancellationToken cancellationToken = default)
2929
{
3030
if (parameters == null)
3131
{
@@ -36,7 +36,7 @@ internal ContainerOperations(DockerClient client)
3636
return await _client.MakeRequestAsync<ContainerListResponse[]>(_client.NoErrorHandlers, HttpMethod.Get, "containers/json", queryParameters, cancellationToken).ConfigureAwait(false);
3737
}
3838

39-
public async Task<CreateContainerResponse> CreateContainerAsync(CreateContainerParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
39+
public async Task<CreateContainerResponse> CreateContainerAsync(CreateContainerParameters parameters, CancellationToken cancellationToken = default)
4040
{
4141
IQueryString qs = null;
4242

@@ -54,7 +54,7 @@ internal ContainerOperations(DockerClient client)
5454
return await _client.MakeRequestAsync<CreateContainerResponse>(new[] { NoSuchImageHandler }, HttpMethod.Post, "containers/create", qs, data, cancellationToken).ConfigureAwait(false);
5555
}
5656

57-
public async Task<ContainerInspectResponse> InspectContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken))
57+
public async Task<ContainerInspectResponse> InspectContainerAsync(string id, CancellationToken cancellationToken = default)
5858
{
5959
if (string.IsNullOrEmpty(id))
6060
{
@@ -64,7 +64,7 @@ internal ContainerOperations(DockerClient client)
6464
return await _client.MakeRequestAsync<ContainerInspectResponse>(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/json", cancellationToken).ConfigureAwait(false);
6565
}
6666

67-
public async Task<ContainerInspectResponse> InspectContainerAsync(string id, ContainerInspectParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
67+
public async Task<ContainerInspectResponse> InspectContainerAsync(string id, ContainerInspectParameters parameters, CancellationToken cancellationToken = default)
6868
{
6969
if (string.IsNullOrEmpty(id))
7070
{
@@ -80,7 +80,7 @@ internal ContainerOperations(DockerClient client)
8080
return await _client.MakeRequestAsync<ContainerInspectResponse>(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/json", queryString, cancellationToken).ConfigureAwait(false);
8181
}
8282

83-
public async Task<ContainerProcessesResponse> ListProcessesAsync(string id, ContainerListProcessesParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
83+
public async Task<ContainerProcessesResponse> ListProcessesAsync(string id, ContainerListProcessesParameters parameters, CancellationToken cancellationToken = default)
8484
{
8585
if (string.IsNullOrEmpty(id))
8686
{
@@ -96,32 +96,16 @@ internal ContainerOperations(DockerClient client)
9696
return await _client.MakeRequestAsync<ContainerProcessesResponse>(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/top", queryParameters, cancellationToken).ConfigureAwait(false);
9797
}
9898

99-
public Task<Stream> GetContainerLogsAsync(string id, ContainerLogsParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
99+
public async Task GetContainerLogsAsync(string id, ContainerLogsParameters parameters, IProgress<string> progress, CancellationToken cancellationToken = default)
100100
{
101-
if (string.IsNullOrEmpty(id))
102-
{
103-
throw new ArgumentNullException(nameof(id));
104-
}
105-
106-
if (parameters == null)
107-
{
108-
throw new ArgumentNullException(nameof(parameters));
109-
}
101+
using var multiplexedStream = await GetContainerLogsAsync(id, parameters, cancellationToken)
102+
.ConfigureAwait(false);
110103

111-
IQueryString queryParameters = new QueryString<ContainerLogsParameters>(parameters);
112-
return _client.MakeRequestForStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/logs", queryParameters, cancellationToken);
104+
await StreamUtil.MonitorStreamAsync(multiplexedStream, progress, cancellationToken)
105+
.ConfigureAwait(false);
113106
}
114107

115-
public Task GetContainerLogsAsync(string id, ContainerLogsParameters parameters, CancellationToken cancellationToken, IProgress<string> progress)
116-
{
117-
return StreamUtil.MonitorStreamAsync(
118-
GetContainerLogsAsync(id, parameters, cancellationToken),
119-
_client,
120-
cancellationToken,
121-
progress);
122-
}
123-
124-
public async Task<MultiplexedStream> GetContainerLogsAsync(string id, bool tty, ContainerLogsParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
108+
public async Task<MultiplexedStream> GetContainerLogsAsync(string id, ContainerLogsParameters parameters, CancellationToken cancellationToken = default)
125109
{
126110
if (string.IsNullOrEmpty(id))
127111
{
@@ -133,14 +117,18 @@ public Task GetContainerLogsAsync(string id, ContainerLogsParameters parameters,
133117
throw new ArgumentNullException(nameof(parameters));
134118
}
135119

136-
IQueryString queryParameters = new QueryString<ContainerLogsParameters>(parameters);
120+
var queryParameters = new QueryString<ContainerLogsParameters>(parameters);
137121

138-
Stream result = await _client.MakeRequestForStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/logs", queryParameters, cancellationToken).ConfigureAwait(false);
122+
var containerInspectResponse = await InspectContainerAsync(id, cancellationToken)
123+
.ConfigureAwait(false);
139124

140-
return new MultiplexedStream(result, !tty);
125+
var stream = await _client.MakeRequestForStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/logs", queryParameters, null, null, cancellationToken)
126+
.ConfigureAwait(false);
127+
128+
return new MultiplexedStream(stream, !containerInspectResponse.Config.Tty);
141129
}
142130

143-
public async Task<IList<ContainerFileSystemChangeResponse>> InspectChangesAsync(string id, CancellationToken cancellationToken = default(CancellationToken))
131+
public async Task<IList<ContainerFileSystemChangeResponse>> InspectChangesAsync(string id, CancellationToken cancellationToken = default)
144132
{
145133
if (string.IsNullOrEmpty(id))
146134
{
@@ -176,7 +164,7 @@ public Task<Stream> GetContainerStatsAsync(string id, ContainerStatsParameters p
176164
return _client.MakeRequestForStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Get, $"containers/{id}/stats", queryParameters, null, null, cancellationToken);
177165
}
178166

179-
public Task GetContainerStatsAsync(string id, ContainerStatsParameters parameters, IProgress<ContainerStatsResponse> progress, CancellationToken cancellationToken = default(CancellationToken))
167+
public Task GetContainerStatsAsync(string id, ContainerStatsParameters parameters, IProgress<ContainerStatsResponse> progress, CancellationToken cancellationToken = default)
180168
{
181169
return StreamUtil.MonitorStreamForMessagesAsync(
182170
GetContainerStatsAsync(id, parameters, cancellationToken),
@@ -185,7 +173,7 @@ public Task<Stream> GetContainerStatsAsync(string id, ContainerStatsParameters p
185173
progress);
186174
}
187175

188-
public Task ResizeContainerTtyAsync(string id, ContainerResizeParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
176+
public Task ResizeContainerTtyAsync(string id, ContainerResizeParameters parameters, CancellationToken cancellationToken = default)
189177
{
190178
if (string.IsNullOrEmpty(id))
191179
{
@@ -201,7 +189,7 @@ public Task<Stream> GetContainerStatsAsync(string id, ContainerStatsParameters p
201189
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/resize", queryParameters, cancellationToken);
202190
}
203191

204-
public async Task<bool> StartContainerAsync(string id, ContainerStartParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
192+
public async Task<bool> StartContainerAsync(string id, ContainerStartParameters parameters, CancellationToken cancellationToken = default)
205193
{
206194
if (string.IsNullOrEmpty(id))
207195
{
@@ -214,7 +202,7 @@ public Task<Stream> GetContainerStatsAsync(string id, ContainerStatsParameters p
214202
return result ?? throw new InvalidOperationException();
215203
}
216204

217-
public async Task<bool> StopContainerAsync(string id, ContainerStopParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
205+
public async Task<bool> StopContainerAsync(string id, ContainerStopParameters parameters, CancellationToken cancellationToken = default)
218206
{
219207
if (string.IsNullOrEmpty(id))
220208
{
@@ -234,7 +222,7 @@ public Task<Stream> GetContainerStatsAsync(string id, ContainerStatsParameters p
234222
return result ?? throw new InvalidOperationException();
235223
}
236224

237-
public Task RestartContainerAsync(string id, ContainerRestartParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
225+
public Task RestartContainerAsync(string id, ContainerRestartParameters parameters, CancellationToken cancellationToken = default)
238226
{
239227
if (string.IsNullOrEmpty(id))
240228
{
@@ -253,7 +241,7 @@ public Task<Stream> GetContainerStatsAsync(string id, ContainerStatsParameters p
253241
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/restart", queryParameters, null, null, TimeSpan.FromMilliseconds(Timeout.Infinite), cancellationToken);
254242
}
255243

256-
public Task KillContainerAsync(string id, ContainerKillParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
244+
public Task KillContainerAsync(string id, ContainerKillParameters parameters, CancellationToken cancellationToken = default)
257245
{
258246
if (string.IsNullOrEmpty(id))
259247
{
@@ -280,7 +268,7 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
280268
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/rename", queryParameters, cancellationToken);
281269
}
282270

283-
public Task PauseContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken))
271+
public Task PauseContainerAsync(string id, CancellationToken cancellationToken = default)
284272
{
285273
if (string.IsNullOrEmpty(id))
286274
{
@@ -290,7 +278,7 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
290278
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/pause", cancellationToken);
291279
}
292280

293-
public Task UnpauseContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken))
281+
public Task UnpauseContainerAsync(string id, CancellationToken cancellationToken = default)
294282
{
295283
if (string.IsNullOrEmpty(id))
296284
{
@@ -300,7 +288,7 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
300288
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/unpause", cancellationToken);
301289
}
302290

303-
public async Task<MultiplexedStream> AttachContainerAsync(string id, bool tty, ContainerAttachParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
291+
public async Task<MultiplexedStream> AttachContainerAsync(string id, ContainerAttachParameters parameters, CancellationToken cancellationToken = default)
304292
{
305293
if (string.IsNullOrEmpty(id))
306294
{
@@ -313,11 +301,17 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
313301
}
314302

315303
var queryParameters = new QueryString<ContainerAttachParameters>(parameters);
316-
var result = await _client.MakeRequestForStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/attach", queryParameters, null, null, cancellationToken).ConfigureAwait(false);
317-
return new MultiplexedStream(result, !tty);
304+
305+
var containerInspectResponse = await InspectContainerAsync(id, cancellationToken)
306+
.ConfigureAwait(false);
307+
308+
var stream = await _client.MakeRequestForHijackedStreamAsync(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/attach", queryParameters, null, null, cancellationToken)
309+
.ConfigureAwait(false);
310+
311+
return new MultiplexedStream(stream, !containerInspectResponse.Config.Tty);
318312
}
319313

320-
public async Task<ContainerWaitResponse> WaitContainerAsync(string id, CancellationToken cancellationToken = default(CancellationToken))
314+
public async Task<ContainerWaitResponse> WaitContainerAsync(string id, CancellationToken cancellationToken = default)
321315
{
322316
if (string.IsNullOrEmpty(id))
323317
{
@@ -327,7 +321,7 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
327321
return await _client.MakeRequestAsync<ContainerWaitResponse>(new[] { NoSuchContainerHandler }, HttpMethod.Post, $"containers/{id}/wait", null, null, null, TimeSpan.FromMilliseconds(Timeout.Infinite), cancellationToken).ConfigureAwait(false);
328322
}
329323

330-
public Task RemoveContainerAsync(string id, ContainerRemoveParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
324+
public Task RemoveContainerAsync(string id, ContainerRemoveParameters parameters, CancellationToken cancellationToken = default)
331325
{
332326
if (string.IsNullOrEmpty(id))
333327
{
@@ -343,7 +337,7 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
343337
return _client.MakeRequestAsync(new[] { NoSuchContainerHandler }, HttpMethod.Delete, $"containers/{id}", queryParameters, cancellationToken);
344338
}
345339

346-
public async Task<GetArchiveFromContainerResponse> GetArchiveFromContainerAsync(string id, GetArchiveFromContainerParameters parameters, bool statOnly, CancellationToken cancellationToken = default(CancellationToken))
340+
public async Task<GetArchiveFromContainerResponse> GetArchiveFromContainerAsync(string id, GetArchiveFromContainerParameters parameters, bool statOnly, CancellationToken cancellationToken = default)
347341
{
348342
if (string.IsNullOrEmpty(id))
349343
{
@@ -372,7 +366,7 @@ public Task RenameContainerAsync(string id, ContainerRenameParameters parameters
372366
};
373367
}
374368

375-
public Task ExtractArchiveToContainerAsync(string id, ContainerPathStatParameters parameters, Stream stream, CancellationToken cancellationToken = default(CancellationToken))
369+
public Task ExtractArchiveToContainerAsync(string id, ContainerPathStatParameters parameters, Stream stream, CancellationToken cancellationToken = default)
376370
{
377371
if (string.IsNullOrEmpty(id))
378372
{
@@ -396,7 +390,7 @@ public async Task<ContainersPruneResponse> PruneContainersAsync(ContainersPruneP
396390
return await _client.MakeRequestAsync<ContainersPruneResponse>(_client.NoErrorHandlers, HttpMethod.Post, "containers/prune", queryParameters, cancellationToken).ConfigureAwait(false);
397391
}
398392

399-
public async Task<ContainerUpdateResponse> UpdateContainerAsync(string id, ContainerUpdateParameters parameters, CancellationToken cancellationToken = default(CancellationToken))
393+
public async Task<ContainerUpdateResponse> UpdateContainerAsync(string id, ContainerUpdateParameters parameters, CancellationToken cancellationToken = default)
400394
{
401395
if (string.IsNullOrEmpty(id))
402396
{

src/Docker.DotNet/Endpoints/ExecOperations.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ public async Task<MultiplexedStream> StartContainerExecAsync(string id, Containe
5555

5656
var data = new JsonRequestContent<ContainerExecStartParameters>(parameters, DockerClient.JsonSerializer);
5757

58-
var result = await _client.MakeRequestForStreamAsync([NoSuchContainerHandler], HttpMethod.Post, $"exec/{id}/start", null, data, null, cancellationToken)
58+
var stream = await _client.MakeRequestForHijackedStreamAsync([NoSuchContainerHandler], HttpMethod.Post, $"exec/{id}/start", null, data, null, cancellationToken)
5959
.ConfigureAwait(false);
6060

61-
return new MultiplexedStream(result, !parameters.Tty);
61+
return new MultiplexedStream(stream, !parameters.Tty);
6262
}
6363
}

0 commit comments

Comments
 (0)