diff --git a/servicetalk-buffer-api/src/main/java/io/servicetalk/buffer/api/CharSequences.java b/servicetalk-buffer-api/src/main/java/io/servicetalk/buffer/api/CharSequences.java index 4405fee90b..a4966b5b31 100644 --- a/servicetalk-buffer-api/src/main/java/io/servicetalk/buffer/api/CharSequences.java +++ b/servicetalk-buffer-api/src/main/java/io/servicetalk/buffer/api/CharSequences.java @@ -206,39 +206,95 @@ public static int caseInsensitiveHashCode(CharSequence seq) { } /** - * Split a given {@link CharSequence} to separate ones on the given {@code delimiter}. - * The returned {@link CharSequence}s are created by invoking the {@link CharSequence#subSequence(int, int)} method - * on the main one. + * Split a given {@link #newAsciiString(Buffer) AsciiString} to separate ones on the given {@code delimiter}. * + * Trimming white-space before and after each token can be controlled by the {@code trim} flag * This method has no support for regex. * - * @param input The initial {@link CharSequence} to split, this experiences no side effects - * @param delimiter The delimiter character + * @param input The initial {@link CharSequence} to split, this experiences no side effects. + * @param delimiter The delimiter character. + * @param trim Flag to control whether the individual items must be trimmed. * @return a {@link List} of {@link CharSequence} subsequences of the input with the separated values */ - public static List split(final CharSequence input, final char delimiter) { + public static List split(final CharSequence input, final char delimiter, final boolean trim) { if (input.length() == 0) { return emptyList(); } + return trim ? splitWithTrim(input, isAsciiString(input), delimiter) : + split0(input, isAsciiString(input), delimiter); + } + + private static List split0(final CharSequence input, final boolean isAscii, final char delimiter) { int startIndex = 0; - List result = new ArrayList<>(); + + List result = new ArrayList<>(4); + for (int i = 0; i < input.length(); i++) { - if (input.charAt(i) == delimiter) { - if ((i - startIndex) > 0) { - result.add(input.subSequence(startIndex, i)); - } + char c = input.charAt(i); + if (c == delimiter) { + result.add(subsequence(isAscii, input, startIndex, i)); startIndex = i + 1; } } if ((input.length() - startIndex) > 0) { - result.add(input.subSequence(startIndex, input.length())); + result.add(subsequence(isAscii, input, startIndex, input.length())); + } else { + result.add(isAscii ? EMPTY_ASCII_BUFFER : ""); + } + + return result; + } + + private static List splitWithTrim(final CharSequence input, final boolean isAscii, + final char delimiter) { + int startIndex = -1; + int endIndex = -1; + boolean reset = true; + + List result = new ArrayList<>(4); + + for (int i = 0; i < input.length(); i++) { + char c = input.charAt(i); + if (c != ' ' && c != delimiter) { + endIndex = i + 1; + } + + if (reset && c != ' ' && c != delimiter) { + startIndex = i; + reset = false; + } else if (c == delimiter) { + if (endIndex > startIndex) { + result.add(subsequence(isAscii, input, startIndex, endIndex)); + } else { + result.add(isAscii ? EMPTY_ASCII_BUFFER : ""); + } + + startIndex = i + 1; + endIndex = i + 1; + reset = true; + } } + + if (startIndex != -1) { + if ((input.length() - startIndex) > 0) { + result.add(subsequence(isAscii, input, startIndex, endIndex)); + } else { + result.add(isAscii ? EMPTY_ASCII_BUFFER : ""); + } + } + return result; } + private static CharSequence subsequence(final boolean isAscii, final CharSequence input, + final int start, final int end) { + return isAscii ? newAsciiString(((AsciiBuffer) input).unwrap().copy(start, end - start)) : + input.subSequence(start, end); + } + private static boolean equalsIgnoreCase(final char a, final char b) { return a == b || toLowerCase(a) == toLowerCase(b); } diff --git a/servicetalk-buffer-api/src/test/java/io/servicetalk/buffer/api/CharSequencesTest.java b/servicetalk-buffer-api/src/test/java/io/servicetalk/buffer/api/CharSequencesTest.java new file mode 100644 index 0000000000..b05690fa16 --- /dev/null +++ b/servicetalk-buffer-api/src/test/java/io/servicetalk/buffer/api/CharSequencesTest.java @@ -0,0 +1,155 @@ +/* + * Copyright © 2021 Apple Inc. and the ServiceTalk project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.servicetalk.buffer.api; + +import org.junit.Test; + +import java.util.function.Function; + +import static io.servicetalk.buffer.api.CharSequences.split; +import static java.util.function.Function.identity; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; + +public class CharSequencesTest { + + // Common strings + public static final String GZIP = "gzip"; + public static final String DEFLATE = "deflate"; + public static final String COMPRESS = "compress"; + + private static void splitNoTrim(Function f) { + assertThat(split(f.apply(" , "), ',', false), + contains(f.apply(" "), f.apply(" "))); + assertThat(split(f.apply(" , ,"), ',', false), + contains(f.apply(" "), f.apply(" "), f.apply(""))); + assertThat(split(f.apply(" gzip , deflate "), ',', false), + contains(f.apply(" gzip "), f.apply(" deflate "))); + assertThat(split(f.apply(" gzip , deflate ,"), ',', false), + contains(f.apply(" gzip "), f.apply(" deflate "), f.apply(""))); + assertThat(split(f.apply("gzip, deflate"), ',', false), + contains(f.apply(GZIP), f.apply(" deflate"))); + assertThat(split(f.apply("gzip , deflate"), ',', false), + contains(f.apply("gzip "), f.apply(" deflate"))); + assertThat(split(f.apply("gzip , deflate"), ',', false), + contains(f.apply("gzip "), f.apply(" deflate"))); + assertThat(split(f.apply(" gzip, deflate"), ',', false), + contains(f.apply(" gzip"), f.apply(" deflate"))); + assertThat(split(f.apply(GZIP), ',', false), + contains(f.apply(GZIP))); + assertThat(split(f.apply("gzip,"), ',', false), + contains(f.apply(GZIP), f.apply(""))); + assertThat(split(f.apply("gzip,deflate,compress"), ',', false), + contains(f.apply(GZIP), f.apply(DEFLATE), f.apply(COMPRESS))); + assertThat(split(f.apply("gzip,,compress"), ',', false), + contains(f.apply(GZIP), f.apply(""), f.apply(COMPRESS))); + assertThat(split(f.apply("gzip, ,compress"), ',', false), + contains(f.apply(GZIP), f.apply(" "), f.apply(COMPRESS))); + assertThat(split(f.apply("gzip , , compress"), ',', false), + contains(f.apply("gzip "), f.apply(" "), f.apply(" compress"))); + assertThat(split(f.apply("gzip , white space word , compress"), ',', false), + contains(f.apply("gzip "), f.apply(" white space word "), f.apply(" compress"))); + assertThat(split(f.apply("gzip compress"), ' ', false), + contains(f.apply(GZIP), f.apply(COMPRESS))); + assertThat(split(f.apply("gzip compress"), ' ', false), + contains(f.apply(GZIP), f.apply(""), f.apply(""), f.apply(""), f.apply(""), + f.apply(COMPRESS))); + assertThat(split(f.apply(" gzip compress "), ' ', false), + contains(f.apply(""), f.apply(GZIP), f.apply(""), f.apply(""), f.apply(""), + f.apply(""), f.apply(COMPRESS), f.apply(""))); + assertThat(split(f.apply("gzip,,,,,compress"), ',', false), + contains(f.apply(GZIP), f.apply(""), f.apply(""), f.apply(""), f.apply(""), + f.apply(COMPRESS))); + assertThat(split(f.apply(",gzip,,,,,compress,"), ',', false), + contains(f.apply(""), f.apply(GZIP), f.apply(""), f.apply(""), f.apply(""), + f.apply(""), f.apply(COMPRESS), f.apply(""))); + assertThat(split(f.apply(",,,,"), ',', false), + contains(f.apply(""), f.apply(""), f.apply(""), f.apply(""), f.apply(""))); + assertThat(split(f.apply(" "), ' ', false), + contains(f.apply(""), f.apply(""), f.apply(""), f.apply(""), f.apply(""))); + } + + private static void splitWithTrim(Function f) { + assertThat(split(f.apply(" , "), ',', true), + contains(f.apply(""), f.apply(""))); + assertThat(split(f.apply(" , ,"), ',', true), + contains(f.apply(""), f.apply(""), f.apply(""))); + assertThat(split(f.apply(" gzip , deflate "), ',', true), + contains(f.apply(GZIP), f.apply(DEFLATE))); + assertThat(split(f.apply(" gzip , deflate ,"), ',', true), + contains(f.apply(GZIP), f.apply(DEFLATE), f.apply(""))); + assertThat(split(f.apply("gzip, deflate"), ',', true), + contains(f.apply(GZIP), f.apply(DEFLATE))); + assertThat(split(f.apply("gzip , deflate"), ',', true), + contains(f.apply(GZIP), f.apply(DEFLATE))); + assertThat(split(f.apply("gzip , deflate"), ',', true), + contains(f.apply(GZIP), f.apply(DEFLATE))); + assertThat(split(f.apply(" gzip, deflate"), ',', true), + contains(f.apply(GZIP), f.apply(DEFLATE))); + assertThat(split(f.apply(GZIP), ',', true), + contains(f.apply(GZIP))); + assertThat(split(f.apply("gzip,"), ',', true), + contains(f.apply(GZIP), f.apply(""))); + assertThat(split(f.apply("gzip,deflate,compress"), ',', true), + contains(f.apply(GZIP), f.apply(DEFLATE), f.apply(COMPRESS))); + assertThat(split(f.apply("gzip,,compress"), ',', true), + contains(f.apply(GZIP), f.apply(""), f.apply(COMPRESS))); + assertThat(split(f.apply("gzip, ,compress"), ',', true), + contains(f.apply(GZIP), f.apply(""), f.apply(COMPRESS))); + assertThat(split(f.apply("gzip , , compress"), ',', true), + contains(f.apply(GZIP), f.apply(""), f.apply(COMPRESS))); + assertThat(split(f.apply("gzip , white space word , compress"), ',', true), + contains(f.apply(GZIP), f.apply("white space word"), f.apply(COMPRESS))); + assertThat(split(f.apply("gzip compress"), ' ', true), + contains(f.apply(GZIP), f.apply(COMPRESS))); + assertThat(split(f.apply("gzip compress"), ' ', true), + contains(f.apply(GZIP), f.apply(""), f.apply(""), f.apply(""), f.apply(""), + f.apply(COMPRESS))); + assertThat(split(f.apply(" gzip compress "), ' ', true), + contains(f.apply(""), f.apply(GZIP), f.apply(""), f.apply(""), + f.apply(""), f.apply(""), f.apply(COMPRESS), f.apply(""))); + assertThat(split(f.apply("gzip,,,,,compress"), ',', true), + contains(f.apply(GZIP), f.apply(""), f.apply(""), f.apply(""), f.apply(""), + f.apply(COMPRESS))); + assertThat(split(f.apply(",gzip,,,,,compress,"), ',', true), + contains(f.apply(""), f.apply(GZIP), f.apply(""), f.apply(""), f.apply(""), + f.apply(""), f.apply(COMPRESS), f.apply(""))); + assertThat(split(f.apply(",,,,"), ',', true), + contains(f.apply(""), f.apply(""), f.apply(""), f.apply(""), f.apply(""))); + assertThat(split(f.apply(" "), ' ', true), + contains(f.apply(""), f.apply(""), f.apply(""), f.apply(""), f.apply(""))); + } + + @Test + public void splitStringNoTrim() { + splitNoTrim(identity()); + } + + @Test + public void splitStringWithTrim() { + splitWithTrim(identity()); + } + + @Test + public void splitAsciiNoTrim() { + splitNoTrim(CharSequences::newAsciiString); + } + + @Test + public void splitAsciiWithTrim() { + splitWithTrim(CharSequences::newAsciiString); + } +} diff --git a/servicetalk-encoding-api-internal/src/main/java/io/servicetalk/encoding/api/internal/HeaderUtils.java b/servicetalk-encoding-api-internal/src/main/java/io/servicetalk/encoding/api/internal/HeaderUtils.java index f722aedc1f..b63fb01264 100644 --- a/servicetalk-encoding-api-internal/src/main/java/io/servicetalk/encoding/api/internal/HeaderUtils.java +++ b/servicetalk-encoding-api-internal/src/main/java/io/servicetalk/encoding/api/internal/HeaderUtils.java @@ -24,6 +24,7 @@ import java.util.List; import javax.annotation.Nullable; +import static io.servicetalk.buffer.api.CharSequences.split; import static io.servicetalk.encoding.api.ContentCodings.identity; import static java.util.Collections.singletonList; import static java.util.Objects.requireNonNull; @@ -104,7 +105,7 @@ private static List parseAcceptEncoding(@Nullable final CharSequen } List knownEncodings = new ArrayList<>(); - List acceptEncodingValues = CharSequences.split(acceptEncodingHeaderValue, ','); + List acceptEncodingValues = split(acceptEncodingHeaderValue, ',', true); for (CharSequence val : acceptEncodingValues) { ContentCodec enc = encodingFor(allowedEncodings, val); if (enc != null) { diff --git a/servicetalk-grpc-netty/build.gradle b/servicetalk-grpc-netty/build.gradle index 7405e0cd63..03ef92135d 100644 --- a/servicetalk-grpc-netty/build.gradle +++ b/servicetalk-grpc-netty/build.gradle @@ -35,6 +35,7 @@ dependencies { testImplementation testFixtures(project(":servicetalk-concurrent-api")) testImplementation testFixtures(project(":servicetalk-concurrent-internal")) testImplementation testFixtures(project(":servicetalk-transport-netty-internal")) + testImplementation testFixtures(project(":servicetalk-buffer-api")) testImplementation project(":servicetalk-concurrent-api-internal") testImplementation project(":servicetalk-concurrent-test-internal") testImplementation project(":servicetalk-encoding-api-internal") diff --git a/servicetalk-grpc-netty/src/test/java/io/servicetalk/grpc/netty/GrpcMessageEncodingTest.java b/servicetalk-grpc-netty/src/test/java/io/servicetalk/grpc/netty/GrpcMessageEncodingTest.java index 1eeab1a728..346d1f1f69 100644 --- a/servicetalk-grpc-netty/src/test/java/io/servicetalk/grpc/netty/GrpcMessageEncodingTest.java +++ b/servicetalk-grpc-netty/src/test/java/io/servicetalk/grpc/netty/GrpcMessageEncodingTest.java @@ -49,14 +49,16 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.function.Supplier; import java.util.zip.DeflaterOutputStream; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import java.util.zip.InflaterInputStream; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import static io.grpc.internal.GrpcUtil.MESSAGE_ACCEPT_ENCODING; @@ -64,6 +66,7 @@ import static io.servicetalk.buffer.api.Buffer.asInputStream; import static io.servicetalk.buffer.api.Buffer.asOutputStream; import static io.servicetalk.buffer.api.CharSequences.contentEquals; +import static io.servicetalk.buffer.api.Matchers.contentEqualTo; import static io.servicetalk.concurrent.api.Publisher.from; import static io.servicetalk.concurrent.api.Single.failed; import static io.servicetalk.concurrent.api.Single.succeeded; @@ -84,7 +87,6 @@ import static java.util.Arrays.asList; import static java.util.Arrays.stream; import static java.util.Collections.disjoint; -import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; import static java.util.zip.GZIPInputStream.GZIP_MAGIC; @@ -92,7 +94,6 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.internal.util.io.IOUtil.closeQuietly; @@ -168,13 +169,13 @@ public String toString() { } }; - private static final Function REQ_RESP_VERIFIER = (options) - -> new StreamingHttpServiceFilterFactory() { + private static final BiFunction, StreamingHttpServiceFilterFactory> + REQ_RESP_VERIFIER = (options, errors) -> new StreamingHttpServiceFilterFactory() { @Override public StreamingHttpServiceFilter create(final StreamingHttpService service) { return new StreamingHttpServiceFilter(service) { - @Override + @Override public Single handle(final HttpServiceContext ctx, final StreamingHttpRequest request, final StreamingHttpResponseFactory responseFactory) { @@ -183,7 +184,6 @@ public Single handle(final HttpServiceContext ctx, final List serverSupportedEncodings = options.serverSupported; try { - request.transformPayloadBody(bufferPublisher -> bufferPublisher.map((buffer -> { try { byte compressedFlag = buffer.getByte(0); @@ -203,7 +203,7 @@ public Single handle(final HttpServiceContext ctx, assertEquals(reqEncoding != identity() ? 1 : 0, compressedFlag); } catch (Throwable t) { - t.printStackTrace(); + errors.add(t); throw t; } return buffer; @@ -211,15 +211,9 @@ public Single handle(final HttpServiceContext ctx, final List actualReqAcceptedEncodings = stream(request.headers() .get(MESSAGE_ACCEPT_ENCODING, "NOT_PRESENT").toString().split(",")) - .map((String::trim)).collect(toList()); + .map(String::trim).collect(toList()); - final List expectedReqAcceptedEncodings = (clientSupportedEncodings == null) ? - emptyList() : - clientSupportedEncodings.stream() - .filter((enc) -> enc != identity()) - .map((ContentCodec::name)) - .map((CharSequence::toString)) - .collect(toList()); + final List expectedReqAcceptedEncodings = encodingsAsStrings(clientSupportedEncodings); assertTrue("Request encoding should be present in the request headers", contentEquals(reqEncoding.name(), request.headers().get(MESSAGE_ENCODING, "identity"))); @@ -227,89 +221,87 @@ public Single handle(final HttpServiceContext ctx, assertEquals(expectedReqAcceptedEncodings, actualReqAcceptedEncodings); } } catch (Throwable t) { - t.printStackTrace(); + errors.add(t); throw t; } return super.handle(ctx, request, responseFactory).map((response -> { try { - final List actualRespAcceptedEncodings = stream(response.headers() - .get(MESSAGE_ACCEPT_ENCODING, "NOT_PRESENT").toString().split(",")) - .map((String::trim)).collect(toList()); - - final List expectedRespAcceptedEncodings = (serverSupportedEncodings == null) ? - emptyList() : - serverSupportedEncodings.stream() - .filter((enc) -> enc != identity()) - .map((ContentCodec::name)) - .map((CharSequence::toString)) - .collect(toList()); - - if (!expectedRespAcceptedEncodings.isEmpty() && !actualRespAcceptedEncodings.isEmpty()) { - assertEquals(expectedRespAcceptedEncodings, actualRespAcceptedEncodings); - } + handle0(clientSupportedEncodings, serverSupportedEncodings, response); + } catch (Throwable t) { + errors.add(t); + throw t; + } + + return response; + })); + } - final String respEncName = response.headers() - .get(MESSAGE_ENCODING, "identity").toString(); + private void handle0(final List clientSupportedEncodings, + final List serverSupportedEncodings, + final StreamingHttpResponse response) { + final List actualRespAcceptedEncodings = stream(response.headers() + .get(MESSAGE_ACCEPT_ENCODING, "NOT_PRESENT").toString().split(",")) + .map((String::trim)).collect(toList()); - if (clientSupportedEncodings == null) { - assertEquals(identity().name().toString(), respEncName); - } else if (serverSupportedEncodings == null) { - assertEquals(identity().name().toString(), respEncName); - } else { - if (disjoint(serverSupportedEncodings, clientSupportedEncodings)) { - assertEquals(identity().name().toString(), respEncName); - } else { - assertNotNull("Response encoding not in the client supported list " + - "[" + clientSupportedEncodings + "]", - encodingFor(clientSupportedEncodings, valueOf(response.headers() - .get(MESSAGE_ENCODING, "identity")))); - - assertNotNull("Response encoding not in the server supported list " + - "[" + serverSupportedEncodings + "]", - encodingFor(serverSupportedEncodings, valueOf(response.headers() - .get(MESSAGE_ENCODING, "identity")))); + final List expectedRespAcceptedEncodings = encodingsAsStrings(serverSupportedEncodings); + + if (!expectedRespAcceptedEncodings.isEmpty() && !actualRespAcceptedEncodings.isEmpty()) { + assertEquals(expectedRespAcceptedEncodings, actualRespAcceptedEncodings); + } + + final String respEncName = response.headers() + .get(MESSAGE_ENCODING, "identity").toString(); + + if (clientSupportedEncodings.isEmpty() || serverSupportedEncodings.isEmpty()) { + assertThat(identity().name(), contentEqualTo(respEncName)); + } else { + if (disjoint(serverSupportedEncodings, clientSupportedEncodings)) { + assertEquals(identity().name().toString(), respEncName); + } else { + ContentCodec expected = identity(); + for (ContentCodec codec : clientSupportedEncodings) { + if (serverSupportedEncodings.contains(codec)) { + expected = codec; + break; } } - response.transformPayloadBody(bufferPublisher -> bufferPublisher.map((buffer -> { - try { - final ContentCodec respEnc = - encodingFor(clientSupportedEncodings == null ? asList(identity()) : - clientSupportedEncodings, valueOf(response.headers() - .get(MESSAGE_ENCODING, "identity"))); - - if (buffer.readableBytes() > 0) { - byte compressedFlag = buffer.getByte(0); - assertEquals(respEnc != identity() ? 1 : 0, compressedFlag); - - if (respEnc == gzipDefault() || respEnc.name().equals(CUSTOM_ENCODING.name())) { - int actualHeader = buffer.getShortLE(5) & 0xFFFF; - assertEquals(GZIP_MAGIC, actualHeader); - } - - if (respEnc != identity()) { - assertTrue("Compressed content length should be less than the original " + - "payload size", buffer.readableBytes() < PAYLOAD_SIZE); - } else { - assertTrue("Uncompressed content length should be more than the original " + - "payload size " + buffer.readableBytes(), - buffer.readableBytes() > PAYLOAD_SIZE); - } - } - } catch (Throwable t) { - t.printStackTrace(); - throw t; + assertEquals(expected, encodingFor(clientSupportedEncodings, response.headers() + .get(MESSAGE_ENCODING, identity().name()))); + } + } + + response.transformPayloadBody(bufferPublisher -> bufferPublisher.map((buffer -> { + try { + final ContentCodec respEnc = + encodingFor(clientSupportedEncodings, valueOf(response.headers() + .get(MESSAGE_ENCODING, "identity"))); + + if (buffer.readableBytes() > 0) { + byte compressedFlag = buffer.getByte(0); + assertEquals(respEnc != identity() ? 1 : 0, compressedFlag); + + if (respEnc == gzipDefault() || respEnc.name().equals(CUSTOM_ENCODING.name())) { + int actualHeader = buffer.getShortLE(5) & 0xFFFF; + assertEquals(GZIP_MAGIC, actualHeader); + } + + if (respEnc != identity()) { + assertTrue("Compressed content length should be less than the original " + + "payload size", buffer.readableBytes() < PAYLOAD_SIZE); + } else { + assertTrue("Uncompressed content length should be more than the original " + + "payload size " + buffer.readableBytes(), + buffer.readableBytes() > PAYLOAD_SIZE); } - return buffer; - }))); + } } catch (Throwable t) { - t.printStackTrace(); + errors.add(t); throw t; } - - return response; - })); + return buffer; + }))); } }; } @@ -359,6 +351,7 @@ public Publisher testResponseStream(GrpcServiceContext ctx, TestRe private final TesterClient client; private final ContentCodec requestEncoding; private final boolean expectedSuccess; + private final List errors = Collections.synchronizedList(new ArrayList<>()); public GrpcMessageEncodingTest(final List serverSupportedCodings, final List clientSupportedCodings, @@ -395,6 +388,7 @@ public static Object[][] params() { {singletonList(gzipDefault()), asList(gzipDefault(), identity()), identity(), true}, {singletonList(gzipDefault()), asList(gzipDefault(), identity()), identity(), true}, {singletonList(gzipDefault()), asList(gzipDefault(), identity()), gzipDefault(), true}, + {singletonList(gzipDefault()), asList(deflateDefault(), gzipDefault()), gzipDefault(), true}, {null, asList(gzipDefault(), identity()), gzipDefault(), false}, {null, asList(gzipDefault(), deflateDefault(), identity()), deflateDefault(), false}, {null, asList(gzipDefault(), identity()), identity(), true}, @@ -413,14 +407,9 @@ public void tearDown() throws Exception { private ServerContext listenAndAwait(final TestEncodingScenario encodingOptions) throws Exception { - StreamingHttpServiceFilterFactory filterFactory = REQ_RESP_VERIFIER.apply(encodingOptions); - if (encodingOptions.serverSupported == null) { - return grpcServerBuilder.appendHttpServiceFilter(filterFactory) - .listenAndAwait(new ServiceFactory(new TesterServiceImpl())); - } else { - return grpcServerBuilder.appendHttpServiceFilter(filterFactory) - .listenAndAwait(new ServiceFactory(new TesterServiceImpl(), encodingOptions.serverSupported)); - } + StreamingHttpServiceFilterFactory filterFactory = REQ_RESP_VERIFIER.apply(encodingOptions, errors); + return grpcServerBuilder.appendHttpServiceFilter(filterFactory) + .listenAndAwait(new ServiceFactory(new TesterServiceImpl(), encodingOptions.serverSupported)); } private TesterClient newClient(@Nullable final List supportedCodings) { @@ -431,12 +420,20 @@ private TesterClient newClient(@Nullable final List supportedCodin } @Test - public void test() throws ExecutionException, InterruptedException { + public void test() throws Throwable { if (expectedSuccess) { assertSuccessful(requestEncoding); } else { assertUnimplemented(requestEncoding); } + + verifyNoErrors(); + } + + private void verifyNoErrors() throws Throwable { + if (!errors.isEmpty()) { + throw errors.get(0); + } } private static TestRequest request() { @@ -476,19 +473,26 @@ private static void assertGrpcStatusException(GrpcStatusException grpcStatusExce assertThat(grpcStatusException.status().code(), is(GrpcStatusCode.UNIMPLEMENTED)); } + @Nonnull + private static List encodingsAsStrings(final List supportedEncodings) { + return supportedEncodings.stream() + .filter(enc -> enc != identity()) + .map(ContentCodec::name) + .map(CharSequence::toString) + .collect(toList()); + } + static class TestEncodingScenario { final ContentCodec requestEncoding; - @Nullable final List clientSupported; - @Nullable final List serverSupported; TestEncodingScenario(final ContentCodec requestEncoding, - final List clientSupported, - final List serverSupported) { + @Nullable final List clientSupported, + @Nullable final List serverSupported) { this.requestEncoding = requestEncoding; - this.clientSupported = clientSupported; - this.serverSupported = serverSupported; + this.clientSupported = clientSupported == null ? singletonList(identity()) : clientSupported; + this.serverSupported = serverSupported == null ? singletonList(identity()) : serverSupported; } } } diff --git a/servicetalk-http-api/src/main/java/io/servicetalk/http/api/CharSequences.java b/servicetalk-http-api/src/main/java/io/servicetalk/http/api/CharSequences.java index 9c43da0611..8a780fd11e 100644 --- a/servicetalk-http-api/src/main/java/io/servicetalk/http/api/CharSequences.java +++ b/servicetalk-http-api/src/main/java/io/servicetalk/http/api/CharSequences.java @@ -133,7 +133,7 @@ public static int indexOf(CharSequence sequence, char c, int fromIndex) { * @return a {@link List} of {@link CharSequence} subsequences of the input with the separated values */ public static List split(final CharSequence input, final char delimiter) { - return io.servicetalk.buffer.api.CharSequences.split(input, delimiter); + return io.servicetalk.buffer.api.CharSequences.split(input, delimiter, false); } /**