HttpHeaders are no longer a MultiValueMap

This change removes the `MultiValueMap` nature of `HttpHeaders`, since
it inherits APIs that do not align well with underlying server
implementations. Notably, methods that allows to iterate over the whole
collection of headers are susceptible to artificially introduced
duplicates when multiple casings are used for a given header, depending
on the underlying implementation.

This change includes a dedicated key set implementation to support
iterator-based removal, and either keeps map method implementations that
are relevant or introduces header-focused methods that have a similar
responsibility (like `hasHeaderValues(String, List)` and
`containsHeaderValue(String, String)`).

In order to nudge users away from using an HttpHeaders as a Map, the
`asSingleValueMap` view is deprecated. In order to offer an escape
hatch to users that do make use of the `MultiValueMap` API, a similar
`asMultiValueMap` view is introduced but is immediately marked as
deprecated.

This change also adds map-like but header-focused assertions to
`HttpHeadersAssert`, since it cannot extend `AbstractMapAssert` anymore.

Closes gh-33913
This commit is contained in:
Simon Baslé
2024-12-02 14:55:27 +01:00
parent 1e0ef99b0c
commit 0c6f5d7d29
100 changed files with 1116 additions and 508 deletions
@@ -301,7 +301,7 @@ class ResponseEntityExceptionHandlerTests {
ResponseEntity<Object> responseEntity =
testException(new NoHandlerFoundException("GET", "/resource", requestHeaders));
assertThat(responseEntity.getHeaders()).isEmpty();
assertThat(responseEntity.getHeaders().isEmpty()).isTrue();
}
@Test
@@ -3576,7 +3576,21 @@ class ServletAnnotationControllerHandlerMethodTests extends AbstractServletHandl
@RequestMapping("/httpHeaders")
public void httpHeaders(@RequestHeader HttpHeaders headers, Writer writer) throws IOException {
assertThat(headers.getContentType()).as("Invalid Content-Type").isEqualTo(new MediaType("text", "html"));
multiValueMap(headers, writer);
for (Iterator<Map.Entry<String, List<String>>> it1 = headers.headerSet().iterator(); it1.hasNext();) {
Map.Entry<String, List<String>> entry = it1.next();
writer.write(entry.getKey() + "=[");
for (Iterator<String> it2 = entry.getValue().iterator(); it2.hasNext();) {
String value = it2.next();
writer.write(value);
if (it2.hasNext()) {
writer.write(',');
}
}
writer.write(']');
if (it1.hasNext()) {
writer.write(',');
}
}
}
}