Add HttpHeaders.copyOf factory method

Prior to this commit, the `HttpHeaders` class would provide constructor
variants where the instances are are backed by the existing headers
collection given as a parameter.

While such constructors are clearly documented and meant for internal
usage, there are cases where developers would like to copy the data from
an existing headers instance without being backed by the same collection
instance and thus, being mutated from some place else.

This commit introduces new factory methods `HttpHeaders.copyOf` for this
purpose. While this name aligns with some of the Java collections
factory methods, in this case the returned instance is not immutable, on
purpose. `HttpHeaders` does not extends `MultiValueMap` anymore and
shouldn't be seen as such.

Closes: gh-34341

Signed-off-by: Bryce J. Fisher <bryce.fisher@gmail.com>
[brian.clozel@broadcom.com: reduce scope and update javadoc]
Signed-off-by: Brian Clozel <brian.clozel@broadcom.com>
This commit is contained in:
Bryce J. Fisher 2025-01-29 17:07:56 -07:00 committed by Brian Clozel
parent 7695be0079
commit 2a846c9594
3 changed files with 36 additions and 2 deletions

View File

@ -491,6 +491,25 @@ public class HttpHeaders implements Serializable {
}
}
/**
* Create a new {@code HttpHeaders} mutable instance and copy all header values given as a parameter.
* @param headers the headers to copy
* @since 7.0
*/
public static HttpHeaders copyOf(MultiValueMap<String, String> headers) {
HttpHeaders httpHeadersCopy = new HttpHeaders();
headers.forEach((key, values) -> httpHeadersCopy.put(key, new ArrayList<>(values)));
return httpHeadersCopy;
}
/**
* Create a new {@code HttpHeaders} mutable instance and copy all header values given as a parameter.
* @param httpHeaders the headers to copy
* @since 7.0
*/
public static HttpHeaders copyOf(HttpHeaders httpHeaders) {
return copyOf(httpHeaders.headers);
}
/**
* Get the list of header values for the given header name, if any.

View File

@ -49,7 +49,6 @@ class ReadOnlyHttpHeaders extends HttpHeaders {
@SuppressWarnings("serial")
private @Nullable List<MediaType> cachedAccept;
ReadOnlyHttpHeaders(MultiValueMap<String, String> headers) {
super(headers);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -77,6 +77,22 @@ class HttpHeadersTests {
writeable.setContentType(MediaType.APPLICATION_JSON);
}
@Test
void copyOfCopiesHeaders() {
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("X-Project", "Spring");
headers.add("X-Project", "Framework");
HttpHeaders readOnly = HttpHeaders.readOnlyHttpHeaders(headers);
assertThat(readOnly.getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
HttpHeaders writable = HttpHeaders.copyOf(readOnly);
writable.setContentType(MediaType.TEXT_PLAIN);
// content-type value is cached by ReadOnlyHttpHeaders
assertThat(readOnly.getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
assertThat(writable.getContentType()).isEqualTo(MediaType.TEXT_PLAIN);
assertThat(writable.get("X-Project")).contains("Spring", "Framework");
}
@Test
void getOrEmpty() {
String key = "FOO";