From e66e75c109d48477a10272ecf82c1fe731824aa5 Mon Sep 17 00:00:00 2001 From: tvallin Date: Tue, 22 Nov 2022 14:35:45 +0100 Subject: [PATCH 1/3] Brotli implementation Signed-off-by: tvallin --- .../client/filter/EncodingFilterTest.java | 17 ++++-- core-common/pom.xml | 4 ++ .../jersey/message/BrotliEncoder.java | 37 ++++++++++++ .../jersey/message/BrotliEncodingTest.java | 57 +++++++++++++++++++ .../file/DefaultMediaTypePredictor.java | 2 + pom.xml | 6 ++ .../jersey/tests/e2e/common/EncodingTest.java | 46 +++++++++++++++ .../servlet_40_mvc_1/BrotliITCase.java | 52 +++++++++++++++++ 8 files changed, 215 insertions(+), 6 deletions(-) create mode 100644 core-common/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java create mode 100644 core-common/src/test/java/org/glassfish/jersey/message/BrotliEncodingTest.java create mode 100644 tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/BrotliITCase.java diff --git a/core-client/src/test/java/org/glassfish/jersey/client/filter/EncodingFilterTest.java b/core-client/src/test/java/org/glassfish/jersey/client/filter/EncodingFilterTest.java index a48671fc27..22343bcd71 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/filter/EncodingFilterTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/filter/EncodingFilterTest.java @@ -39,6 +39,7 @@ import org.glassfish.jersey.client.spi.AsyncConnectorCallback; import org.glassfish.jersey.client.spi.Connector; import org.glassfish.jersey.client.spi.ConnectorProvider; +import org.glassfish.jersey.message.BrotliEncoder; import org.glassfish.jersey.message.DeflateEncoder; import org.glassfish.jersey.message.GZipEncoder; @@ -60,11 +61,12 @@ public void testAcceptEncoding() { Client client = ClientBuilder.newClient(new ClientConfig( EncodingFilter.class, GZipEncoder.class, + BrotliEncoder.class, DeflateEncoder.class ).connectorProvider(new TestConnector())); Invocation.Builder invBuilder = client.target(UriBuilder.fromUri("/").build()).request(); Response r = invBuilder.get(); - assertEquals("deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); + assertEquals("br,deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); assertNull(r.getHeaderString(CONTENT_ENCODING)); } @@ -73,11 +75,12 @@ public void testContentEncoding() { Client client = ClientBuilder.newClient(new ClientConfig( EncodingFilter.class, GZipEncoder.class, + BrotliEncoder.class, DeflateEncoder.class ).property(ClientProperties.USE_ENCODING, "gzip").connectorProvider(new TestConnector())); Invocation.Builder invBuilder = client.target(UriBuilder.fromUri("/").build()).request(); Response r = invBuilder.post(Entity.entity("Hello world", MediaType.TEXT_PLAIN_TYPE)); - assertEquals("deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); + assertEquals("br,deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); assertEquals("gzip", r.getHeaderString(CONTENT_ENCODING)); } @@ -85,10 +88,10 @@ public void testContentEncoding() { public void testContentEncodingViaFeature() { Client client = ClientBuilder.newClient(new ClientConfig() .connectorProvider(new TestConnector()) - .register(new EncodingFeature("gzip", GZipEncoder.class, DeflateEncoder.class))); + .register(new EncodingFeature("gzip", GZipEncoder.class, BrotliEncoder.class, DeflateEncoder.class))); Invocation.Builder invBuilder = client.target(UriBuilder.fromUri("/").build()).request(); Response r = invBuilder.post(Entity.entity("Hello world", MediaType.TEXT_PLAIN_TYPE)); - assertEquals("deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); + assertEquals("br,deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); assertEquals("gzip", r.getHeaderString(CONTENT_ENCODING)); } @@ -97,11 +100,12 @@ public void testContentEncodingSkippedForNoEntity() { Client client = ClientBuilder.newClient(new ClientConfig( EncodingFilter.class, GZipEncoder.class, + BrotliEncoder.class, DeflateEncoder.class ).property(ClientProperties.USE_ENCODING, "gzip").connectorProvider(new TestConnector())); Invocation.Builder invBuilder = client.target(UriBuilder.fromUri("/").build()).request(); Response r = invBuilder.get(); - assertEquals("deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); + assertEquals("br,deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); assertNull(r.getHeaderString(CONTENT_ENCODING)); } @@ -110,11 +114,12 @@ public void testUnsupportedContentEncoding() { Client client = ClientBuilder.newClient(new ClientConfig( EncodingFilter.class, GZipEncoder.class, + BrotliEncoder.class, DeflateEncoder.class ).property(ClientProperties.USE_ENCODING, "non-gzip").connectorProvider(new TestConnector())); Invocation.Builder invBuilder = client.target(UriBuilder.fromUri("/").build()).request(); Response r = invBuilder.get(); - assertEquals("deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); + assertEquals("br,deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); assertNull(r.getHeaderString(CONTENT_ENCODING)); } diff --git a/core-common/pom.xml b/core-common/pom.xml index 2f61b3bfc1..0ae1732534 100644 --- a/core-common/pom.xml +++ b/core-common/pom.xml @@ -206,6 +206,10 @@ + + com.oracle.brotli + brotli + jakarta.ws.rs jakarta.ws.rs-api diff --git a/core-common/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java b/core-common/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java new file mode 100644 index 0000000000..92083a6088 --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java @@ -0,0 +1,37 @@ +package org.glassfish.jersey.message; + +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(); + } +} diff --git a/core-common/src/test/java/org/glassfish/jersey/message/BrotliEncodingTest.java b/core-common/src/test/java/org/glassfish/jersey/message/BrotliEncodingTest.java new file mode 100644 index 0000000000..f0a7a7eb8c --- /dev/null +++ b/core-common/src/test/java/org/glassfish/jersey/message/BrotliEncodingTest.java @@ -0,0 +1,57 @@ +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.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class BrotliEncodingTest extends AbstractEncodingTest { + + @Test + public void testEncode() throws IOException { + test(new AbstractEncodingTest.TestSpec() { + @Override + public OutputStream getEncoded(OutputStream stream) throws IOException { + return new BrotliEncoder().encode("gzip", stream); + } + + @Override + public InputStream getDecoded(InputStream stream) throws IOException { + return BrotliInputStream.builder().inputStream(stream).build(); + } + }); + } + + @Test + public void testDecode() throws IOException { + test(new AbstractEncodingTest.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 AbstractEncodingTest.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); + } + }); + } +} diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/file/DefaultMediaTypePredictor.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/file/DefaultMediaTypePredictor.java index 56eb7155c6..0bb0100709 100644 --- a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/file/DefaultMediaTypePredictor.java +++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/file/DefaultMediaTypePredictor.java @@ -49,6 +49,7 @@ public class DefaultMediaTypePredictor implements MediaTypePredictor { *
  • ".tar" - application/x-tar
  • *
  • ".zip" - application/zip
  • *
  • ".gz" - application/x-gzip
  • + *
  • ".br" - application/br
  • *
  • ".rar" - application/x-rar
  • *
  • ".mp3" - audio/mpeg
  • *
  • ".wav" - audio/x-wave
  • @@ -70,6 +71,7 @@ public enum CommonMediaTypes { TAR(".tar", new MediaType("application", "x-tar")), ZIP(".zip", new MediaType("application", "zip")), GZ(".gz", new MediaType("application", "x-gzip")), + BR("br", new MediaType("application", "br")), RAR(".rar", new MediaType("application", "x-rar")), MP3(".mp3", new MediaType("audio", "mpeg")), WAV(".wav", new MediaType("audio", "x-wave")), diff --git a/pom.xml b/pom.xml index d98b4c8cfa..1759b48824 100644 --- a/pom.xml +++ b/pom.xml @@ -1531,6 +1531,11 @@ + + com.oracle.brotli + brotli + ${brotli.version} + jakarta.ws.rs jakarta.ws.rs-api @@ -2164,6 +2169,7 @@ 9.4 2.3.6 + 1.0.0-SNAPSHOT 2.11.0 3.3.2 1.2 diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/common/EncodingTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/common/EncodingTest.java index b9b899c333..47dfd57711 100644 --- a/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/common/EncodingTest.java +++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/common/EncodingTest.java @@ -31,7 +31,9 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; +import com.oracle.brotli.decoder.BrotliInputStream; import org.glassfish.jersey.client.filter.EncodingFilter; +import org.glassfish.jersey.message.BrotliEncoder; import org.glassfish.jersey.message.DeflateEncoder; import org.glassfish.jersey.message.GZipEncoder; import org.glassfish.jersey.server.ResourceConfig; @@ -59,10 +61,31 @@ protected Application configure() { EchoResource.class, org.glassfish.jersey.server.filter.EncodingFilter.class, GZipEncoder.class, + BrotliEncoder.class, DeflateEncoder.class ); } + @Test + public void testBrotli() throws IOException { + test(new TestSpec() { + @Override + public InputStream decode(InputStream stream) throws IOException { + return BrotliInputStream.builder().inputStream(stream).build(); + } + + @Override + public void checkHeadersAndStatus(Response response) { + assertEquals("br", response.getHeaderString(HttpHeaders.CONTENT_ENCODING)); + } + + @Override + public Invocation.Builder setHeaders(Invocation.Builder invBuilder) { + return invBuilder.header(HttpHeaders.ACCEPT_ENCODING, "br"); + } + }); + } + @Test public void testGZip() throws IOException { test(new TestSpec() { @@ -161,6 +184,29 @@ public void checkHeadersAndStatus(Response response) { }); } + @Test + public void testBrotliPreferredClientServer() throws IOException { + test(new TestSpec() { + @Override + public Invocation.Builder setHeaders(Invocation.Builder invBuilder) { + return invBuilder.header(HttpHeaders.ACCEPT_ENCODING, "br,deflate,gzip"); + } + + @Override + public WebTarget configure(WebTarget target) { + target.register(DeflateEncoder.class) + .register(BrotliEncoder.class) + .register(EncodingFilter.class); + return target; + } + + @Override + public void checkHeadersAndStatus(Response response) { + assertEquals("br", response.getHeaderString(HttpHeaders.CONTENT_ENCODING)); + } + }); + } + private void test(TestSpec testSpec) throws IOException { String input = "hello"; Response response = testSpec.setHeaders(testSpec.configure(target()).request()) diff --git a/tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/BrotliITCase.java b/tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/BrotliITCase.java new file mode 100644 index 0000000000..db6e85fb7a --- /dev/null +++ b/tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/BrotliITCase.java @@ -0,0 +1,52 @@ +package org.glassfish.jersey.tests.integration.servlet_40_mvc_1; + +import org.glassfish.jersey.message.BrotliEncoder; +import org.junit.jupiter.api.Test; + +import javax.ws.rs.core.Response; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class BrotliITCase extends TestSupport { + + private static final String CONTENT_ENCODING = "Content-Encoding"; + private static final String BR = "br"; + + @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)); + } + +} \ No newline at end of file From 73dbd8699da02b7e4968103c3e98555835e3d305 Mon Sep 17 00:00:00 2001 From: tvallin Date: Tue, 22 Nov 2022 14:54:52 +0100 Subject: [PATCH 2/3] Add copyright Signed-off-by: tvallin --- .../glassfish/jersey/message/BrotliEncoder.java | 15 +++++++++++++++ .../jersey/message/BrotliEncodingTest.java | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/core-common/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java b/core-common/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java index 92083a6088..f56cdcea6e 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ package org.glassfish.jersey.message; import com.oracle.brotli.decoder.BrotliInputStream; diff --git a/core-common/src/test/java/org/glassfish/jersey/message/BrotliEncodingTest.java b/core-common/src/test/java/org/glassfish/jersey/message/BrotliEncodingTest.java index f0a7a7eb8c..e081f0871a 100644 --- a/core-common/src/test/java/org/glassfish/jersey/message/BrotliEncodingTest.java +++ b/core-common/src/test/java/org/glassfish/jersey/message/BrotliEncodingTest.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + package org.glassfish.jersey.message; import com.oracle.brotli.decoder.BrotliInputStream; From 4f7f28c088a3bfd662eb819540d017bed1bd7cdc Mon Sep 17 00:00:00 2001 From: tvallin Date: Thu, 1 Dec 2022 16:27:17 +0100 Subject: [PATCH 3/3] Move brotli to incubator Signed-off-by: tvallin --- .../client/filter/EncodingFilterTest.java | 17 ++-- core-common/pom.xml | 4 - .../jersey/message/BrotliEncodingTest.java | 73 -------------- incubator/brotli/pom.xml | 53 ++++++++++ .../jersey/message/BrotliEncoder.java | 15 --- .../jersey/message/BrotliEncoderTest.java | 98 +++++++++++++++++++ .../jersey/message/BrotliITTest.java | 95 ++++++++++++++++++ incubator/pom.xml | 1 + .../file/DefaultMediaTypePredictor.java | 2 - pom.xml | 6 -- .../jersey/tests/e2e/common/EncodingTest.java | 46 --------- .../servlet_40_mvc_1/BrotliITCase.java | 52 ---------- 12 files changed, 253 insertions(+), 209 deletions(-) delete mode 100644 core-common/src/test/java/org/glassfish/jersey/message/BrotliEncodingTest.java create mode 100644 incubator/brotli/pom.xml rename {core-common => incubator/brotli}/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java (62%) create mode 100644 incubator/brotli/src/test/java/org/glassfish/jersey/message/BrotliEncoderTest.java create mode 100644 incubator/brotli/src/test/java/org/glassfish/jersey/message/BrotliITTest.java delete mode 100644 tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/BrotliITCase.java diff --git a/core-client/src/test/java/org/glassfish/jersey/client/filter/EncodingFilterTest.java b/core-client/src/test/java/org/glassfish/jersey/client/filter/EncodingFilterTest.java index 22343bcd71..a48671fc27 100644 --- a/core-client/src/test/java/org/glassfish/jersey/client/filter/EncodingFilterTest.java +++ b/core-client/src/test/java/org/glassfish/jersey/client/filter/EncodingFilterTest.java @@ -39,7 +39,6 @@ import org.glassfish.jersey.client.spi.AsyncConnectorCallback; import org.glassfish.jersey.client.spi.Connector; import org.glassfish.jersey.client.spi.ConnectorProvider; -import org.glassfish.jersey.message.BrotliEncoder; import org.glassfish.jersey.message.DeflateEncoder; import org.glassfish.jersey.message.GZipEncoder; @@ -61,12 +60,11 @@ public void testAcceptEncoding() { Client client = ClientBuilder.newClient(new ClientConfig( EncodingFilter.class, GZipEncoder.class, - BrotliEncoder.class, DeflateEncoder.class ).connectorProvider(new TestConnector())); Invocation.Builder invBuilder = client.target(UriBuilder.fromUri("/").build()).request(); Response r = invBuilder.get(); - assertEquals("br,deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); + assertEquals("deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); assertNull(r.getHeaderString(CONTENT_ENCODING)); } @@ -75,12 +73,11 @@ public void testContentEncoding() { Client client = ClientBuilder.newClient(new ClientConfig( EncodingFilter.class, GZipEncoder.class, - BrotliEncoder.class, DeflateEncoder.class ).property(ClientProperties.USE_ENCODING, "gzip").connectorProvider(new TestConnector())); Invocation.Builder invBuilder = client.target(UriBuilder.fromUri("/").build()).request(); Response r = invBuilder.post(Entity.entity("Hello world", MediaType.TEXT_PLAIN_TYPE)); - assertEquals("br,deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); + assertEquals("deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); assertEquals("gzip", r.getHeaderString(CONTENT_ENCODING)); } @@ -88,10 +85,10 @@ public void testContentEncoding() { public void testContentEncodingViaFeature() { Client client = ClientBuilder.newClient(new ClientConfig() .connectorProvider(new TestConnector()) - .register(new EncodingFeature("gzip", GZipEncoder.class, BrotliEncoder.class, DeflateEncoder.class))); + .register(new EncodingFeature("gzip", GZipEncoder.class, DeflateEncoder.class))); Invocation.Builder invBuilder = client.target(UriBuilder.fromUri("/").build()).request(); Response r = invBuilder.post(Entity.entity("Hello world", MediaType.TEXT_PLAIN_TYPE)); - assertEquals("br,deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); + assertEquals("deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); assertEquals("gzip", r.getHeaderString(CONTENT_ENCODING)); } @@ -100,12 +97,11 @@ public void testContentEncodingSkippedForNoEntity() { Client client = ClientBuilder.newClient(new ClientConfig( EncodingFilter.class, GZipEncoder.class, - BrotliEncoder.class, DeflateEncoder.class ).property(ClientProperties.USE_ENCODING, "gzip").connectorProvider(new TestConnector())); Invocation.Builder invBuilder = client.target(UriBuilder.fromUri("/").build()).request(); Response r = invBuilder.get(); - assertEquals("br,deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); + assertEquals("deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); assertNull(r.getHeaderString(CONTENT_ENCODING)); } @@ -114,12 +110,11 @@ public void testUnsupportedContentEncoding() { Client client = ClientBuilder.newClient(new ClientConfig( EncodingFilter.class, GZipEncoder.class, - BrotliEncoder.class, DeflateEncoder.class ).property(ClientProperties.USE_ENCODING, "non-gzip").connectorProvider(new TestConnector())); Invocation.Builder invBuilder = client.target(UriBuilder.fromUri("/").build()).request(); Response r = invBuilder.get(); - assertEquals("br,deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); + assertEquals("deflate,gzip,x-gzip", r.getHeaderString(ACCEPT_ENCODING)); assertNull(r.getHeaderString(CONTENT_ENCODING)); } diff --git a/core-common/pom.xml b/core-common/pom.xml index 0ae1732534..2f61b3bfc1 100644 --- a/core-common/pom.xml +++ b/core-common/pom.xml @@ -206,10 +206,6 @@ - - com.oracle.brotli - brotli - jakarta.ws.rs jakarta.ws.rs-api diff --git a/core-common/src/test/java/org/glassfish/jersey/message/BrotliEncodingTest.java b/core-common/src/test/java/org/glassfish/jersey/message/BrotliEncodingTest.java deleted file mode 100644 index e081f0871a..0000000000 --- a/core-common/src/test/java/org/glassfish/jersey/message/BrotliEncodingTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ - -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.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -public class BrotliEncodingTest extends AbstractEncodingTest { - - @Test - public void testEncode() throws IOException { - test(new AbstractEncodingTest.TestSpec() { - @Override - public OutputStream getEncoded(OutputStream stream) throws IOException { - return new BrotliEncoder().encode("gzip", stream); - } - - @Override - public InputStream getDecoded(InputStream stream) throws IOException { - return BrotliInputStream.builder().inputStream(stream).build(); - } - }); - } - - @Test - public void testDecode() throws IOException { - test(new AbstractEncodingTest.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 AbstractEncodingTest.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); - } - }); - } -} diff --git a/incubator/brotli/pom.xml b/incubator/brotli/pom.xml new file mode 100644 index 0000000000..a2a448b4e1 --- /dev/null +++ b/incubator/brotli/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + 2.38-SNAPSHOT + + project + org.glassfish.jersey + 2.38-SNAPSHOT + ../../pom.xml + + + org.glassfish.jersey.message + jersey-brotli + jar + brotli + + + + + org.glassfish.jersey.core + jersey-common + ${project.version} + + + com.oracle.brotli + brotli + 1.0.0-SNAPSHOT + + + jakarta.ws.rs + jakarta.ws.rs-api + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-external + ${project.version} + test + + + org.junit.jupiter + junit-jupiter + test + + + org.hamcrest + hamcrest + test + + + + \ No newline at end of file diff --git a/core-common/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java b/incubator/brotli/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java similarity index 62% rename from core-common/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java rename to incubator/brotli/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java index f56cdcea6e..92083a6088 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java +++ b/incubator/brotli/src/main/java/org/glassfish/jersey/message/BrotliEncoder.java @@ -1,18 +1,3 @@ -/* - * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0, which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * This Source Code may also be made available under the following Secondary - * Licenses when the conditions for such availability set forth in the - * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, - * version 2 with the GNU Classpath Exception, which is available at - * https://www.gnu.org/software/classpath/license.html. - * - * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 - */ package org.glassfish.jersey.message; import com.oracle.brotli.decoder.BrotliInputStream; diff --git a/incubator/brotli/src/test/java/org/glassfish/jersey/message/BrotliEncoderTest.java b/incubator/brotli/src/test/java/org/glassfish/jersey/message/BrotliEncoderTest.java new file mode 100644 index 0000000000..3974a21f55 --- /dev/null +++ b/incubator/brotli/src/test/java/org/glassfish/jersey/message/BrotliEncoderTest.java @@ -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; + } +} diff --git a/incubator/brotli/src/test/java/org/glassfish/jersey/message/BrotliITTest.java b/incubator/brotli/src/test/java/org/glassfish/jersey/message/BrotliITTest.java new file mode 100644 index 0000000000..15731c028d --- /dev/null +++ b/incubator/brotli/src/test/java/org/glassfish/jersey/message/BrotliITTest.java @@ -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, ""); + assertResponseContains(response, ""); + } + + 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}); + } + } + +} \ No newline at end of file diff --git a/incubator/pom.xml b/incubator/pom.xml index 1a362714fa..98566634fe 100644 --- a/incubator/pom.xml +++ b/incubator/pom.xml @@ -42,6 +42,7 @@ html-json kryo open-tracing + brotli diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/file/DefaultMediaTypePredictor.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/file/DefaultMediaTypePredictor.java index 0bb0100709..56eb7155c6 100644 --- a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/file/DefaultMediaTypePredictor.java +++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/file/DefaultMediaTypePredictor.java @@ -49,7 +49,6 @@ public class DefaultMediaTypePredictor implements MediaTypePredictor { *
  • ".tar" - application/x-tar
  • *
  • ".zip" - application/zip
  • *
  • ".gz" - application/x-gzip
  • - *
  • ".br" - application/br
  • *
  • ".rar" - application/x-rar
  • *
  • ".mp3" - audio/mpeg
  • *
  • ".wav" - audio/x-wave
  • @@ -71,7 +70,6 @@ public enum CommonMediaTypes { TAR(".tar", new MediaType("application", "x-tar")), ZIP(".zip", new MediaType("application", "zip")), GZ(".gz", new MediaType("application", "x-gzip")), - BR("br", new MediaType("application", "br")), RAR(".rar", new MediaType("application", "x-rar")), MP3(".mp3", new MediaType("audio", "mpeg")), WAV(".wav", new MediaType("audio", "x-wave")), diff --git a/pom.xml b/pom.xml index 1759b48824..d98b4c8cfa 100644 --- a/pom.xml +++ b/pom.xml @@ -1531,11 +1531,6 @@ - - com.oracle.brotli - brotli - ${brotli.version} - jakarta.ws.rs jakarta.ws.rs-api @@ -2169,7 +2164,6 @@ 9.4 2.3.6 - 1.0.0-SNAPSHOT 2.11.0 3.3.2 1.2 diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/common/EncodingTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/common/EncodingTest.java index 47dfd57711..b9b899c333 100644 --- a/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/common/EncodingTest.java +++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/e2e/common/EncodingTest.java @@ -31,9 +31,7 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; -import com.oracle.brotli.decoder.BrotliInputStream; import org.glassfish.jersey.client.filter.EncodingFilter; -import org.glassfish.jersey.message.BrotliEncoder; import org.glassfish.jersey.message.DeflateEncoder; import org.glassfish.jersey.message.GZipEncoder; import org.glassfish.jersey.server.ResourceConfig; @@ -61,31 +59,10 @@ protected Application configure() { EchoResource.class, org.glassfish.jersey.server.filter.EncodingFilter.class, GZipEncoder.class, - BrotliEncoder.class, DeflateEncoder.class ); } - @Test - public void testBrotli() throws IOException { - test(new TestSpec() { - @Override - public InputStream decode(InputStream stream) throws IOException { - return BrotliInputStream.builder().inputStream(stream).build(); - } - - @Override - public void checkHeadersAndStatus(Response response) { - assertEquals("br", response.getHeaderString(HttpHeaders.CONTENT_ENCODING)); - } - - @Override - public Invocation.Builder setHeaders(Invocation.Builder invBuilder) { - return invBuilder.header(HttpHeaders.ACCEPT_ENCODING, "br"); - } - }); - } - @Test public void testGZip() throws IOException { test(new TestSpec() { @@ -184,29 +161,6 @@ public void checkHeadersAndStatus(Response response) { }); } - @Test - public void testBrotliPreferredClientServer() throws IOException { - test(new TestSpec() { - @Override - public Invocation.Builder setHeaders(Invocation.Builder invBuilder) { - return invBuilder.header(HttpHeaders.ACCEPT_ENCODING, "br,deflate,gzip"); - } - - @Override - public WebTarget configure(WebTarget target) { - target.register(DeflateEncoder.class) - .register(BrotliEncoder.class) - .register(EncodingFilter.class); - return target; - } - - @Override - public void checkHeadersAndStatus(Response response) { - assertEquals("br", response.getHeaderString(HttpHeaders.CONTENT_ENCODING)); - } - }); - } - private void test(TestSpec testSpec) throws IOException { String input = "hello"; Response response = testSpec.setHeaders(testSpec.configure(target()).request()) diff --git a/tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/BrotliITCase.java b/tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/BrotliITCase.java deleted file mode 100644 index db6e85fb7a..0000000000 --- a/tests/integration/servlet-4.0-mvc-1/src/test/java/org/glassfish/jersey/tests/integration/servlet_40_mvc_1/BrotliITCase.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.glassfish.jersey.tests.integration.servlet_40_mvc_1; - -import org.glassfish.jersey.message.BrotliEncoder; -import org.junit.jupiter.api.Test; - -import javax.ws.rs.core.Response; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; - -public class BrotliITCase extends TestSupport { - - private static final String CONTENT_ENCODING = "Content-Encoding"; - private static final String BR = "br"; - - @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)); - } - -} \ No newline at end of file