Skip to content

Commit f2e1c4b

Browse files
authored
Remove Microsoft.AspNetCore.WebUtilities dependency (#419)
* Remove Microsoft.AspNetCore.WebUtilities dependency * Fix more query-string handling * Add unit test * Merge with master
1 parent b0e7d99 commit f2e1c4b

File tree

7 files changed

+85
-53
lines changed

7 files changed

+85
-53
lines changed

src/KubernetesClient/Kubernetes.Watch.cs

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
using Microsoft.AspNetCore.WebUtilities;
21
using Microsoft.Rest;
32
using System;
43
using System.Collections.Generic;
54
using System.IO;
65
using System.Net;
76
using System.Net.Http;
7+
using System.Text;
88
using System.Threading;
99
using System.Threading.Tasks;
1010

@@ -43,53 +43,51 @@ public partial class Kubernetes
4343

4444
uriBuilder.Path += path;
4545

46-
var query = string.Empty;
47-
46+
var query = new StringBuilder();
4847
// Don't sent watch, because setting that value will cause the WatcherDelegatingHandler to kick in. That class
4948
// "eats" the first line, which is something we don't want.
5049
// query = QueryHelpers.AddQueryString(query, "watch", "true");
51-
52-
if (@continue != null)
50+
if(@continue != null)
5351
{
54-
query = QueryHelpers.AddQueryString(query, "continue", Uri.EscapeDataString(@continue));
52+
Utilities.AddQueryParameter(query, "continue", @continue);
5553
}
5654

57-
if (fieldSelector != null)
55+
if (!string.IsNullOrEmpty(fieldSelector))
5856
{
59-
query = QueryHelpers.AddQueryString(query, "fieldSelector", Uri.EscapeDataString(fieldSelector));
57+
Utilities.AddQueryParameter(query, "fieldSelector", fieldSelector);
6058
}
6159

6260
if (includeUninitialized != null)
6361
{
64-
query = QueryHelpers.AddQueryString(query, "includeUninitialized", includeUninitialized.Value ? "true" : "false");
62+
Utilities.AddQueryParameter(query, "includeUninitialized", includeUninitialized.Value ? "true" : "false");
6563
}
6664

67-
if (labelSelector != null)
65+
if (!string.IsNullOrEmpty(labelSelector))
6866
{
69-
query = QueryHelpers.AddQueryString(query, "labelSelector", Uri.EscapeDataString(labelSelector));
67+
Utilities.AddQueryParameter(query, "labelSelector", labelSelector);
7068
}
7169

7270
if (limit != null)
7371
{
74-
query = QueryHelpers.AddQueryString(query, "limit", limit.Value.ToString());
72+
Utilities.AddQueryParameter(query, "limit", limit.Value.ToString());
7573
}
7674

7775
if (pretty != null)
7876
{
79-
query = QueryHelpers.AddQueryString(query, "pretty", pretty.Value ? "true" : "false");
77+
Utilities.AddQueryParameter(query, "pretty", pretty.Value ? "true" : "false");
8078
}
8179

8280
if (timeoutSeconds != null)
8381
{
84-
query = QueryHelpers.AddQueryString(query, "timeoutSeconds", timeoutSeconds.Value.ToString());
82+
Utilities.AddQueryParameter(query, "timeoutSeconds", timeoutSeconds.Value.ToString());
8583
}
8684

87-
if (resourceVersion != null)
85+
if (!string.IsNullOrEmpty(resourceVersion))
8886
{
89-
query = QueryHelpers.AddQueryString(query, "resourceVersion", resourceVersion);
87+
Utilities.AddQueryParameter(query, "resourceVersion", resourceVersion);
9088
}
9189

92-
uriBuilder.Query = query;
90+
uriBuilder.Query = query.Length == 0 ? "" : query.ToString(1, query.Length-1); // UriBuilder.Query doesn't like leading '?' chars, so trim it
9391

9492
// Create HTTP transport objects
9593
var httpRequest = new HttpRequestMessage(HttpMethod.Get, uriBuilder.ToString());

src/KubernetesClient/Kubernetes.WebSocket.cs

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using k8s.Models;
2-
using Microsoft.AspNetCore.WebUtilities;
32
using Microsoft.Rest;
43
using Microsoft.Rest.Serialization;
54
using System;
@@ -14,6 +13,8 @@
1413
using System.Security.Cryptography.X509Certificates;
1514
using System.Threading;
1615
using System.Threading.Tasks;
16+
using System.Text;
17+
using System.Globalization;
1718

1819
namespace k8s
1920
{
@@ -102,27 +103,23 @@ public partial class Kubernetes
102103

103104
uriBuilder.Path += $"api/v1/namespaces/{@namespace}/pods/{name}/exec";
104105

105-
var query = string.Empty;
106+
var query = new StringBuilder();
106107

107108
foreach (var c in command)
108109
{
109-
query = QueryHelpers.AddQueryString(query, "command", c);
110+
Utilities.AddQueryParameter(query, "command", c);
110111
}
111112

112-
if (container != null)
113+
if (!string.IsNullOrEmpty(container))
113114
{
114-
query = QueryHelpers.AddQueryString(query, "container", Uri.EscapeDataString(container));
115+
Utilities.AddQueryParameter(query, "container", container);
115116
}
116117

117-
query = QueryHelpers.AddQueryString(query, new Dictionary<string, string>
118-
{
119-
{"stderr", stderr ? "1" : "0"},
120-
{"stdin", stdin ? "1" : "0"},
121-
{"stdout", stdout ? "1" : "0"},
122-
{"tty", tty ? "1" : "0"}
123-
}).TrimStart('?');
124-
125-
uriBuilder.Query = query;
118+
query.Append("&stderr=").Append(stderr ? '1' : '0'); // the query string is guaranteed not to be empty here because it has a 'command' param
119+
query.Append("&stdin=").Append(stdin ? '1' : '0');
120+
query.Append("&stdout=").Append(stdout ? '1' : '0');
121+
query.Append("&tty=").Append(tty ? '1' : '0');
122+
uriBuilder.Query = query.ToString(1, query.Length-1); // UriBuilder.Query doesn't like leading '?' chars, so trim it
126123

127124
return this.StreamConnectAsync(uriBuilder.Uri, _invocationId, webSocketSubProtol, customHeaders, cancellationToken);
128125
}
@@ -171,14 +168,13 @@ public partial class Kubernetes
171168

172169
uriBuilder.Path += $"api/v1/namespaces/{@namespace}/pods/{name}/portforward";
173170

174-
var q = "";
171+
var q = new StringBuilder();
175172
foreach (var port in ports)
176173
{
177-
q = QueryHelpers.AddQueryString(q, "ports", $"{port}");
174+
if (q.Length != 0) q.Append('&');
175+
q.Append("ports=").Append(port.ToString(CultureInfo.InvariantCulture));
178176
}
179-
uriBuilder.Query = q.TrimStart('?');
180-
181-
177+
uriBuilder.Query = q.ToString();
182178

183179
return StreamConnectAsync(uriBuilder.Uri, _invocationId, webSocketSubProtocol, customHeaders, cancellationToken);
184180
}
@@ -226,14 +222,13 @@ public partial class Kubernetes
226222

227223
uriBuilder.Path += $"api/v1/namespaces/{@namespace}/pods/{name}/attach";
228224

229-
uriBuilder.Query = QueryHelpers.AddQueryString(string.Empty, new Dictionary<string, string>
230-
{
231-
{ "container", container},
232-
{ "stderr", stderr ? "1": "0"},
233-
{ "stdin", stdin ? "1": "0"},
234-
{ "stdout", stdout ? "1": "0"},
235-
{ "tty", tty ? "1": "0"}
236-
}).TrimStart('?');
225+
var query = new StringBuilder();
226+
query.Append("?stderr=").Append(stderr ? '1' : '0');
227+
query.Append("&stdin=").Append(stdin ? '1' : '0');
228+
query.Append("&stdout=").Append(stdout ? '1' : '0');
229+
query.Append("&tty=").Append(tty ? '1' : '0');
230+
Utilities.AddQueryParameter(query, "container", container);
231+
uriBuilder.Query = query.ToString(1, query.Length-1); // UriBuilder.Query doesn't like leading '?' chars, so trim it
237232

238233
return StreamConnectAsync(uriBuilder.Uri, _invocationId, webSocketSubProtol, customHeaders, cancellationToken);
239234
}

src/KubernetesClient/KubernetesClient.csproj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,11 @@
3232
<PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="3.0.0" Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp2.1'" />
3333
<PackageReference Include="Nerdbank.GitVersioning" Version="3.0.50" PrivateAssets="all" />
3434
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.3" />
35-
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="1.1.2" Condition="'$(TargetFramework)' != 'netstandard2.0'" />
36-
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
3735
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.10" />
3836
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" Condition="'$(TargetFramework)' != 'netstandard2.0' and '$(TargetFramework)' != 'netcoreapp2.1'" />
3937
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp2.1'" />
4038
<PackageReference Include="YamlDotNet" Version="6.0.0" />
39+
<PackageReference Include="System.Buffers" Version="4.5.1" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
4140
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
4241
</ItemGroup>
4342
<ItemGroup Condition="'$(TargetFramework)' == 'net452'">

src/KubernetesClient/Utilities.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using System.Text;
3+
4+
namespace k8s
5+
{
6+
internal static class Utilities
7+
{
8+
/// <summary>Given a <see cref="StringBuilder"/> that is building a query string, adds a parameter to it.</summary>
9+
public static void AddQueryParameter(StringBuilder sb, string key, string value)
10+
{
11+
if (sb == null) throw new ArgumentNullException(nameof(sb));
12+
if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
13+
sb.Append(sb.Length != 0 ? '&' : '?').Append(Uri.EscapeDataString(key)).Append('=');
14+
if (!string.IsNullOrEmpty(value)) sb.Append(Uri.EscapeDataString(value));
15+
}
16+
}
17+
}

src/KubernetesClient/WatcherDelegatingHandler.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.Net.Http;
77
using System.Threading;
88
using System.Threading.Tasks;
9-
using Microsoft.AspNetCore.WebUtilities;
109

1110
namespace k8s
1211
{
@@ -21,11 +20,11 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
2120
{
2221
var originResponse = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
2322

24-
if (originResponse.IsSuccessStatusCode)
23+
if (originResponse.IsSuccessStatusCode && request.Method == HttpMethod.Get) // all watches are GETs, so we can ignore others
2524
{
26-
var query = QueryHelpers.ParseQuery(request.RequestUri.Query);
27-
28-
if (query.TryGetValue("watch", out var values) && values.Any(v => v == "true"))
25+
string query = request.RequestUri.Query;
26+
int index = query.IndexOf("watch=true");
27+
if (index > 0 && (query[index-1] == '&' || query[index-1] == '?'))
2928
{
3029
originResponse.Content = new LineSeparatedHttpContent(originResponse.Content, cancellationToken);
3130
}

tests/KubernetesClient.Tests/Kubernetes.WebSockets.Tests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public async Task WebSocketNamespacedPodExecAsync()
5858
};
5959

6060
Assert.Equal(mockWebSocketBuilder.PublicWebSocket, webSocket); // Did the method return the correct web socket?
61-
Assert.Equal(new Uri("ws://localhost/api/v1/namespaces/mynamespace/pods/mypod/exec?command=%2Fbin%2Fbash&command=-c&command=echo%20Hello,%20World%0Aexit%200%0A&container=mycontainer&stderr=1&stdin=1&stdout=1&tty=1"), mockWebSocketBuilder.Uri); // Did we connect to the correct URL?
61+
Assert.Equal(new Uri("ws://localhost/api/v1/namespaces/mynamespace/pods/mypod/exec?command=%2Fbin%2Fbash&command=-c&command=echo%20Hello%2C%20World%0Aexit%200%0A&container=mycontainer&stderr=1&stdin=1&stdout=1&tty=1"), mockWebSocketBuilder.Uri); // Did we connect to the correct URL?
6262
Assert.Empty(mockWebSocketBuilder.Certificates); // No certificates were used in this test
6363
Assert.Equal(expectedHeaders, mockWebSocketBuilder.RequestHeaders); // Did we use the expected headers
6464
}
@@ -136,7 +136,7 @@ public async Task WebSocketNamespacedPodAttachAsync()
136136
};
137137

138138
Assert.Equal(mockWebSocketBuilder.PublicWebSocket, webSocket); // Did the method return the correct web socket?
139-
Assert.Equal(new Uri("ws://localhost:80/api/v1/namespaces/mynamespace/pods/mypod/attach?container=my-container&stderr=1&stdin=1&stdout=1&tty=1"), mockWebSocketBuilder.Uri); // Did we connect to the correct URL?
139+
Assert.Equal(new Uri("ws://localhost:80/api/v1/namespaces/mynamespace/pods/mypod/attach?stderr=1&stdin=1&stdout=1&tty=1&container=my-container"), mockWebSocketBuilder.Uri); // Did we connect to the correct URL?
140140
Assert.Empty(mockWebSocketBuilder.Certificates); // No certificates were used in this test
141141
Assert.Equal(expectedHeaders, mockWebSocketBuilder.RequestHeaders); // Did we use the expected headers
142142
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Text;
3+
using Xunit;
4+
5+
namespace k8s.Tests
6+
{
7+
public class UtilityTests
8+
{
9+
[Fact]
10+
public void TestQueryStringUtilities()
11+
{
12+
var sb = new StringBuilder();
13+
Assert.Throws<ArgumentNullException>(() => Utilities.AddQueryParameter(null, "key", "value"));
14+
Assert.Throws<ArgumentNullException>(() => Utilities.AddQueryParameter(sb, null, "value"));
15+
Assert.Throws<ArgumentNullException>(() => Utilities.AddQueryParameter(sb, "", "value"));
16+
17+
Utilities.AddQueryParameter(sb, "key", "value");
18+
Utilities.AddQueryParameter(sb, "key", "a=b");
19+
Utilities.AddQueryParameter(sb, "+key", null);
20+
Utilities.AddQueryParameter(sb, "ekey", "");
21+
Assert.Equal("?key=value&key=a%3Db&%2Bkey=&ekey=", sb.ToString());
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)