-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement target gzip request compression
Signed-off-by: Andre Kurait <[email protected]>
- Loading branch information
1 parent
656bdb0
commit 1733301
Showing
9 changed files
with
369 additions
and
18 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
27 changes: 27 additions & 0 deletions
27
RFS/src/main/java/com/rfs/common/http/CompositeTransformer.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.rfs.common.http; | ||
|
||
import java.nio.ByteBuffer; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import reactor.core.publisher.Mono; | ||
|
||
@AllArgsConstructor | ||
@Getter | ||
public class CompositeTransformer implements RequestTransformer { | ||
private final RequestTransformer firstTransformer; | ||
private final RequestTransformer secondTransformer; | ||
|
||
@Override | ||
public Mono<TransformedRequest> transform(String method, String path, Map<String, List<String>> headers, Mono<ByteBuffer> body) { | ||
return firstTransformer.transform(method, path, headers, body) | ||
.flatMap(firstResult -> secondTransformer.transform(method, path, firstResult.getHeaders(), firstResult.getBody())); | ||
} | ||
|
||
public List<RequestTransformer> getTransformers() { | ||
return Arrays.asList(firstTransformer, secondTransformer); | ||
} | ||
} |
This file contains 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
75 changes: 75 additions & 0 deletions
75
RFS/src/main/java/com/rfs/common/http/GzipRequestTransformer.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package com.rfs.common.http; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.io.OutputStream; | ||
import java.nio.ByteBuffer; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.zip.Deflater; | ||
import java.util.zip.GZIPOutputStream; | ||
|
||
import io.netty.handler.codec.http.HttpHeaderNames; | ||
import lombok.AllArgsConstructor; | ||
import lombok.SneakyThrows; | ||
import lombok.extern.slf4j.Slf4j; | ||
import reactor.core.publisher.Mono; | ||
|
||
@AllArgsConstructor | ||
@Slf4j | ||
public class GzipRequestTransformer implements RequestTransformer { | ||
private static final String CONTENT_ENCODING_HEADER_NAME = HttpHeaderNames.CONTENT_ENCODING.toString(); | ||
private static final String GZIP_CONTENT_ENCODING_HEADER_VALUE = "gzip"; | ||
private static final int READ_BUFFER_SIZE = 256 * 1024; // Arbitrary, 256KB | ||
|
||
// Local benchmarks show 15% throughput improvement with this setting | ||
private static final int COMPRESSION_LEVEL = Deflater.BEST_SPEED; | ||
|
||
@Override | ||
public Mono<TransformedRequest> transform(String method, String path, Map<String, List<String>> headers, Mono<ByteBuffer> body) { | ||
return body | ||
.map(this::gzipByteBufferSimple) | ||
.singleOptional() | ||
.flatMap( | ||
bodyOp -> { | ||
Map<String, List<String>> newHeaders = new HashMap<>(headers); | ||
if (bodyOp.isPresent()) { | ||
newHeaders.put(CONTENT_ENCODING_HEADER_NAME, List.of(GZIP_CONTENT_ENCODING_HEADER_VALUE)); | ||
} | ||
return Mono.just(new TransformedRequest(newHeaders, Mono.justOrEmpty(bodyOp))); | ||
} | ||
); | ||
} | ||
|
||
@SneakyThrows | ||
private ByteBuffer gzipByteBufferSimple(final ByteBuffer inputBuffer) { | ||
var readbuffer = inputBuffer.duplicate(); | ||
var baos = new ByteArrayOutputStream(); | ||
try (GZIPOutputStream gzipOutputStream = new FastGzipOutputStream(baos, READ_BUFFER_SIZE, false)) { | ||
if (readbuffer.hasArray()) { | ||
gzipOutputStream.write(readbuffer.array(), readbuffer.arrayOffset() + readbuffer.position(), readbuffer.remaining()); | ||
} else { | ||
byte[] buffer = new byte[READ_BUFFER_SIZE]; | ||
while (readbuffer.hasRemaining()) { | ||
int bytesRead = Math.min(buffer.length, readbuffer.remaining()); | ||
readbuffer.get(buffer, 0, bytesRead); | ||
gzipOutputStream.write(buffer, 0, bytesRead); | ||
} | ||
} | ||
} | ||
if (inputBuffer.remaining() > 0) { | ||
log.atDebug().setMessage("Gzip compression ratio: {}") | ||
.addArgument(() -> String.format("%.2f%%", (double) baos.size() / inputBuffer.remaining() * 100)) | ||
.log(); | ||
} | ||
return ByteBuffer.wrap(baos.toByteArray()); | ||
} | ||
|
||
private static class FastGzipOutputStream extends GZIPOutputStream { | ||
public FastGzipOutputStream(OutputStream out, int size, boolean syncFlush) throws IOException { | ||
super(out, size, syncFlush); | ||
def.setLevel(COMPRESSION_LEVEL); | ||
} | ||
} | ||
} |
This file contains 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
45 changes: 45 additions & 0 deletions
45
RFS/src/test/java/com/rfs/common/http/CompositeTransformerTest.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package com.rfs.common.http; | ||
|
||
import java.nio.ByteBuffer; | ||
import java.util.Collections; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import org.mockito.Mockito; | ||
import reactor.core.publisher.Mono; | ||
import reactor.test.StepVerifier; | ||
|
||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.Mockito.when; | ||
|
||
public class CompositeTransformerTest { | ||
|
||
@Test | ||
public void testCompositeTransformer() { | ||
// Create mock transformers | ||
RequestTransformer firstTransformer = Mockito.mock(RequestTransformer.class); | ||
RequestTransformer secondTransformer = Mockito.mock(RequestTransformer.class); | ||
|
||
// Set up mock behavior | ||
TransformedRequest firstResult = new TransformedRequest(Collections.emptyMap(), Mono.empty()); | ||
TransformedRequest finalResult = new TransformedRequest(Collections.emptyMap(), Mono.just(ByteBuffer.wrap("test".getBytes()))); | ||
|
||
when(firstTransformer.transform(any(), any(), any(), any())).thenReturn(Mono.just(firstResult)); | ||
when(secondTransformer.transform(any(), any(), any(), any())).thenReturn(Mono.just(finalResult)); | ||
|
||
// Create CompositeTransformer | ||
CompositeTransformer compositeTransformer = new CompositeTransformer(firstTransformer, secondTransformer); | ||
|
||
// Test the transform method | ||
Mono<TransformedRequest> result = compositeTransformer.transform("GET", "/test", Collections.emptyMap(), Mono.empty()); | ||
|
||
// Verify the result | ||
StepVerifier.create(result) | ||
.expectNext(finalResult) | ||
.verifyComplete(); | ||
|
||
// Verify that both transformers were called | ||
Mockito.verify(firstTransformer).transform(any(), any(), any(), any()); | ||
Mockito.verify(secondTransformer).transform(any(), any(), any(), any()); | ||
} | ||
} |
Oops, something went wrong.