Skip to content

Instantly share code, notes, and snippets.

@simonbasle
Created January 20, 2023 10:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simonbasle/f7880ded86857d71ee68bbe10cf2edce to your computer and use it in GitHub Desktop.
Save simonbasle/f7880ded86857d71ee68bbe10cf2edce to your computer and use it in GitHub Desktop.
Investigation for SPR gh-28613
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()
}
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