Skip to content

Commit 9e33cf6

Browse files
Yannicaiuto
authored andcommitted
[repository/downloader] Add support for multiple header values
Headers can generally be specified multiple times, not just once. This refactoring is prerequisite for adding support for getting credentials from the credential helper. Note that this does not change the Starlark API or the qualifier for the gRPC remote downloader (both of which need to go through the process for incompatible changes - which should hopefully be pretty straight forward to do as this feature doesn't seem to be used that much AFAICT). Progress on bazelbuild#15856 Closes bazelbuild#16260. PiperOrigin-RevId: 474770302 Change-Id: I74620e36481414a41108991bc61321d104a6d39a
1 parent 966cea9 commit 9e33cf6

16 files changed

+171
-118
lines changed

src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/DelegatingDownloader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public void setDelegate(@Nullable Downloader delegate) {
4747
@Override
4848
public void download(
4949
List<URL> urls,
50-
Map<URI, Map<String, String>> authHeaders,
50+
Map<URI, Map<String, List<String>>> authHeaders,
5151
Optional<Checksum> checksum,
5252
String canonicalId,
5353
Path destination,

src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/DownloadManager.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public void setNetrcCreds(Credentials netrcCreds) {
111111
*/
112112
public Path download(
113113
List<URL> originalUrls,
114-
Map<URI, Map<String, String>> authHeaders,
114+
Map<URI, Map<String, List<String>>> authHeaders,
115115
Optional<Checksum> checksum,
116116
String canonicalId,
117117
Optional<String> type,
@@ -133,7 +133,7 @@ public Path download(
133133
// ctx.download{,_and_extract}, this not the case. Should be refactored to handle all .netrc
134134
// parsing in one place, in Java code (similarly to #downloadAndReadOneUrl).
135135
ImmutableList<URL> rewrittenUrls = ImmutableList.copyOf(originalUrls);
136-
Map<URI, Map<String, String>> rewrittenAuthHeaders = authHeaders;
136+
Map<URI, Map<String, List<String>>> rewrittenAuthHeaders = authHeaders;
137137

138138
if (rewriter != null) {
139139
ImmutableList<UrlRewriter.RewrittenURL> rewrittenUrlMappings = rewriter.amend(originalUrls);
@@ -303,7 +303,7 @@ public byte[] downloadAndReadOneUrl(
303303
if (Thread.interrupted()) {
304304
throw new InterruptedException();
305305
}
306-
Map<URI, Map<String, String>> authHeaders = ImmutableMap.of();
306+
Map<URI, Map<String, List<String>>> authHeaders = ImmutableMap.of();
307307
ImmutableList<URL> rewrittenUrls = ImmutableList.of(originalUrl);
308308

309309
if (netrcCreds != null) {
@@ -314,7 +314,7 @@ public byte[] downloadAndReadOneUrl(
314314
authHeaders =
315315
ImmutableMap.of(
316316
originalUrl.toURI(),
317-
ImmutableMap.of(headers.getKey(), headers.getValue().get(0)));
317+
ImmutableMap.of(headers.getKey(), ImmutableList.of(headers.getValue().get(0))));
318318
}
319319
} catch (URISyntaxException e) {
320320
// If the credentials extraction failed, we're letting bazel try without credentials.

src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/Downloader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public interface Downloader {
4242
*/
4343
void download(
4444
List<URL> urls,
45-
Map<URI, Map<String, String>> authHeaders,
45+
Map<URI, Map<String, List<String>>> authHeaders,
4646
Optional<Checksum> checksum,
4747
String canonicalId,
4848
Path output,

src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnector.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ private int scale(int unscaled) {
9191
return Math.round(unscaled * timeoutScaling);
9292
}
9393

94-
URLConnection connect(URL originalUrl, Function<URL, ImmutableMap<String, String>> requestHeaders)
94+
URLConnection connect(
95+
URL originalUrl, Function<URL, ImmutableMap<String, List<String>>> requestHeaders)
9596
throws IOException {
9697

9798
if (Thread.interrupted()) {
@@ -116,13 +117,16 @@ URLConnection connect(URL originalUrl, Function<URL, ImmutableMap<String, String
116117
COMPRESSED_EXTENSIONS.contains(HttpUtils.getExtension(url.getPath()))
117118
|| COMPRESSED_EXTENSIONS.contains(HttpUtils.getExtension(originalUrl.getPath()));
118119
connection.setInstanceFollowRedirects(false);
119-
for (Map.Entry<String, String> entry : requestHeaders.apply(url).entrySet()) {
120+
for (Map.Entry<String, List<String>> entry : requestHeaders.apply(url).entrySet()) {
120121
if (isAlreadyCompressed && Ascii.equalsIgnoreCase(entry.getKey(), "Accept-Encoding")) {
121122
// We're not going to ask for compression if we're downloading a file that already
122123
// appears to be compressed.
123124
continue;
124125
}
125-
connection.addRequestProperty(entry.getKey(), entry.getValue());
126+
String key = entry.getKey();
127+
for (String value : entry.getValue()) {
128+
connection.addRequestProperty(key, value);
129+
}
126130
}
127131
if (connection.getRequestProperty("User-Agent") == null) {
128132
connection.setRequestProperty("User-Agent", USER_AGENT_VALUE);

src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpConnectorMultiplexer.java

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414

1515
package com.google.devtools.build.lib.bazel.repository.downloader;
1616

17+
import com.google.common.annotations.VisibleForTesting;
1718
import com.google.common.base.Function;
1819
import com.google.common.base.Optional;
1920
import com.google.common.base.Preconditions;
21+
import com.google.common.collect.ImmutableList;
2022
import com.google.common.collect.ImmutableMap;
2123
import com.google.devtools.build.lib.analysis.BlazeVersionInfo;
2224
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
@@ -30,6 +32,7 @@
3032
import java.net.URL;
3133
import java.net.URLConnection;
3234
import java.util.HashMap;
35+
import java.util.List;
3336
import java.util.Map;
3437

3538
/**
@@ -47,12 +50,12 @@
4750
@ThreadSafe
4851
final class HttpConnectorMultiplexer {
4952

50-
private static final ImmutableMap<String, String> REQUEST_HEADERS =
53+
private static final ImmutableMap<String, List<String>> REQUEST_HEADERS =
5154
ImmutableMap.of(
5255
"Accept-Encoding",
53-
"gzip",
56+
ImmutableList.of("gzip"),
5457
"User-Agent",
55-
"Bazel/" + BlazeVersionInfo.instance().getReleaseName());
58+
ImmutableList.of("Bazel/" + BlazeVersionInfo.instance().getReleaseName()));
5659

5760
private final EventHandler eventHandler;
5861
private final HttpConnector connector;
@@ -84,50 +87,53 @@ public HttpStream connect(URL url, Optional<Checksum> checksum) throws IOExcepti
8487
*
8588
* @param url the URL to conenct to. can be: file, http, or https
8689
* @param checksum checksum lazily checked on entire payload, or empty to disable
87-
* @return an {@link InputStream} of response payload
90+
* @param authHeaders the authentication headers
8891
* @param type extension, e.g. "tar.gz" to force on downloaded filename, or empty to not do this
92+
* @return an {@link InputStream} of response payload
8993
* @throws IOException if all mirrors are down and contains suppressed exception of each attempt
9094
* @throws InterruptedIOException if current thread is being cast into oblivion
9195
* @throws IllegalArgumentException if {@code urls} is empty or has an unsupported protocol
9296
*/
9397
public HttpStream connect(
9498
URL url,
9599
Optional<Checksum> checksum,
96-
Map<URI, Map<String, String>> authHeaders,
100+
Map<URI, Map<String, List<String>>> authHeaders,
97101
Optional<String> type)
98102
throws IOException {
99103
Preconditions.checkArgument(HttpUtils.isUrlSupportedByDownloader(url));
100104
if (Thread.interrupted()) {
101105
throw new InterruptedIOException();
102106
}
103-
Function<URL, ImmutableMap<String, String>> headerFunction =
107+
Function<URL, ImmutableMap<String, List<String>>> headerFunction =
104108
getHeaderFunction(REQUEST_HEADERS, authHeaders);
105109
URLConnection connection = connector.connect(url, headerFunction);
106110
return httpStreamFactory.create(
107111
connection,
108112
url,
109113
checksum,
110-
(Throwable cause, ImmutableMap<String, String> extraHeaders) -> {
114+
(Throwable cause, ImmutableMap<String, List<String>> extraHeaders) -> {
111115
eventHandler.handle(
112116
Event.progress(String.format("Lost connection for %s due to %s", url, cause)));
113117
return connector.connect(
114118
connection.getURL(),
115119
newUrl ->
116-
new ImmutableMap.Builder<String, String>()
120+
new ImmutableMap.Builder<String, List<String>>()
117121
.putAll(headerFunction.apply(newUrl))
118122
.putAll(extraHeaders)
119123
.buildOrThrow());
120124
},
121125
type);
122126
}
123127

124-
public static Function<URL, ImmutableMap<String, String>> getHeaderFunction(
125-
Map<String, String> baseHeaders, Map<URI, Map<String, String>> additionalHeaders) {
128+
@VisibleForTesting
129+
static Function<URL, ImmutableMap<String, List<String>>> getHeaderFunction(
130+
Map<String, List<String>> baseHeaders,
131+
Map<URI, Map<String, List<String>>> additionalHeaders) {
126132
return url -> {
127-
ImmutableMap<String, String> headers = ImmutableMap.copyOf(baseHeaders);
133+
ImmutableMap<String, List<String>> headers = ImmutableMap.copyOf(baseHeaders);
128134
try {
129135
if (additionalHeaders.containsKey(url.toURI())) {
130-
Map<String, String> newHeaders = new HashMap<>(headers);
136+
Map<String, List<String>> newHeaders = new HashMap<>(headers);
131137
newHeaders.putAll(additionalHeaders.get(url.toURI()));
132138
headers = ImmutableMap.copyOf(newHeaders);
133139
}

src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/HttpDownloader.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public void setTimeoutScaling(float timeoutScaling) {
6363
@Override
6464
public void download(
6565
List<URL> urls,
66-
Map<URI, Map<String, String>> authHeaders,
66+
Map<URI, Map<String, List<String>>> authHeaders,
6767
Optional<Checksum> checksum,
6868
String canonicalId,
6969
Path destination,
@@ -132,7 +132,7 @@ public void download(
132132
/** Downloads the contents of one URL and reads it into a byte array. */
133133
public byte[] downloadAndReadOneUrl(
134134
URL url,
135-
Map<URI, Map<String, String>> authHeaders,
135+
Map<URI, Map<String, List<String>>> authHeaders,
136136
ExtendedEventHandler eventHandler,
137137
Map<String, String> clientEnv)
138138
throws IOException, InterruptedException {

src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/RetryingInputStream.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
package com.google.devtools.build.lib.bazel.repository.downloader;
1616

1717
import com.google.common.base.Strings;
18+
import com.google.common.collect.ImmutableList;
1819
import com.google.common.collect.ImmutableMap;
1920
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
2021
import java.io.IOException;
2122
import java.io.InputStream;
2223
import java.io.InterruptedIOException;
2324
import java.net.SocketTimeoutException;
2425
import java.net.URLConnection;
26+
import java.util.List;
2527
import java.util.Vector;
2628
import java.util.concurrent.atomic.AtomicInteger;
2729
import java.util.concurrent.atomic.AtomicLong;
@@ -38,11 +40,9 @@ class RetryingInputStream extends InputStream {
3840

3941
/** Lambda for establishing a connection. */
4042
interface Reconnector {
41-
4243
/** Establishes a connection with the same parameters as what was passed to us initially. */
43-
URLConnection connect(
44-
Throwable cause, ImmutableMap<String, String> extraHeaders)
45-
throws IOException;
44+
URLConnection connect(Throwable cause, ImmutableMap<String, List<String>> extraHeaders)
45+
throws IOException;
4646
}
4747

4848
private volatile InputStream delegate;
@@ -117,11 +117,12 @@ private void reconnectWhereWeLeftOff(IOException cause) throws IOException {
117117
URLConnection connection;
118118
long amountRead = toto.get();
119119
if (amountRead == 0) {
120-
connection = reconnector.connect(cause, ImmutableMap.<String, String>of());
120+
connection = reconnector.connect(cause, ImmutableMap.of());
121121
} else {
122122
connection =
123123
reconnector.connect(
124-
cause, ImmutableMap.of("Range", String.format("bytes=%d-", amountRead)));
124+
cause,
125+
ImmutableMap.of("Range", ImmutableList.of(String.format("bytes=%d-", amountRead))));
125126
if (!Strings.nullToEmpty(connection.getHeaderField("Content-Range"))
126127
.startsWith(String.format("bytes %d-", amountRead))) {
127128
throw new IOException(String.format(

src/main/java/com/google/devtools/build/lib/bazel/repository/downloader/UrlRewriter.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,11 @@ public ImmutableList<RewrittenURL> amend(List<URL> urls) {
126126
* @param authHeaders A map of the URLs and their corresponding auth tokens.
127127
* @return A map of the updated authentication headers.
128128
*/
129-
public Map<URI, Map<String, String>> updateAuthHeaders(
130-
List<RewrittenURL> urls, Map<URI, Map<String, String>> authHeaders, Credentials netrcCreds) {
131-
Map<URI, Map<String, String>> updatedAuthHeaders = new HashMap<>(authHeaders);
129+
public Map<URI, Map<String, List<String>>> updateAuthHeaders(
130+
List<RewrittenURL> urls,
131+
Map<URI, Map<String, List<String>>> authHeaders,
132+
Credentials netrcCreds) {
133+
Map<URI, Map<String, List<String>>> updatedAuthHeaders = new HashMap<>(authHeaders);
132134

133135
for (RewrittenURL url : urls) {
134136
// if URL was not re-written by UrlRewriter in first place, we should not attach auth headers
@@ -142,7 +144,8 @@ public Map<URI, Map<String, String>> updateAuthHeaders(
142144
try {
143145
String token =
144146
"Basic " + Base64.getEncoder().encodeToString(userInfo.getBytes(ISO_8859_1));
145-
updatedAuthHeaders.put(url.url().toURI(), ImmutableMap.of("Authorization", token));
147+
updatedAuthHeaders.put(
148+
url.url().toURI(), ImmutableMap.of("Authorization", ImmutableList.of(token)));
146149
} catch (URISyntaxException e) {
147150
// If the credentials extraction failed, we're letting bazel try without credentials.
148151
}
@@ -159,7 +162,8 @@ public Map<URI, Map<String, String>> updateAuthHeaders(
159162
if (firstAuthHeader.getValue() != null && !firstAuthHeader.getValue().isEmpty()) {
160163
updatedAuthHeaders.put(
161164
url.url().toURI(),
162-
ImmutableMap.of(firstAuthHeader.getKey(), firstAuthHeader.getValue().get(0)));
165+
ImmutableMap.of(
166+
firstAuthHeader.getKey(), ImmutableList.of(firstAuthHeader.getValue().get(0))));
163167
}
164168
} catch (URISyntaxException | IOException e) {
165169
// If the credentials extraction failed, we're letting bazel try without credentials.

src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkBaseExternalContext.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,9 @@ protected void checkInOutputDirectory(String operation, StarlarkPath path)
154154
* authentication, adding those headers is enough; for other forms of authentication other
155155
* measures might be necessary.
156156
*/
157-
private static ImmutableMap<URI, Map<String, String>> getAuthHeaders(Map<String, Dict<?, ?>> auth)
158-
throws RepositoryFunctionException, EvalException {
159-
ImmutableMap.Builder<URI, Map<String, String>> headers = new ImmutableMap.Builder<>();
157+
private static ImmutableMap<URI, Map<String, List<String>>> getAuthHeaders(
158+
Map<String, Dict<?, ?>> auth) throws RepositoryFunctionException, EvalException {
159+
ImmutableMap.Builder<URI, Map<String, List<String>>> headers = new ImmutableMap.Builder<>();
160160
for (Map.Entry<String, Dict<?, ?>> entry : auth.entrySet()) {
161161
try {
162162
URL url = new URL(entry.getKey());
@@ -174,7 +174,9 @@ private static ImmutableMap<URI, Map<String, String>> getAuthHeaders(Map<String,
174174
url.toURI(),
175175
ImmutableMap.of(
176176
"Authorization",
177-
"Basic " + Base64.getEncoder().encodeToString(credentials.getBytes(UTF_8))));
177+
ImmutableList.of(
178+
"Basic "
179+
+ Base64.getEncoder().encodeToString(credentials.getBytes(UTF_8)))));
178180
} else if ("pattern".equals(authMap.get("type"))) {
179181
if (!authMap.containsKey("pattern")) {
180182
throw Starlark.errorf(
@@ -201,7 +203,7 @@ private static ImmutableMap<URI, Map<String, String>> getAuthHeaders(Map<String,
201203
result = result.replaceAll(demarcatedComponent, (String) authMap.get(component));
202204
}
203205

204-
headers.put(url.toURI(), ImmutableMap.of("Authorization", result));
206+
headers.put(url.toURI(), ImmutableMap.of("Authorization", ImmutableList.of(result)));
205207
}
206208
}
207209
} catch (MalformedURLException e) {
@@ -445,7 +447,7 @@ public StructImpl download(
445447
String integrity,
446448
StarlarkThread thread)
447449
throws RepositoryFunctionException, EvalException, InterruptedException {
448-
ImmutableMap<URI, Map<String, String>> authHeaders =
450+
ImmutableMap<URI, Map<String, List<String>>> authHeaders =
449451
getAuthHeaders(getAuthContents(authUnchecked, "auth"));
450452

451453
ImmutableList<URL> urls =
@@ -634,7 +636,7 @@ public StructImpl downloadAndExtract(
634636
Dict<?, ?> renameFiles, // <String, String> expected
635637
StarlarkThread thread)
636638
throws RepositoryFunctionException, InterruptedException, EvalException {
637-
ImmutableMap<URI, Map<String, String>> authHeaders =
639+
ImmutableMap<URI, Map<String, List<String>>> authHeaders =
638640
getAuthHeaders(getAuthContents(auth, "auth"));
639641

640642
ImmutableList<URL> urls =

src/main/java/com/google/devtools/build/lib/remote/downloader/GrpcRemoteDownloader.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import build.bazel.remote.execution.v2.RequestMetadata;
2424
import com.google.common.annotations.VisibleForTesting;
2525
import com.google.common.base.Strings;
26+
import com.google.common.collect.Iterables;
2627
import com.google.devtools.build.lib.bazel.repository.downloader.Checksum;
2728
import com.google.devtools.build.lib.bazel.repository.downloader.Downloader;
2829
import com.google.devtools.build.lib.bazel.repository.downloader.HashOutputStream;
@@ -106,7 +107,7 @@ public void close() {
106107
@Override
107108
public void download(
108109
List<URL> urls,
109-
Map<URI, Map<String, String>> authHeaders,
110+
Map<URI, Map<String, List<String>>> authHeaders,
110111
com.google.common.base.Optional<Checksum> checksum,
111112
String canonicalId,
112113
Path destination,
@@ -148,7 +149,7 @@ public void download(
148149
static FetchBlobRequest newFetchBlobRequest(
149150
String instanceName,
150151
List<URL> urls,
151-
Map<URI, Map<String, String>> authHeaders,
152+
Map<URI, Map<String, List<String>>> authHeaders,
152153
com.google.common.base.Optional<Checksum> checksum,
153154
String canonicalId) {
154155
FetchBlobRequest.Builder requestBuilder =
@@ -197,13 +198,17 @@ private OutputStream newOutputStream(
197198
return out;
198199
}
199200

200-
private static String authHeadersJson(Map<URI, Map<String, String>> authHeaders) {
201+
private static String authHeadersJson(Map<URI, Map<String, List<String>>> authHeaders) {
201202
Map<String, JsonObject> subObjects = new TreeMap<>();
202-
for (Map.Entry<URI, Map<String, String>> entry : authHeaders.entrySet()) {
203+
for (Map.Entry<URI, Map<String, List<String>>> entry : authHeaders.entrySet()) {
203204
JsonObject subObject = new JsonObject();
204-
Map<String, String> orderedHeaders = new TreeMap<>(entry.getValue());
205-
for (Map.Entry<String, String> subEntry : orderedHeaders.entrySet()) {
206-
subObject.addProperty(subEntry.getKey(), subEntry.getValue());
205+
Map<String, List<String>> orderedHeaders = new TreeMap<>(entry.getValue());
206+
for (Map.Entry<String, List<String>> subEntry : orderedHeaders.entrySet()) {
207+
// TODO(yannic): Introduce incompatible flag for including all headers, not just the first.
208+
String value = Iterables.getFirst(subEntry.getValue(), null);
209+
if (value != null) {
210+
subObject.addProperty(subEntry.getKey(), value);
211+
}
207212
}
208213
subObjects.put(entry.getKey().toString(), subObject);
209214
}

0 commit comments

Comments
 (0)