Created
January 20, 2023 10:19
-
-
Save simonbasle/f7880ded86857d71ee68bbe10cf2edce to your computer and use it in GitHub Desktop.
Investigation for SPR gh-28613
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
plugins { | |
id 'java' | |
id 'org.springframework.boot' version '3.0.1' | |
id 'io.spring.dependency-management' version '1.1.0' | |
} | |
group = 'com.example' | |
version = '0.0.1-SNAPSHOT' | |
sourceCompatibility = '17' | |
repositories { | |
mavenLocal() | |
mavenCentral() | |
} | |
configurations { | |
all*.exclude module : 'spring-boot-starter-logging' | |
all*.exclude module : 'logback-classic' | |
} | |
dependencies { | |
implementation 'org.springframework.boot:spring-boot-starter-web' | |
implementation 'org.springframework.boot:spring-boot-starter-webflux' | |
developmentOnly 'org.springframework.boot:spring-boot-devtools' | |
testImplementation 'org.springframework.boot:spring-boot-starter-test' | |
testImplementation('org.mock-server:mockserver-netty-no-dependencies:5.13.2') { | |
} | |
testImplementation 'org.apache.httpcomponents:httpclient' | |
testImplementation 'org.apache.httpcomponents.client5:httpclient5' | |
testImplementation 'org.apache.httpcomponents.core5:httpcore5-reactive' | |
testImplementation 'org.eclipse.jetty:jetty-reactive-httpclient' | |
testImplementation 'com.squareup.okhttp3:okhttp:4.9.1' | |
} | |
tasks.named('test') { | |
useJUnitPlatform() | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.sprtest; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.net.URI; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.zip.GZIPOutputStream; | |
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; | |
import org.apache.hc.client5.http.impl.async.HttpAsyncClients; | |
import org.eclipse.jetty.client.HttpClient; | |
import org.junit.jupiter.params.ParameterizedTest; | |
import org.junit.jupiter.params.provider.Arguments; | |
import org.junit.jupiter.params.provider.MethodSource; | |
import org.mockserver.configuration.Configuration; | |
import org.mockserver.integration.ClientAndServer; | |
import org.mockserver.matchers.Times; | |
import org.mockserver.model.ConnectionOptions; | |
import org.mockserver.model.HttpResponse; | |
import org.mockserver.model.MediaType; | |
import org.slf4j.event.Level; | |
import org.springframework.boot.test.context.SpringBootTest; | |
import org.springframework.boot.test.web.client.TestRestTemplate; | |
import org.springframework.boot.web.client.RestTemplateBuilder; | |
import org.springframework.http.HttpHeaders; | |
import org.springframework.http.HttpStatus; | |
import org.springframework.http.ResponseEntity; | |
import org.springframework.http.client.ClientHttpRequestFactory; | |
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; | |
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; | |
import org.springframework.http.client.SimpleClientHttpRequestFactory; | |
import org.springframework.http.client.reactive.ClientHttpConnector; | |
import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector; | |
import org.springframework.http.client.reactive.JettyClientHttpConnector; | |
import org.springframework.http.client.reactive.ReactorClientHttpConnector; | |
import org.springframework.web.reactive.function.client.WebClient; | |
import static org.assertj.core.api.Assertions.assertThat; | |
import static org.junit.jupiter.api.Named.named; | |
import static org.mockserver.model.HttpRequest.request; | |
import static org.mockserver.model.HttpResponse.response; | |
/** | |
* @author Simon Baslé | |
*/ | |
@SpringBootTest | |
class ZipControllerTests { | |
private static final ClientAndServer nettyMockServer = ClientAndServer.startClientAndServer( | |
Configuration.configuration().logLevel(Level.WARN), 8080); | |
public static final HttpStatus HTTP_STATUS = HttpStatus.NOT_FOUND; | |
@ParameterizedTest | |
@MethodSource("gzipParams") | |
void gzip(Mode mode, Spring springClient, Client underlyingClient, byte[] body) { | |
final ConnectionOptions connectionOptions = ConnectionOptions.connectionOptions(); | |
switch (mode) { | |
case CHUNKED -> connectionOptions.withChunkSize(100); | |
case SOCKET_CLOSE -> connectionOptions.withCloseSocket(true); | |
} | |
mockServer(connectionOptions, body); | |
ResponseEntity<String> response = null; | |
if (springClient == Spring.REST_TEMPLATE) { | |
final Class<? extends ClientHttpRequestFactory> factoryType = switch (underlyingClient) { | |
case JDK -> SimpleClientHttpRequestFactory.class; | |
case APACHE_HC5_SYNC -> HttpComponentsClientHttpRequestFactory.class; | |
case OK_HTTP -> OkHttp3ClientHttpRequestFactory.class; | |
default -> throw new IllegalArgumentException(underlyingClient + " not compatible with RestTemplate"); | |
}; | |
TestRestTemplate testRestTemplate = new TestRestTemplate(new RestTemplateBuilder() | |
.requestFactory(factoryType)); | |
// WHEN | |
response = testRestTemplate | |
.postForEntity(URI.create("http://localhost:8080/"), | |
request().withHeader(HttpHeaders.ACCEPT_ENCODING, "gzip"), | |
String.class); | |
} | |
else if (springClient == Spring.WEBCLIENT) { | |
final ClientHttpConnector connector = switch (underlyingClient) { | |
case JETTY -> { | |
HttpClient httpClient = new HttpClient(); | |
yield new JettyClientHttpConnector(httpClient); | |
} | |
case APACHE_HC5_ASYNC -> { | |
final CloseableHttpAsyncClient apacheClient = HttpAsyncClients.custom() | |
.build(); | |
yield new HttpComponentsClientHttpConnector(apacheClient); | |
} | |
case REACTOR -> new ReactorClientHttpConnector(); | |
default -> throw new IllegalArgumentException(underlyingClient + " not compatible with webclient"); | |
}; | |
response = WebClient.builder().clientConnector(connector) | |
.baseUrl("http://localhost:8080/").build() | |
.post() | |
.header(HttpHeaders.ACCEPT_ENCODING, "gzip") | |
.exchangeToMono(r -> r.toEntity(String.class)) | |
.block(); | |
} | |
// THEN | |
assertThat(response.getStatusCode()).isEqualTo(HTTP_STATUS); | |
assertThat(response.getBody()).isNull(); | |
} | |
enum Mode { | |
CHUNKED, SOCKET_CLOSE; | |
} | |
enum Spring { | |
WEBCLIENT, REST_TEMPLATE; | |
} | |
enum Client { | |
JETTY, APACHE_HC5_ASYNC, REACTOR, APACHE_HC5_SYNC, OK_HTTP, JDK | |
} | |
static List<Arguments> combination(Mode mode, Spring springClient, Client... applicableClients) { | |
List<Arguments> list = new ArrayList<>(); | |
for (Client applicableClient : applicableClients) { | |
list.add(Arguments.of(mode, springClient, applicableClient, named("empty gzip", EMPTY_GZIPPED))); | |
list.add(Arguments.of(mode, springClient, applicableClient, named("no bytes", EMPTY_BYTES))); | |
} | |
return list; | |
} | |
static List<Arguments> gzipParams() { | |
final List<Arguments> list = new ArrayList<>(); | |
for (Mode mode : Mode.values()) { | |
list.addAll(combination(mode, Spring.REST_TEMPLATE, | |
Client.OK_HTTP, Client.APACHE_HC5_SYNC, Client.JDK)); | |
list.addAll(combination(mode, Spring.WEBCLIENT, | |
Client.JETTY, Client.APACHE_HC5_ASYNC, Client.REACTOR)); | |
} | |
return list; | |
} | |
static byte[] EMPTY_GZIPPED; | |
static byte[] EMPTY_BYTES = new byte[0]; | |
static { | |
final ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
final GZIPOutputStream gzipOutputStream; | |
try { | |
gzipOutputStream = new GZIPOutputStream(baos); | |
gzipOutputStream.flush(); | |
gzipOutputStream.close(); | |
EMPTY_GZIPPED = baos.toByteArray(); | |
} | |
catch (IOException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
private void mockServer(ConnectionOptions connectionOptions, byte[] body) { | |
HttpResponse httpResponse = response() | |
.withStatusCode(HTTP_STATUS.value()) | |
.withConnectionOptions(connectionOptions | |
.withSuppressContentLengthHeader(true)) | |
.withHeader("Content-Encoding", "gzip") | |
.withContentType(MediaType.TEXT_PLAIN) | |
.withBody(body); | |
nettyMockServer | |
.when(request().withPath("/"), Times.once()) | |
.respond(httpResponse); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment