Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Brotli support #5202

Draft
wants to merge 3 commits into
base: 2.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions incubator/brotli/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<version>2.38-SNAPSHOT</version>
<parent>
<artifactId>project</artifactId>
<groupId>org.glassfish.jersey</groupId>
<version>2.38-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

<groupId>org.glassfish.jersey.message</groupId>
<artifactId>jersey-brotli</artifactId>
<packaging>jar</packaging>
<name>brotli</name>


<dependencies>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.oracle.brotli</groupId>
<artifactId>brotli</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-external</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.glassfish.jersey.message;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not believe it is a good idea to have the same package name in two modules, and org.glassfish.jersey.message is in core-common already.


import com.oracle.brotli.decoder.BrotliInputStream;
import com.oracle.brotli.encoder.BrotliOutputStream;
import org.glassfish.jersey.spi.ContentEncoder;

import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.core.HttpHeaders;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
* Brotli encoding support. Interceptor that encodes the output or decodes the input if
* {@link HttpHeaders#CONTENT_ENCODING Content-Encoding header} value equals to {@code br}.
*/
@Priority(Priorities.ENTITY_CODER)
public class BrotliEncoder extends ContentEncoder {

/**
* Initialize BrotliEncoder.
*/
public BrotliEncoder() {
super("br");
}

@Override
public InputStream decode(String contentEncoding, InputStream encodedStream) throws IOException {
return BrotliInputStream.builder().inputStream(encodedStream).build();
}

@Override
public OutputStream encode(String contentEncoding, OutputStream entityStream) throws IOException {
return BrotliOutputStream.builder().outputStream(entityStream).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.glassfish.jersey.message;

import com.oracle.brotli.decoder.BrotliInputStream;
import com.oracle.brotli.encoder.BrotliOutputStream;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class BrotliEncoderTest {

@Test
public void testEncode() throws IOException {
test(new TestSpec() {
@Override
public OutputStream getEncoded(OutputStream stream) throws IOException {
return new BrotliEncoder().encode("br", stream);
}

@Override
public InputStream getDecoded(InputStream stream) throws IOException {
return BrotliInputStream.builder().inputStream(stream).build();
}
});
}

@Test
public void testDecode() throws IOException {
test(new TestSpec() {
@Override
public OutputStream getEncoded(OutputStream stream) throws IOException {
return BrotliOutputStream.builder().outputStream(stream).build();
}

@Override
public InputStream getDecoded(InputStream stream) throws IOException {
return new BrotliEncoder().decode("br", stream);
}
});
}

@Test
public void testEncodeDecode() throws IOException {
test(new TestSpec() {
@Override
public OutputStream getEncoded(OutputStream stream) throws IOException {
return new BrotliEncoder().encode("br", stream);
}

@Override
public InputStream getDecoded(InputStream stream) throws IOException {
return new BrotliEncoder().decode("br", stream);
}
});
}

void test(TestSpec testSpec) throws IOException {
byte[] entity = "Hello world!".getBytes();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStream encoded = testSpec.getEncoded(baos);
encoded.write(entity);
encoded.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
byte[] result = new byte[entity.length];
InputStream decoded = testSpec.getDecoded(bais);
int len = decoded.read(result);
assertEquals(-1, decoded.read());
decoded.close();
assertEquals(entity.length, len);
assertArrayEquals(entity, result);
}

interface TestSpec {
/**
* Returns encoded stream.
*
* @param stream Original stream.
* @return Encoded stream.
* @throws IOException I/O exception.
*/
OutputStream getEncoded(OutputStream stream) throws IOException;

/**
* Returns decoded stream.
*
* @param stream Original stream.
* @return Decoded stream.
* @throws IOException I/O exception.
*/
InputStream getDecoded(InputStream stream) throws IOException;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package org.glassfish.jersey.message;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.filter.EncodingFilter;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.TestProperties;
import org.glassfish.jersey.test.external.ExternalTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerException;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import org.junit.jupiter.api.Test;

import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class BrotliITTest extends JerseyTest {

private static final String CONTENT_ENCODING = "Content-Encoding";
private static final String BR = "br";

@Override
protected Application configure() {
enable(TestProperties.LOG_TRAFFIC);
enable(TestProperties.DUMP_ENTITY);
return new MyApplication();
}

@Override
protected TestContainerFactory getTestContainerFactory() throws TestContainerException {
return new ExternalTestContainerFactory();
}

protected void assertHtmlResponse(final String response) {
assertNotNull(response, "No text returned!");

assertResponseContains(response, "<html>");
assertResponseContains(response, "</html>");
}

protected void assertResponseContains(final String response, final String text) {
assertTrue(response.contains(text), "Response should contain " + text + " but was: " + response);
}

@Test
public void testString() throws Exception {
Response response = target("/client/string")
.register(BrotliEncoder.class)
.request("text/html")
.acceptEncoding(BR)
.get();
String resp = response.readEntity(String.class);
assertResponseContains(resp, "string string string string string string");
assertEquals(BR, response.getHeaderString(CONTENT_ENCODING));
}

@Test
public void testJsp() throws Exception {
Response response = target("/client/html")
.register(BrotliEncoder.class)
.request("text/html", "application/xhtml+xml", "application/xml;q=0.9", "*/*;q=0.8")
.acceptEncoding(BR)
.get();
String resp = response.readEntity(String.class);
assertHtmlResponse(resp);
assertResponseContains(resp, "find this string");
assertEquals(BR, response.getHeaderString(CONTENT_ENCODING));
}

@Test
public void testJspNotDecoded() throws Exception {
Response response = target("/client/html")
.request("text/html", "application/xhtml+xml", "application/xml;q=0.9", "*/*;q=0.8")
.acceptEncoding(BR)
.get();
String resp = response.readEntity(String.class);
assertFalse(resp.contains("find this string"));
assertEquals(BR, response.getHeaderString(CONTENT_ENCODING));
}

class MyApplication extends ResourceConfig {

public MyApplication() {
property("jersey.config.server.mvc.templateBasePath.jsp", "/WEB-INF/jsp");
property("jersey.config.servlet.filter.forwardOn404", "true");
property("jersey.config.servlet.filter.staticContentRegex", "/WEB-INF/.*\\.jsp");
packages(MyApplication.class.getPackage().getName());
EncodingFilter.enableFor(this, new Class[] {BrotliEncoder.class});
}
}

}
1 change: 1 addition & 0 deletions incubator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
<module>html-json</module>
<module>kryo</module>
<module>open-tracing</module>
<module>brotli</module>
</modules>

<dependencies>
Expand Down