-
Notifications
You must be signed in to change notification settings - Fork 229
Refactor HTTP retries (#3325) #3590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
a531c2f
to
ce049fc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks really good, I only have nits to suggest.
lib/src/http.dart
Outdated
statusCode == HttpStatus.tooManyRequests) { | ||
// Throw if the response indicates a server error or an intermittent | ||
// client error, but mark it as intermittent so it can be retried. | ||
throw PubHttpResponseException(this as http.Response, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have to cast, maybe it's better to do extension Throwing on http.Response
or, make PubHttpResponseException
accept an BaseResponse
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extending http.Response
breaks the extension for the _download method, which uses http.StreamedResponse
. Vice-versa for if we extend http.StreamedResponse
, since the versions method uses http.Response
.
The change to make PubHttpResponseException
accept http.BaseResponse
works, but causes build errors in lish.dart:
If we change handleJsonError
to accept http.BaseResponse
, we get some more errors:
And then finally, if we change parseJsonResponse
to accept http.BaseResponse
:
So, the cast was the only way I could come up with to make it all work. 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point, we can clean it up later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One option could be to create PubHttpResponse(BaseResponse, List<int>? body);
so that it has an optional body.
But maybe this is better left for a follow up PR...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we actually have: #3130
For refactoring handleJsonError
, I think if we're going to make better output -- that's also a great opportunity to re-think the API interface and use something like BaseResponse
instead.
Actually, it might be cleaner to cast from BaseResponse
to Response
in handleJsonError
, because -- if it's not a Response
then it's same as not being able to read the body. So we could do something like:
void handleJsonError(http.BaseResponse response) {
if (response is http.Response) { // this is a cast, but also handle cases where the cast fails
var errorMap = parseJsonResponse(response);
... // what we did before
}
invalidServerResponse(response);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cast from BaseResponse
to Response
looks good!
I ended up having to implement that in this PR because the cast in throwIfNotOk
was causing tests to fail.
Lines 192 to 205 in 7afe52c
void handleJsonError(http.BaseResponse response) { | |
if (response is! http.Response) { | |
// Not likely to be a common code path, but necessary. | |
// See https://github.com/dart-lang/pub/pull/3590#discussion_r1012978108 | |
fail(log.red('Invalid server response')); | |
} | |
var errorMap = parseJsonResponse(response); | |
if (errorMap['error'] is! Map || | |
!errorMap['error'].containsKey('message') || | |
errorMap['error']['message'] is! String) { | |
invalidServerResponse(response); | |
} | |
fail(log.red(errorMap['error']['message'])); | |
} |
lib/src/source/hosted.dart
Outdated
headers['X-Pub-Environment'] = environment; | ||
} | ||
|
||
var type = Zone.current[#_dependencyType]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't use this symbol here, I at-least I don't think we can. Since it's a private symbol, it should be distinct from the symbol defined in http.dart
:
Line 138 in 86536fd
return runZoned(callback, zoneValues: {#_dependencyType: type}); |
At-least that's my guess.
May I suggest that we make:
extension AttachHeaders on http.Request {
void attachMetadataHeaders() {
...
}
}
in http.dart
.
We'll have to refactor the places that use Client.get
and Client.read
into creating Request
objects and passing them to Client.send
.
But I don't think that matters much.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes me wonder if we have any tests covering the meta-data sending?
It would probably require changing isPubDevUrl
to something like:
static bool isPubDevUrl(String url) {
final origin = Uri.parse(url).origin;
return origin == pubDevUrl ||
origin == pubDartlangUrl ||
(runningFromTest &&
origin == io.Platform.environment['_PUB_TEST_DEFAULT_HOSTED_URL']);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed the headers method to an extension in http.dart. 👍
Re: the metadata test, I'd like to do that test but need some clarification on the desired behavior for the metadata URL-checking logic. I've sent you a message on Chat.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about we do tests for metadata sending a follow up PR (it's not tests we currently have). And it's probably better to keep this PR small.
Co-authored-by: Jonas Finnemann Jensen <[email protected]>
86536fd
to
3f23c43
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really good!
We don't have to do the metadata tests in this PR - bet they surely would be nice to have
lib/src/source/hosted.dart
Outdated
headers['X-Pub-Environment'] = environment; | ||
} | ||
|
||
var type = Zone.current[#_dependencyType]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes me wonder if we have any tests covering the meta-data sending?
It would probably require changing isPubDevUrl
to something like:
static bool isPubDevUrl(String url) {
final origin = Uri.parse(url).origin;
return origin == pubDevUrl ||
origin == pubDartlangUrl ||
(runningFromTest &&
origin == io.Platform.environment['_PUB_TEST_DEFAULT_HOSTED_URL']);
}
…t PubHttpException
…rowIfNotOk() in more places
@nehalvpatel I think if we fix the tests and retry in publishing, then this is pretty much good to go. We can revisit the types in |
7997462
to
afe16ee
Compare
/// | ||
/// If false is passed for [throwIfNotOk], the response will not be validated. | ||
/// See [http.BaseResponse.throwIfNotOk] extension for validation details. | ||
Future<http.Response> fetch(http.BaseRequest request, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind, but I have mixed feelings -- mostly because it's easy to call send()
by accident instead of using fetch()
.
or rather it's not very obvious that fetch
is intended to throw something in certain cases. But maybe, this is something we can refactor later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could do:
class PubHttpClient {
Future<http.Response> fetch(http.BaseRequest request);
Future<http.StreamingResponse> fetchStream(http.BaseRequest request);
// For the new cases where you really need it.
http.Client rawClient();
}
But maybe, we do this sort of thing later. Now that we're moving package:oauth2
into pub
-- we could consider refactoring all of that, and just have a fetch
method and fetchStream
method. But it's a lot more refactoring, let's cleanup later.
UPDATE FROM 10/26/2022 - Plan:
HttpException
,SocketException
,TlsException
,WebSocketException
,PubHttpException
if PubHttpException.isIntermittentPackageIntegrityException
will subclassPubHttpException
and be used for call-site specific errors. For example: crc32c validation and sha256 validationApplicationException
(investigation of existing behavior needed)throwIfNotOk()
The goal is to simplify and condense the retry logic. A future PR will address the other items in #3325.