diff --git a/app/lib/package/upload_signer_service.dart b/app/lib/package/upload_signer_service.dart index a3c6d906f3..daffdaa65e 100644 --- a/app/lib/package/upload_signer_service.dart +++ b/app/lib/package/upload_signer_service.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:_pub_shared/data/package_api.dart'; +import 'package:_pub_shared/utils/http.dart'; import 'package:clock/clock.dart'; import 'package:gcloud/service_scope.dart' as ss; import 'package:googleapis/iam/v1.dart' as iam; @@ -105,20 +106,22 @@ abstract class UploadSignerService { class _IamBasedUploadSigner extends UploadSignerService { final String projectId; final String email; - final iam.IamApi iamApi; + final http.Client authClient; - _IamBasedUploadSigner(this.projectId, this.email, http.Client client) - : iamApi = iam.IamApi(client); + _IamBasedUploadSigner(this.projectId, this.email, this.authClient); @override Future sign(List bytes) async { final request = iam.SignBlobRequest()..bytesToSignAsBytes = bytes; final name = 'projects/$projectId/serviceAccounts/$email'; - final iam.SignBlobResponse response = - // TODO: figure out what new API we should use. - // ignore: deprecated_member_use - await iamApi.projects.serviceAccounts.signBlob(request, name); - return SigningResult(email, response.signatureAsBytes); + return await withRetryHttpClient(client: authClient, (client) async { + final iamApi = iam.IamApi(client); + final response = + // TODO: figure out what new API we should use. + // ignore: deprecated_member_use + await iamApi.projects.serviceAccounts.signBlob(request, name); + return SigningResult(email, response.signatureAsBytes); + }); } } diff --git a/app/lib/publisher/domain_verifier.dart b/app/lib/publisher/domain_verifier.dart index 71d98e6c02..cb7287fbc9 100644 --- a/app/lib/publisher/domain_verifier.dart +++ b/app/lib/publisher/domain_verifier.dart @@ -2,13 +2,13 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'package:_pub_shared/utils/http.dart'; import 'package:clock/clock.dart'; import 'package:gcloud/service_scope.dart' as ss; import 'package:googleapis/searchconsole/v1.dart' as wmx; import 'package:googleapis_auth/googleapis_auth.dart' as auth; import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; -import 'package:retry/retry.dart' show retry; import '../shared/exceptions.dart' show AuthorizationException; @@ -47,8 +47,9 @@ class DomainVerifier { ); try { // Request list of sites/domains from the Search Console API. - final sites = await retry( - () => wmx.SearchConsoleApi(client).sites.list(), + final sites = await withRetryHttpClient( + client: client, + (client) => wmx.SearchConsoleApi(client).sites.list(), maxAttempts: 3, maxDelay: Duration(milliseconds: 500), retryIf: (e) => e is! auth.AccessDeniedException, diff --git a/app/lib/service/email/email_sender.dart b/app/lib/service/email/email_sender.dart index a21c6d233d..099bc8d2bd 100644 --- a/app/lib/service/email/email_sender.dart +++ b/app/lib/service/email/email_sender.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:convert' show json; import 'dart:io'; +import 'package:_pub_shared/utils/http.dart'; import 'package:clock/clock.dart'; import 'package:gcloud/service_scope.dart' as ss; import 'package:googleapis/iamcredentials/v1.dart' as iam_credentials; @@ -264,12 +265,14 @@ class _GmailSmtpRelay extends EmailSenderBase { /// [_serviceAccountEmail] configured for _domain-wide delegation_ following: /// https://developers.google.com/identity/protocols/oauth2/service-account Future _createAccessToken(String sender) async { - final iam = iam_credentials.IAMCredentialsApi(_authClient); final iat = clock.now().toUtc().millisecondsSinceEpoch ~/ 1000 - 20; iam_credentials.SignJwtResponse jwtResponse; try { - jwtResponse = await retry( - () => iam.projects.serviceAccounts.signJwt( + jwtResponse = await withRetryHttpClient(client: _authClient, ( + client, + ) async { + final iam = iam_credentials.IAMCredentialsApi(client); + return iam.projects.serviceAccounts.signJwt( iam_credentials.SignJwtRequest() ..payload = json.encode({ 'iss': _serviceAccountEmail, @@ -280,8 +283,8 @@ class _GmailSmtpRelay extends EmailSenderBase { 'sub': sender, }), 'projects/-/serviceAccounts/$_serviceAccountEmail', - ), - ); + ); + }); } on Exception catch (e, st) { _logger.severe( 'Signing JWT for sending email failed, ' @@ -294,29 +297,24 @@ class _GmailSmtpRelay extends EmailSenderBase { ); } - final client = http.Client(); - try { + return await withRetryHttpClient((client) async { // Send a POST request with: // Content-Type: application/x-www-form-urlencoded; charset=utf-8 - return await retry(() async { - final r = await client.post( - _googleOauth2TokenUrl, - body: { - 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', - 'assertion': jwtResponse.signedJwt, - }, + final r = await client.post( + _googleOauth2TokenUrl, + body: { + 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', + 'assertion': jwtResponse.signedJwt, + }, + ); + if (r.statusCode != 200) { + throw SmtpClientAuthenticationException( + 'statusCode=${r.statusCode} from $_googleOauth2TokenUrl ' + 'while trying exchange JWT for access_token', ); - if (r.statusCode != 200) { - throw SmtpClientAuthenticationException( - 'statusCode=${r.statusCode} from $_googleOauth2TokenUrl ' - 'while trying exchange JWT for access_token', - ); - } - return json.decode(r.body)['access_token'] as String; - }); - } finally { - client.close(); - } + } + return json.decode(r.body)['access_token'] as String; + }); } } diff --git a/app/lib/service/secret/backend.dart b/app/lib/service/secret/backend.dart index 7a1a2db9f9..d1cba56f28 100644 --- a/app/lib/service/secret/backend.dart +++ b/app/lib/service/secret/backend.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:convert'; +import 'package:_pub_shared/utils/http.dart'; import 'package:clock/clock.dart'; import 'package:gcloud/service_scope.dart' as ss; import 'package:googleapis/secretmanager/v1.dart' as secretmanager; @@ -12,7 +13,6 @@ import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; import 'package:pub_dev/shared/configuration.dart'; import 'package:pub_dev/shared/monitoring.dart'; -import 'package:retry/retry.dart'; import 'models.dart'; @@ -72,17 +72,17 @@ final class GcpSecretBackend extends SecretBackend { Future _lookup(String id) async { try { - final api = secretmanager.SecretManagerApi(_client); - final secret = await retry( - () async => await api.projects.secrets.versions.access( + return await withRetryHttpClient(client: _client, (client) async { + final api = secretmanager.SecretManagerApi(client); + final secret = await api.projects.secrets.versions.access( 'projects/${activeConfiguration.projectId}/secrets/$id/versions/latest', - ), - ); - final data = secret.payload?.data; - if (data == null) { - return null; - } - return utf8.decode(base64.decode(data)); + ); + final data = secret.payload?.data; + if (data == null) { + return null; + } + return utf8.decode(base64.decode(data)); + }); } catch (e, st) { // Log the issue _log.pubNoticeShout( diff --git a/app/lib/service/services.dart b/app/lib/service/services.dart index f0dd3133ce..dc71d8c91c 100644 --- a/app/lib/service/services.dart +++ b/app/lib/service/services.dart @@ -106,7 +106,7 @@ Future withServices(FutureOr Function() fn) async { ) : loggingEmailSender, ); - registerUploadSigner(await createUploadSigner(retryingAuthClient)); + registerUploadSigner(await createUploadSigner(authClient)); registerSecretBackend(GcpSecretBackend(authClient)); // Configure a CloudCompute pool for later use in TaskBackend diff --git a/pkg/_pub_shared/lib/utils/http.dart b/pkg/_pub_shared/lib/utils/http.dart index e93d8729a3..3eabeb6df2 100644 --- a/pkg/_pub_shared/lib/utils/http.dart +++ b/pkg/_pub_shared/lib/utils/http.dart @@ -87,6 +87,7 @@ Future httpGetWithRetry( Future withRetryHttpClient( Future Function(http.Client client) fn, { int maxAttempts = 3, + Duration maxDelay = const Duration(seconds: 30), /// The HTTP client to use. /// @@ -109,6 +110,7 @@ Future withRetryHttpClient( } }, maxAttempts: maxAttempts, + maxDelay: maxDelay, retryIf: (e) => _retryIf(e) || (retryIf != null && retryIf(e)), ); }