Avoided repeated creation of ReadOnlyHttpHeaders wrapper

See gh-24680
This commit is contained in:
Rossen Stoyanchev 2020-05-08 14:07:30 +01:00
parent 3276f81851
commit df99889aa6
10 changed files with 99 additions and 62 deletions

View File

@ -1769,7 +1769,9 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
/** /**
* Apply a read-only {@code HttpHeaders} wrapper around the given headers. * Apply a read-only {@code HttpHeaders} wrapper around the given headers
* that also caches the parsed representations of the "Accept" and
* "Content-Type" headers.
*/ */
public static HttpHeaders readOnlyHttpHeaders(MultiValueMap<String, String> headers) { public static HttpHeaders readOnlyHttpHeaders(MultiValueMap<String, String> headers) {
Assert.notNull(headers, "HttpHeaders must not be null"); Assert.notNull(headers, "HttpHeaders must not be null");

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -35,10 +36,22 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
private boolean executed = false; private boolean executed = false;
@Nullable
private HttpHeaders readOnlyHeaders;
@Override @Override
public final HttpHeaders getHeaders() { public final HttpHeaders getHeaders() {
return (this.executed ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers); if (this.readOnlyHeaders != null) {
return this.readOnlyHeaders;
}
else if (this.executed) {
this.readOnlyHeaders = HttpHeaders.readOnlyHttpHeaders(this.headers);
return this.readOnlyHeaders;
}
else {
return this.headers;
}
} }
@Override @Override

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -60,6 +60,9 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
private final List<Supplier<? extends Publisher<Void>>> commitActions = new ArrayList<>(4); private final List<Supplier<? extends Publisher<Void>>> commitActions = new ArrayList<>(4);
@Nullable
private HttpHeaders readOnlyHeaders;
public AbstractClientHttpRequest() { public AbstractClientHttpRequest() {
this(new HttpHeaders()); this(new HttpHeaders());
@ -74,10 +77,16 @@ public abstract class AbstractClientHttpRequest implements ClientHttpRequest {
@Override @Override
public HttpHeaders getHeaders() { public HttpHeaders getHeaders() {
if (State.COMMITTED.equals(this.state.get())) { if (this.readOnlyHeaders != null) {
return HttpHeaders.readOnlyHttpHeaders(this.headers); return this.readOnlyHeaders;
}
else if (State.COMMITTED.equals(this.state.get())) {
this.readOnlyHeaders = HttpHeaders.readOnlyHttpHeaders(this.headers);
return this.readOnlyHeaders;
}
else {
return this.headers;
} }
return this.headers;
} }
@Override @Override

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2018 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -47,6 +47,9 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
private boolean bodyUsed = false; private boolean bodyUsed = false;
@Nullable
private HttpHeaders readOnlyHeaders;
/** /**
* Construct a new instance of the ServletServerHttpResponse based on the given {@link HttpServletResponse}. * Construct a new instance of the ServletServerHttpResponse based on the given {@link HttpServletResponse}.
@ -74,7 +77,16 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
@Override @Override
public HttpHeaders getHeaders() { public HttpHeaders getHeaders() {
return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers); if (this.readOnlyHeaders != null) {
return this.readOnlyHeaders;
}
else if (this.headersWritten) {
this.readOnlyHeaders = HttpHeaders.readOnlyHttpHeaders(this.headers);
return this.readOnlyHeaders;
}
else {
return this.headers;
}
} }
@Override @Override

View File

@ -75,6 +75,9 @@ public abstract class AbstractServerHttpResponse implements ServerHttpResponse {
private final List<Supplier<? extends Mono<Void>>> commitActions = new ArrayList<>(4); private final List<Supplier<? extends Mono<Void>>> commitActions = new ArrayList<>(4);
@Nullable
private HttpHeaders readOnlyHeaders;
public AbstractServerHttpResponse(DataBufferFactory dataBufferFactory) { public AbstractServerHttpResponse(DataBufferFactory dataBufferFactory) {
this(dataBufferFactory, new HttpHeaders()); this(dataBufferFactory, new HttpHeaders());
@ -155,8 +158,16 @@ public abstract class AbstractServerHttpResponse implements ServerHttpResponse {
@Override @Override
public HttpHeaders getHeaders() { public HttpHeaders getHeaders() {
return (this.state.get() == State.COMMITTED ? if (this.readOnlyHeaders != null) {
HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers); return this.readOnlyHeaders;
}
else if (this.state.get() == State.COMMITTED) {
this.readOnlyHeaders = HttpHeaders.readOnlyHttpHeaders(this.headers);
return this.readOnlyHeaders;
}
else {
return this.headers;
}
} }
@Override @Override

View File

@ -234,29 +234,28 @@ class DefaultClientResponse implements ClientResponse {
private class DefaultHeaders implements Headers { private class DefaultHeaders implements Headers {
private HttpHeaders delegate() { private final HttpHeaders httpHeaders =
return response.getHeaders(); HttpHeaders.readOnlyHttpHeaders(response.getHeaders());
}
@Override @Override
public OptionalLong contentLength() { public OptionalLong contentLength() {
return toOptionalLong(delegate().getContentLength()); return toOptionalLong(this.httpHeaders.getContentLength());
} }
@Override @Override
public Optional<MediaType> contentType() { public Optional<MediaType> contentType() {
return Optional.ofNullable(delegate().getContentType()); return Optional.ofNullable(this.httpHeaders.getContentType());
} }
@Override @Override
public List<String> header(String headerName) { public List<String> header(String headerName) {
List<String> headerValues = delegate().get(headerName); List<String> headerValues = this.httpHeaders.get(headerName);
return (headerValues != null ? headerValues : Collections.emptyList()); return (headerValues != null ? headerValues : Collections.emptyList());
} }
@Override @Override
public HttpHeaders asHttpHeaders() { public HttpHeaders asHttpHeaders() {
return HttpHeaders.readOnlyHttpHeaders(delegate()); return this.httpHeaders;
} }
private OptionalLong toOptionalLong(long value) { private OptionalLong toOptionalLong(long value) {

View File

@ -265,8 +265,8 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
.map(filter -> filter.apply(exchange)) .map(filter -> filter.apply(exchange))
.orElse(exchange) : exchange); .orElse(exchange) : exchange);
return new DefaultWebClient(filteredExchange, initUriBuilderFactory(), return new DefaultWebClient(filteredExchange, initUriBuilderFactory(),
this.defaultHeaders != null ? unmodifiableCopy(this.defaultHeaders) : null, this.defaultHeaders != null ? HttpHeaders.readOnlyHttpHeaders(this.defaultHeaders) : null,
this.defaultCookies != null ? unmodifiableCopy(this.defaultCookies) : null, this.defaultCookies != null ? HttpHeaders.readOnlyHttpHeaders(this.defaultCookies) : null,
this.defaultRequest, new DefaultWebClientBuilder(this)); this.defaultRequest, new DefaultWebClientBuilder(this));
} }
@ -308,10 +308,6 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
return factory; return factory;
} }
private static HttpHeaders unmodifiableCopy(HttpHeaders headers) {
return HttpHeaders.readOnlyHttpHeaders(headers);
}
private static <K, V> MultiValueMap<K, V> unmodifiableCopy(MultiValueMap<K, V> map) { private static <K, V> MultiValueMap<K, V> unmodifiableCopy(MultiValueMap<K, V> map) {
return CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<>(map)); return CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<>(map));
} }

View File

@ -260,61 +260,60 @@ class DefaultServerRequest implements ServerRequest {
private class DefaultHeaders implements Headers { private class DefaultHeaders implements Headers {
private HttpHeaders delegate() { private final HttpHeaders httpHeaders =
return request().getHeaders(); HttpHeaders.readOnlyHttpHeaders(request().getHeaders());
}
@Override @Override
public List<MediaType> accept() { public List<MediaType> accept() {
return delegate().getAccept(); return this.httpHeaders.getAccept();
} }
@Override @Override
public List<Charset> acceptCharset() { public List<Charset> acceptCharset() {
return delegate().getAcceptCharset(); return this.httpHeaders.getAcceptCharset();
} }
@Override @Override
public List<Locale.LanguageRange> acceptLanguage() { public List<Locale.LanguageRange> acceptLanguage() {
return delegate().getAcceptLanguage(); return this.httpHeaders.getAcceptLanguage();
} }
@Override @Override
public OptionalLong contentLength() { public OptionalLong contentLength() {
long value = delegate().getContentLength(); long value = this.httpHeaders.getContentLength();
return (value != -1 ? OptionalLong.of(value) : OptionalLong.empty()); return (value != -1 ? OptionalLong.of(value) : OptionalLong.empty());
} }
@Override @Override
public Optional<MediaType> contentType() { public Optional<MediaType> contentType() {
return Optional.ofNullable(delegate().getContentType()); return Optional.ofNullable(this.httpHeaders.getContentType());
} }
@Override @Override
public InetSocketAddress host() { public InetSocketAddress host() {
return delegate().getHost(); return this.httpHeaders.getHost();
} }
@Override @Override
public List<HttpRange> range() { public List<HttpRange> range() {
return delegate().getRange(); return this.httpHeaders.getRange();
} }
@Override @Override
public List<String> header(String headerName) { public List<String> header(String headerName) {
List<String> headerValues = delegate().get(headerName); List<String> headerValues = this.httpHeaders.get(headerName);
return (headerValues != null ? headerValues : Collections.emptyList()); return (headerValues != null ? headerValues : Collections.emptyList());
} }
@Override @Override
public HttpHeaders asHttpHeaders() { public HttpHeaders asHttpHeaders() {
return HttpHeaders.readOnlyHttpHeaders(delegate()); return this.httpHeaders;
} }
@Override @Override
public String toString() { public String toString() {
return delegate().toString(); return this.httpHeaders.toString();
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2019 the original author or authors. * Copyright 2002-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -60,6 +60,8 @@ public class DefaultClientResponseTests {
private ClientHttpResponse mockResponse; private ClientHttpResponse mockResponse;
private final HttpHeaders httpHeaders = new HttpHeaders();
private ExchangeStrategies mockExchangeStrategies; private ExchangeStrategies mockExchangeStrategies;
private DefaultClientResponse defaultClientResponse; private DefaultClientResponse defaultClientResponse;
@ -68,6 +70,7 @@ public class DefaultClientResponseTests {
@BeforeEach @BeforeEach
public void createMocks() { public void createMocks() {
mockResponse = mock(ClientHttpResponse.class); mockResponse = mock(ClientHttpResponse.class);
given(mockResponse.getHeaders()).willReturn(this.httpHeaders);
mockExchangeStrategies = mock(ExchangeStrategies.class); mockExchangeStrategies = mock(ExchangeStrategies.class);
defaultClientResponse = new DefaultClientResponse(mockResponse, mockExchangeStrategies, "", "", () -> null); defaultClientResponse = new DefaultClientResponse(mockResponse, mockExchangeStrategies, "", "", () -> null);
} }
@ -91,7 +94,6 @@ public class DefaultClientResponseTests {
@Test @Test
public void header() { public void header() {
HttpHeaders httpHeaders = new HttpHeaders();
long contentLength = 42L; long contentLength = 42L;
httpHeaders.setContentLength(contentLength); httpHeaders.setContentLength(contentLength);
MediaType contentType = MediaType.TEXT_PLAIN; MediaType contentType = MediaType.TEXT_PLAIN;
@ -233,7 +235,6 @@ public class DefaultClientResponseTests {
= factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8))); = factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer); Flux<DataBuffer> body = Flux.just(dataBuffer);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN); httpHeaders.setContentType(MediaType.TEXT_PLAIN);
given(mockResponse.getHeaders()).willReturn(httpHeaders); given(mockResponse.getHeaders()).willReturn(httpHeaders);
given(mockResponse.getStatusCode()).willThrow(new IllegalArgumentException("999")); given(mockResponse.getStatusCode()).willThrow(new IllegalArgumentException("999"));
@ -293,13 +294,12 @@ public class DefaultClientResponseTests {
} }
@Test @Test
public void toEntityListWithUnknownStatusCode() throws Exception { public void toEntityListWithUnknownStatusCode() {
DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer = DefaultDataBuffer dataBuffer =
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8))); factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer); Flux<DataBuffer> body = Flux.just(dataBuffer);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN); httpHeaders.setContentType(MediaType.TEXT_PLAIN);
given(mockResponse.getHeaders()).willReturn(httpHeaders); given(mockResponse.getHeaders()).willReturn(httpHeaders);
given(mockResponse.getStatusCode()).willThrow(new IllegalArgumentException("999")); given(mockResponse.getStatusCode()).willThrow(new IllegalArgumentException("999"));
@ -321,8 +321,7 @@ public class DefaultClientResponseTests {
@Test @Test
public void toEntityListTypeReference() { public void toEntityListTypeReference() {
DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
DefaultDataBuffer dataBuffer = DefaultDataBuffer dataBuffer = factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
factory.wrap(ByteBuffer.wrap("foo".getBytes(StandardCharsets.UTF_8)));
Flux<DataBuffer> body = Flux.just(dataBuffer); Flux<DataBuffer> body = Flux.just(dataBuffer);
mockTextPlainResponse(body); mockTextPlainResponse(body);
@ -332,8 +331,7 @@ public class DefaultClientResponseTests {
given(mockExchangeStrategies.messageReaders()).willReturn(messageReaders); given(mockExchangeStrategies.messageReaders()).willReturn(messageReaders);
ResponseEntity<List<String>> result = defaultClientResponse.toEntityList( ResponseEntity<List<String>> result = defaultClientResponse.toEntityList(
new ParameterizedTypeReference<String>() { new ParameterizedTypeReference<String>() {}).block();
}).block();
assertThat(result.getBody()).isEqualTo(Collections.singletonList("foo")); assertThat(result.getBody()).isEqualTo(Collections.singletonList("foo"));
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(result.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value()); assertThat(result.getStatusCodeValue()).isEqualTo(HttpStatus.OK.value());
@ -342,9 +340,7 @@ public class DefaultClientResponseTests {
private void mockTextPlainResponse(Flux<DataBuffer> body) { private void mockTextPlainResponse(Flux<DataBuffer> body) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN); httpHeaders.setContentType(MediaType.TEXT_PLAIN);
given(mockResponse.getHeaders()).willReturn(httpHeaders);
given(mockResponse.getStatusCode()).willReturn(HttpStatus.OK); given(mockResponse.getStatusCode()).willReturn(HttpStatus.OK);
given(mockResponse.getRawStatusCode()).willReturn(HttpStatus.OK.value()); given(mockResponse.getRawStatusCode()).willReturn(HttpStatus.OK.value());
given(mockResponse.getBody()).willReturn(body); given(mockResponse.getBody()).willReturn(body);

View File

@ -305,62 +305,62 @@ class DefaultServerRequest implements ServerRequest {
*/ */
static class DefaultRequestHeaders implements Headers { static class DefaultRequestHeaders implements Headers {
private final HttpHeaders delegate; private final HttpHeaders httpHeaders;
public DefaultRequestHeaders(HttpHeaders delegate) { public DefaultRequestHeaders(HttpHeaders httpHeaders) {
this.delegate = delegate; this.httpHeaders = HttpHeaders.readOnlyHttpHeaders(httpHeaders);
} }
@Override @Override
public List<MediaType> accept() { public List<MediaType> accept() {
return this.delegate.getAccept(); return this.httpHeaders.getAccept();
} }
@Override @Override
public List<Charset> acceptCharset() { public List<Charset> acceptCharset() {
return this.delegate.getAcceptCharset(); return this.httpHeaders.getAcceptCharset();
} }
@Override @Override
public List<Locale.LanguageRange> acceptLanguage() { public List<Locale.LanguageRange> acceptLanguage() {
return this.delegate.getAcceptLanguage(); return this.httpHeaders.getAcceptLanguage();
} }
@Override @Override
public OptionalLong contentLength() { public OptionalLong contentLength() {
long value = this.delegate.getContentLength(); long value = this.httpHeaders.getContentLength();
return (value != -1 ? OptionalLong.of(value) : OptionalLong.empty()); return (value != -1 ? OptionalLong.of(value) : OptionalLong.empty());
} }
@Override @Override
public Optional<MediaType> contentType() { public Optional<MediaType> contentType() {
return Optional.ofNullable(this.delegate.getContentType()); return Optional.ofNullable(this.httpHeaders.getContentType());
} }
@Override @Override
public InetSocketAddress host() { public InetSocketAddress host() {
return this.delegate.getHost(); return this.httpHeaders.getHost();
} }
@Override @Override
public List<HttpRange> range() { public List<HttpRange> range() {
return this.delegate.getRange(); return this.httpHeaders.getRange();
} }
@Override @Override
public List<String> header(String headerName) { public List<String> header(String headerName) {
List<String> headerValues = this.delegate.get(headerName); List<String> headerValues = this.httpHeaders.get(headerName);
return (headerValues != null ? headerValues : Collections.emptyList()); return (headerValues != null ? headerValues : Collections.emptyList());
} }
@Override @Override
public HttpHeaders asHttpHeaders() { public HttpHeaders asHttpHeaders() {
return HttpHeaders.readOnlyHttpHeaders(this.delegate); return this.httpHeaders;
} }
@Override @Override
public String toString() { public String toString() {
return this.delegate.toString(); return this.httpHeaders.toString();
} }
} }