Skip to content

Commit a251eec

Browse files
Use caller-supplied HttpClient for token refresh (#324)
RefreshAccessToken sent the OAuth token request through the SDK's internal default HttpClient, ignoring a caller-supplied client. In high-traffic apps this leaked connections and exhausted ports. Route the refresh through GetHttpClient(HostType.Api) like normal requests, and add a regression test asserting the refresh uses the supplied client.
1 parent cde11d5 commit a251eec

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

dropbox-sdk-dotnet/Dropbox.Api.Integration.Tests/DropboxApiTests.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,48 @@ public async Task TestRefreshClient()
173173
Assert.IsNotNull(result.Email);
174174
}
175175

176+
/// <summary>
177+
/// Verifies that the token-refresh request is sent through the
178+
/// caller-supplied <see cref="HttpClient"/>, not a client the SDK
179+
/// creates internally. Previously the refresh path used its own default
180+
/// HttpClient, leaking connections and causing port exhaustion in
181+
/// high-traffic scenarios (issue #324).
182+
/// </summary>
183+
/// <returns>The <see cref="Task" />.</returns>
184+
[TestMethod]
185+
public async Task TestRefreshUsesSuppliedHttpClient()
186+
{
187+
var refreshRequests = 0;
188+
var mockHandler = new MockHttpMessageHandler((r, s) =>
189+
{
190+
if (r.RequestUri.AbsoluteUri.Contains("oauth2/token"))
191+
{
192+
refreshRequests++;
193+
}
194+
195+
return s(r);
196+
});
197+
198+
var mockClient = new HttpClient(mockHandler);
199+
var config = new DropboxClientConfig { HttpClient = mockClient };
200+
201+
// An access token whose expiry is already in the past forces a
202+
// proactive refresh on the first request. The SDK compares against
203+
// DateTime.Now, so use local time here.
204+
var client = new DropboxClient(
205+
"expired-access-token",
206+
userRefreshToken,
207+
DateTime.Now.AddMinutes(-5),
208+
appKey,
209+
appSecret,
210+
config);
211+
212+
var result = await client.Users.GetCurrentAccountAsync();
213+
214+
Assert.IsNotNull(result.Email);
215+
Assert.AreEqual(1, refreshRequests, "The refresh request should flow through the supplied HttpClient.");
216+
}
217+
176218
/// <summary>
177219
/// Test get metadata.
178220
/// </summary>

dropbox-sdk-dotnet/Dropbox.Api/DropboxRequestHandler.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,11 @@ public async Task<bool> RefreshAccessToken(string[] scopeList = null)
272272

273273
var bodyContent = new FormUrlEncodedContent(parameters);
274274

275-
var response = await this.defaultHttpClient.PostAsync(url, bodyContent).ConfigureAwait(false);
275+
// Use the same HttpClient as regular API requests so that a
276+
// caller-supplied HttpClient is honored here too. Otherwise the
277+
// token-refresh path leaks connections from the default client,
278+
// causing port exhaustion in high-traffic scenarios (issue #324).
279+
var response = await this.GetHttpClient(HostType.Api).PostAsync(url, bodyContent).ConfigureAwait(false);
276280

277281
// if response is an invalid grant, we want to throw this exception rather than the one thrown in
278282
// response.EnsureSuccessStatusCode();

0 commit comments

Comments
 (0)