Skip to content

Commit f42d85e

Browse files
committed
#16612 optimize method RemoteWebDriver.downloadFile()
Instead of reading the whole file to a byte array, just save given InputStream directly to the file. Now it can download large files (I tried 4GB) while consuming very low memory.
1 parent 340ddc8 commit f42d85e

File tree

6 files changed

+89
-12
lines changed

6 files changed

+89
-12
lines changed

java/src/org/openqa/selenium/grid/node/local/LocalNode.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import java.net.URI;
5555
import java.net.URISyntaxException;
5656
import java.nio.file.attribute.BasicFileAttributes;
57+
import java.nio.file.attribute.FileTime;
5758
import java.time.Clock;
5859
import java.time.Duration;
5960
import java.time.Instant;
@@ -844,12 +845,15 @@ private HttpResponse getDownloadedFile(File downloadsDirectory, String fileName)
844845
BasicFileAttributes attributes = readAttributes(file.toPath(), BasicFileAttributes.class);
845846
return new HttpResponse()
846847
.setHeader("Content-Type", MediaType.OCTET_STREAM.toString())
847-
.setHeader(
848-
"Last-Modified",
849-
HTTP_DATE_FORMAT.format(attributes.lastModifiedTime().toInstant().atZone(UTC)))
848+
.setHeader("Content-Length", String.valueOf(attributes.size()))
849+
.setHeader("Last-Modified", lastModifiedHeader(attributes.lastModifiedTime()))
850850
.setContent(Contents.file(file));
851851
}
852852

853+
private String lastModifiedHeader(FileTime fileTime) {
854+
return HTTP_DATE_FORMAT.format(fileTime.toInstant().atZone(UTC));
855+
}
856+
853857
private File findDownloadedFile(File downloadsDirectory, String filename)
854858
throws WebDriverException {
855859
List<File> matchingFiles =

java/src/org/openqa/selenium/remote/http/Contents.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ public static Supplier file(final File file) {
8989
return new FileContentSupplier(file);
9090
}
9191

92+
public static Supplier fromStream(InputStream stream, long length) {
93+
return new InputStreamContentSupplier(stream, length);
94+
}
95+
9296
public static Supplier bytes(byte[] bytes) {
9397
return new BytesContentSupplier(bytes);
9498
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.remote.http;
19+
20+
import static java.nio.charset.StandardCharsets.UTF_8;
21+
22+
import java.io.IOException;
23+
import java.io.InputStream;
24+
import java.nio.charset.Charset;
25+
import org.openqa.selenium.internal.Require;
26+
27+
class InputStreamContentSupplier implements Contents.Supplier {
28+
29+
private static final int MAX_TEXT_RESPONSE_SIZE = 256 * 1024 * 1024;
30+
private final InputStream stream;
31+
private final long length;
32+
33+
InputStreamContentSupplier(InputStream stream, long length) {
34+
this.stream = Require.nonNull("InputStream", stream);
35+
this.length = length;
36+
}
37+
38+
@Override
39+
public InputStream get() {
40+
return stream;
41+
}
42+
43+
@Override
44+
public long length() {
45+
return length;
46+
}
47+
48+
public void close() {
49+
try {
50+
stream.close();
51+
} catch (IOException ignore) {
52+
}
53+
}
54+
55+
@Override
56+
public String toString() {
57+
return String.format("Contents.fromStream(%s bytes)", length);
58+
}
59+
60+
@Override
61+
public String contentAsString(Charset charset) {
62+
if (length > MAX_TEXT_RESPONSE_SIZE) {
63+
throw new UnsupportedOperationException("Cannot print out too large stream content");
64+
}
65+
try {
66+
return new String(stream.readAllBytes(), UTF_8);
67+
} catch (IOException e) {
68+
throw new RuntimeException(e);
69+
}
70+
}
71+
}

java/src/org/openqa/selenium/remote/http/jdk/JdkHttpClient.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.auto.service.AutoService;
2121
import java.io.ByteArrayOutputStream;
2222
import java.io.IOException;
23+
import java.io.InputStream;
2324
import java.io.UncheckedIOException;
2425
import java.net.Authenticator;
2526
import java.net.PasswordAuthentication;
@@ -440,8 +441,7 @@ private HttpResponse execute0(HttpRequest req) throws UncheckedIOException {
440441

441442
LOG.log(Level.FINE, "Executing request: {0}", req);
442443
long start = System.currentTimeMillis();
443-
444-
BodyHandler<byte[]> byteHandler = BodyHandlers.ofByteArray();
444+
BodyHandler<InputStream> byteHandler = BodyHandlers.ofInputStream();
445445
try {
446446
HttpMethod method = req.getMethod();
447447
URI rawUri = messages.getRawUri(req);
@@ -456,7 +456,7 @@ private HttpResponse execute0(HttpRequest req) throws UncheckedIOException {
456456
}
457457

458458
java.net.http.HttpRequest request = messages.createRequest(req, method, rawUri);
459-
java.net.http.HttpResponse<byte[]> response = client.send(request, byteHandler);
459+
java.net.http.HttpResponse<InputStream> response = client.send(request, byteHandler);
460460

461461
switch (response.statusCode()) {
462462
case 303:

java/src/org/openqa/selenium/remote/http/jdk/JdkHttpMessages.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import static java.nio.charset.StandardCharsets.UTF_8;
2121

22+
import java.io.InputStream;
2223
import java.net.URI;
2324
import java.net.URLEncoder;
2425
import java.net.http.HttpRequest.BodyPublisher;
@@ -152,7 +153,7 @@ public URI getRawUri(HttpRequest req) {
152153
return URI.create(rawUrl);
153154
}
154155

155-
public HttpResponse createResponse(java.net.http.HttpResponse<byte[]> response) {
156+
public HttpResponse createResponse(java.net.http.HttpResponse<InputStream> response) {
156157
HttpResponse res = new HttpResponse();
157158
res.setStatus(response.statusCode());
158159
response
@@ -163,10 +164,8 @@ public HttpResponse createResponse(java.net.http.HttpResponse<byte[]> response)
163164
values.stream()
164165
.filter(Objects::nonNull)
165166
.forEach(value -> res.addHeader(name, value)));
166-
byte[] responseBody = response.body();
167-
if (responseBody != null) {
168-
res.setContent(Contents.bytes(responseBody));
169-
}
167+
long length = response.headers().firstValueAsLong("Content-Length").orElse(-1);
168+
res.setContent(Contents.fromStream(response.body(), length));
170169

171170
return res;
172171
}

java/test/org/openqa/selenium/grid/router/ReverseProxyEndToEndTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ private static void waitUntilReady(Server<?> server, Duration duration) {
125125
.until(
126126
c -> {
127127
HttpResponse response = c.execute(new HttpRequest(GET, "/status"));
128-
System.out.println(Contents.string(response));
129128
Map<String, Object> status = Values.get(response, MAP_TYPE);
130129
return Boolean.TRUE.equals(
131130
status != null && Boolean.parseBoolean(status.get("ready").toString()));

0 commit comments

Comments
 (0)