-
Notifications
You must be signed in to change notification settings - Fork 76
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
chore(JenkinsClient): use new HTTP client with better diagnostics #656
Conversation
We get more control of how redirects are handled and more visibility in to the HTTP response if needed for troubleshooting.
This is the failing test: TerasologyLauncher/src/test/java/org/terasology/launcher/repositories/JenkinsClientTest.java Lines 29 to 39 in 72dbf73
which uses mockito to throw an exception when the given URL's But HttpClient never calls I guess I should make that test override client#openStream instead. |
I then discovered this results in JenkinsClient being able to follow redirects to get the properties file, but then DownloadUtils isn't able to download the corresponding release. 🤦 |
This tries to be a minimal change from the previous version, but the mixture of streaming APIs used is not pretty.
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.
InterruptedExceptionI'm reluctant to catch Or maybe this is the wrong interpretation. Because it's not the current thread that's been interrupted—that was some reader thread that we were waiting on. Maybe that should be considered an implementation detail and it should be wrapped up in a HttpClientWe don't really want to be creating a new HttpClient instance for every request. Especially since we tend to make a lot of requests against the same host. It'd be nice to move that to some Service or other singleton. That would let us move those HttpClient configuration lines out of the request methods too. It's not a huge deal, but we could probably shave off some time from the big stack of requests JenkinsClient does to get a displayVersion for every build. DownloadTask & DownloadUtils.downloadToFileIn the name of incremental progress (and because this rabbit hole was already far enough from where I started out), I tried to leave most of the existing DownloadUtils stuff intact and mostly just swap out the part responsible for getting the InputStream started. But it's all a jumble of concurrency and stream models. I was thinking then I find out we already have a It wouldn't be a huge project to rewire, but more than I wanted to bite off without checking for scope creep. There's also the part where |
instead of relying on the URL argument to be used in a specific way
I almost got to write that without mockito, but "static declarations in inner classes" are not supported until language level 16. anyway, test is fixed now, so taking this out of Draft status. |
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 like those changes 👍
Feel free to either merge straight away or address the review comments - nothing blocking here from my side.
@@ -79,7 +80,7 @@ public void install(GameRelease release, ProgressListener listener) throws IOExc | |||
} | |||
} | |||
|
|||
private void download(GameRelease release, Path targetLocation, ProgressListener listener) throws DownloadException, IOException { | |||
private void download(GameRelease release, Path targetLocation, ProgressListener listener) throws DownloadException, IOException, InterruptedException { |
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'm also not sure about the InterruptedException
. I would have thought that this is similar to any other exception that could cause the download to fail...
However, I do think this should be pretty rare, and unless we get a bunch of complaints from users we should be good either way.
URLConnection connection = url.openConnection(); | ||
connection.setConnectTimeout(10 * 1000); | ||
return connection.getInputStream(); | ||
var client = HttpClient.newBuilder() |
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.
Should we add a //TODO: ...
to reuse one client for multiple requests?
connection.setConnectTimeout(10 * 1000); | ||
return connection.getInputStream(); | ||
var client = HttpClient.newBuilder() | ||
.connectTimeout(Duration.ofSeconds(10)) |
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.
👍
.followRedirects(HttpClient.Redirect.NORMAL) | ||
.build(); | ||
var request = HttpRequest.newBuilder(url.toURI()).build(); | ||
var response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); |
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.
Not sure I like those var
s too much ... 🤔 When building a client or request it's clear to me what the type most likely is, but I'm less sure what the response actually is. Sure, my IDE does show it to, but unfortunately Github does not 😕
I don't think we should be as strict as the presto community, as I think that var
does have it's place for local fields in many places...
I probably just need to get used to them 😅
@@ -73,8 +82,8 @@ Properties requestProperties(final URL artifactUrl) { | |||
final Properties properties = new Properties(); | |||
properties.load(inputStream); | |||
return properties; | |||
} catch (IOException e) { | |||
e.printStackTrace(); | |||
} catch (IOException | URISyntaxException | InterruptedException e) { |
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.
Why do we catch the InterruptedException
here but not in the other places?
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.
oh, because it was used in Optional.map, and the type of thing Optional.map takes cannot throw checked exceptions
listener.update(0); | ||
|
||
final HttpURLConnection connection = getConnectedDownloadConnection(downloadURL); | ||
var result = getConnectedDownloadConnection(downloadURL); |
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.
Hm, I either want an explicit type here, or a better name for getConnectedDownloadConnection
🙈
try (var mockedClientClass = mockStatic(JenkinsClient.class)) { | ||
mockedClientClass.when(() -> JenkinsClient.openStream(any())).thenThrow(IOException.class); |
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.
Ah, we mock only the static part of JenkinsClient
here, right?
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.
just double-checked and I see docs saying "Represents an active mock of a type's static methods," so yes.
I was trying to review another PR and I noticed none of the preview releases were showing up. One of our Jenkins-as-repository / HTTPS / redirect sorts of issues. So I tried swapping out the JenkinsClient#openStream implementation for one that used the newer HttpClient from Java 11.
We do get more control of how redirects are handled and more visibility in to the HTTP response if needed for troubleshooting.
Looks to be successfully following the redirects too.
It is also awfully verbose for this simple use case.
then I spent like two hours going through mailing list archives looking for the part where someone explained why HttpRequest takes a
URI
, not aURL
. Surely that must have been a FAQ while it was going through its development and incubator phase, right? But I haven't found an answer yet, or even anyone on the net-dev list raising the question.