From 8c0d0994eddf443d726e8f28a2ba68e75a37f284 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Mon, 16 Jul 2018 15:14:08 +0100 Subject: [PATCH 01/26] New classes --- .../styx/api/cookies/RequestCookie.java | 134 ++++++++++++ .../styx/api/cookies/ResponseCookie.java | 201 ++++++++++++++++++ .../styx/api/cookies/RequestCookieTest.java | 38 ++++ .../styx/api/cookies/ResponseCookieTest.java | 63 ++++++ 4 files changed, 436 insertions(+) create mode 100644 components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java create mode 100644 components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java create mode 100644 components/api/src/test/java/com/hotels/styx/api/cookies/RequestCookieTest.java create mode 100644 components/api/src/test/java/com/hotels/styx/api/cookies/ResponseCookieTest.java diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java new file mode 100644 index 0000000000..599134594e --- /dev/null +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java @@ -0,0 +1,134 @@ +/* + Copyright (C) 2013-2018 Expedia Inc. + + 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 com.hotels.styx.api.cookies; + +import com.google.common.base.Objects; +import com.hotels.styx.api.HttpHeaders; +import io.netty.handler.codec.http.cookie.ClientCookieEncoder; +import io.netty.handler.codec.http.cookie.Cookie; +import io.netty.handler.codec.http.cookie.DefaultCookie; +import io.netty.handler.codec.http.cookie.ServerCookieDecoder; + +import java.util.Collection; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.isNullOrEmpty; +import static com.hotels.styx.api.HttpHeaderNames.COOKIE; +import static com.hotels.styx.api.common.Strings.quote; +import static java.util.stream.Collectors.toSet; + +/** + * Represents an HTTP cookie as sent in the {@code Cookie} header of an HTTP request. + */ +public final class RequestCookie { + private final String name; + private final String value; + private final int hashCode; + + /** + * Constructs a cookie with a name, value and attributes. + * + * @param name cookie name + * @param value cookie value + */ + private RequestCookie(String name, String value) { + checkArgument(!isNullOrEmpty(name), "name cannot be null or empty"); + checkNotNull(value, "value cannot be null"); + this.name = name; + this.value = value; + this.hashCode = Objects.hashCode(name, value); + } + + /** + * Constructs a cookie with a name, value and attributes. + * + * @param name cookie name + * @param value cookie value + * @return a cookie + */ + public static RequestCookie cookie(String name, String value) { + return new RequestCookie(name, value); + } + + public static Set decode(HttpHeaders headers) { + return headers.getAll(COOKIE).stream() + .map(ServerCookieDecoder.LAX::decode) + .flatMap(Collection::stream) + .map(RequestCookie::convert) + .collect(toSet()); + } + + public static void encode(HttpHeaders.Builder headers, Collection cookies) { + Set nettyCookies = cookies.stream() + .map(RequestCookie::convert) + .collect(toSet()); + + headers.set(COOKIE, ClientCookieEncoder.LAX.encode(nettyCookies)); + } + + private static Cookie convert(RequestCookie cookie) { + return new DefaultCookie(cookie.name, cookie.value); + } + + private static RequestCookie convert(Cookie nettyCookie) { + String name = nettyCookie.name(); + String value = nettyCookie.wrap() ? quote(nettyCookie.value()) : nettyCookie.value(); + + return cookie(name, value); + } + + /** + * Returns cookie name. + * + * @return cookie name + */ + public String name() { + return name; + } + + /** + * Returns cookie value. + * + * @return cookie value + */ + public String value() { + return value; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + RequestCookie other = (RequestCookie) obj; + return Objects.equal(name, other.name) && Objects.equal(value, other.value); + } + + @Override + public String toString() { + return name + "=" + value; + } +} diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java new file mode 100644 index 0000000000..2e6aaa769a --- /dev/null +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java @@ -0,0 +1,201 @@ +/* + Copyright (C) 2013-2018 Expedia Inc. + + 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 com.hotels.styx.api.cookies; + +import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.hotels.styx.api.HttpCookieAttribute; +import com.hotels.styx.api.HttpHeaders; +import io.netty.handler.codec.http.cookie.ClientCookieDecoder; +import io.netty.handler.codec.http.cookie.Cookie; +import io.netty.handler.codec.http.cookie.DefaultCookie; +import io.netty.handler.codec.http.cookie.ServerCookieEncoder; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.isNullOrEmpty; +import static com.google.common.collect.Iterables.isEmpty; +import static com.google.common.collect.Lists.newArrayList; +import static com.hotels.styx.api.HttpCookieAttribute.domain; +import static com.hotels.styx.api.HttpCookieAttribute.httpOnly; +import static com.hotels.styx.api.HttpCookieAttribute.maxAge; +import static com.hotels.styx.api.HttpCookieAttribute.path; +import static com.hotels.styx.api.HttpCookieAttribute.secure; +import static com.hotels.styx.api.HttpHeaderNames.SET_COOKIE; +import static com.hotels.styx.api.common.Strings.quote; +import static java.util.stream.Collectors.toSet; + +/** + * Represents an HTTP cookie as sent in the {@code Set-Cookie} header. + */ +public final class ResponseCookie { + private static final Joiner JOINER_ON_SEMI_COLON_AND_SPACE = Joiner.on("; "); + private final String name; + private final String value; + private final int hashCode; + private final Iterable attributes; + + /** + * Constructs a cookie with a name, value and attributes. + * + * @param name cookie name + * @param value cookie value + * @param attributes cookie attributes + */ + private ResponseCookie(String name, String value, Iterable attributes) { + checkArgument(!isNullOrEmpty(name), "name cannot be null or empty"); + checkNotNull(value, "value cannot be null"); + this.name = name; + this.value = value; + this.attributes = checkNotNull(attributes); + this.hashCode = Objects.hashCode(name, value, attributes); + } + + /** + * Constructs a cookie with a name, value and attributes. + * + * @param name cookie name + * @param value cookie value + * @param attributes cookie attributes + * @return a cookie + */ + public static ResponseCookie cookie(String name, String value, HttpCookieAttribute... attributes) { + return new ResponseCookie(name, value, nonNulls(attributes)); + } + + // throws exception if any values are null + private static Collection nonNulls(X... array) { + for (X item : array) { + checkNotNull(item); + } + + return newArrayList(array); + } + + /** + * Constructs a cookie with a name, value and attributes. + * + * @param name cookie name + * @param value cookie value + * @param attributes cookie attributes + * @return a cookie + */ + public static ResponseCookie cookie(String name, String value, Iterable attributes) { + return new ResponseCookie(name, value, attributes); + } + + public static Set decode(HttpHeaders headers) { + return headers.getAll(SET_COOKIE).stream() + .map(ClientCookieDecoder.LAX::decode) + .map(ResponseCookie::convert) + .collect(toSet()); + } + + public static void encode(HttpHeaders.Builder headers, Collection cookies) { + Set nettyCookies = cookies.stream() + .map(ResponseCookie::convert) + .collect(toSet()); + + headers.set(SET_COOKIE, ServerCookieEncoder.LAX.encode(nettyCookies)); + } + + private static Cookie convert(ResponseCookie cookie) { + return new DefaultCookie(cookie.name, cookie.value); + } + + private static ResponseCookie convert(Cookie cookie) { + Iterable attributes = new ArrayList() { + { + if (!isNullOrEmpty(cookie.domain())) { + add(domain(cookie.domain())); + } + if (!isNullOrEmpty(cookie.path())) { + add(path(cookie.path())); + } + if (cookie.maxAge() != Long.MIN_VALUE) { + add(maxAge((int) cookie.maxAge())); + } + if (cookie.isHttpOnly()) { + add(httpOnly()); + } + if (cookie.isSecure()) { + add(secure()); + } + } + }; + String value = cookie.wrap() ? quote(cookie.value()) : cookie.value(); + return cookie(cookie.name(), value, attributes); + } + + /** + * Returns cookie name. + * + * @return cookie name + */ + public String name() { + return name; + } + + /** + * Returns cookie value. + * + * @return cookie value + */ + public String value() { + return value; + } + + /** + * Returns cookie attributes. + * + * @return cookie attributes + */ + public Iterable attributes() { + return attributes; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ResponseCookie other = (ResponseCookie) obj; + return Objects.equal(name, other.name) && Objects.equal(value, other.value) && Objects.equal(attributes, other.attributes); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder() + .append(name) + .append('=') + .append(value) + .append(isEmpty(attributes) ? "" : "; "); + + return JOINER_ON_SEMI_COLON_AND_SPACE.appendTo(builder, attributes).toString(); + } +} diff --git a/components/api/src/test/java/com/hotels/styx/api/cookies/RequestCookieTest.java b/components/api/src/test/java/com/hotels/styx/api/cookies/RequestCookieTest.java new file mode 100644 index 0000000000..82cf9e9a52 --- /dev/null +++ b/components/api/src/test/java/com/hotels/styx/api/cookies/RequestCookieTest.java @@ -0,0 +1,38 @@ +/* + Copyright (C) 2013-2018 Expedia Inc. + + 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 com.hotels.styx.api.cookies; + +import org.testng.annotations.Test; + +import static com.hotels.styx.api.cookies.RequestCookie.cookie; + +public class RequestCookieTest { + + @Test(expectedExceptions = IllegalArgumentException.class) + public void acceptsOnlyNonEmptyName() { + cookie("", "value"); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void acceptsOnlyNonNullName() { + cookie(null, "value"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void acceptsOnlyNonNullValue() { + cookie("name", null); + } +} \ No newline at end of file diff --git a/components/api/src/test/java/com/hotels/styx/api/cookies/ResponseCookieTest.java b/components/api/src/test/java/com/hotels/styx/api/cookies/ResponseCookieTest.java new file mode 100644 index 0000000000..1b29276b73 --- /dev/null +++ b/components/api/src/test/java/com/hotels/styx/api/cookies/ResponseCookieTest.java @@ -0,0 +1,63 @@ +/* + Copyright (C) 2013-2018 Expedia Inc. + + 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 com.hotels.styx.api.cookies; + +import org.testng.annotations.Test; + +import static com.hotels.styx.api.HttpCookieAttribute.domain; +import static com.hotels.styx.api.HttpCookieAttribute.maxAge; +import static com.hotels.styx.api.cookies.ResponseCookie.cookie; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; + +public class ResponseCookieTest { + + @Test(expectedExceptions = IllegalArgumentException.class) + public void acceptsOnlyNonEmptyName() { + cookie("", "value", domain(".hotels.com")); + } + + @Test(expectedExceptions = IllegalArgumentException.class) + public void acceptsOnlyNonNullName() { + cookie(null, "value", domain(".hotels.com")); + } + + @Test(expectedExceptions = NullPointerException.class) + public void acceptsOnlyNonNullValue() { + cookie("name", null, domain(".hotels.com")); + } + + @Test + public void createsCookieWithOneAttribute() { + ResponseCookie cookie = cookie("name", "value", domain(".hotels.com")); + assertThat(cookie.toString(), is("name=value; Domain=.hotels.com")); + assertThat(cookie.attributes(), contains(domain(".hotels.com"))); + } + + @Test + public void createsCookieWithMultipleAttribute() { + ResponseCookie cookie = cookie("name", "value", domain(".hotels.com"), maxAge(4000)); + assertThat(cookie.toString(), is("name=value; Domain=.hotels.com; Max-Age=4000")); + assertThat(cookie.attributes(), containsInAnyOrder(domain(".hotels.com"), maxAge(4000))); + } + + @Test(expectedExceptions = NullPointerException.class) + public void attributesCannotBeNull() { + cookie("name", "value", domain(".hotels.com"), null, maxAge(4000)); + } +} \ No newline at end of file From 496d3bfdad84a625b3429bce04e413b0421dbee7 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Mon, 16 Jul 2018 15:24:08 +0100 Subject: [PATCH 02/26] Move ServerCookieEncoder and use it --- .../hotels/styx/api/cookies}/CookieUtil.java | 2 +- .../styx/api/cookies/ResponseCookie.java | 1 - .../api/cookies}/ServerCookieEncoder.java | 26 ++++++++----------- .../api/cookies}/ServerCookieEncoderTest.java | 2 +- .../StyxToNettyResponseTranslator.java | 2 +- 5 files changed, 14 insertions(+), 19 deletions(-) rename components/{server/src/main/java/com/hotels/styx/server/netty/codec => api/src/main/java/com/hotels/styx/api/cookies}/CookieUtil.java (99%) rename components/{server/src/main/java/com/hotels/styx/server/netty/codec => api/src/main/java/com/hotels/styx/api/cookies}/ServerCookieEncoder.java (89%) rename components/{server/src/test/java/com/hotels/styx/server/netty/codec => api/src/test/java/com/hotels/styx/api/cookies}/ServerCookieEncoderTest.java (97%) diff --git a/components/server/src/main/java/com/hotels/styx/server/netty/codec/CookieUtil.java b/components/api/src/main/java/com/hotels/styx/api/cookies/CookieUtil.java similarity index 99% rename from components/server/src/main/java/com/hotels/styx/server/netty/codec/CookieUtil.java rename to components/api/src/main/java/com/hotels/styx/api/cookies/CookieUtil.java index 8bb80d2570..8538c3fe46 100644 --- a/components/server/src/main/java/com/hotels/styx/server/netty/codec/CookieUtil.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/CookieUtil.java @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package com.hotels.styx.server.netty.codec; +package com.hotels.styx.api.cookies; import io.netty.handler.codec.http.HttpConstants; import io.netty.util.internal.InternalThreadLocalMap; diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java index 2e6aaa769a..5b54933c54 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java @@ -22,7 +22,6 @@ import io.netty.handler.codec.http.cookie.ClientCookieDecoder; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.codec.http.cookie.DefaultCookie; -import io.netty.handler.codec.http.cookie.ServerCookieEncoder; import java.util.ArrayList; import java.util.Collection; diff --git a/components/server/src/main/java/com/hotels/styx/server/netty/codec/ServerCookieEncoder.java b/components/api/src/main/java/com/hotels/styx/api/cookies/ServerCookieEncoder.java similarity index 89% rename from components/server/src/main/java/com/hotels/styx/server/netty/codec/ServerCookieEncoder.java rename to components/api/src/main/java/com/hotels/styx/api/cookies/ServerCookieEncoder.java index 7560b04951..b13079b334 100644 --- a/components/server/src/main/java/com/hotels/styx/server/netty/codec/ServerCookieEncoder.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/ServerCookieEncoder.java @@ -13,12 +13,8 @@ * License for the specific language governing permissions and limitations * under the License. */ -package com.hotels.styx.server.netty.codec; +package com.hotels.styx.api.cookies; -import static com.hotels.styx.server.netty.codec.CookieUtil.add; -import static com.hotels.styx.server.netty.codec.CookieUtil.addQuoted; -import static com.hotels.styx.server.netty.codec.CookieUtil.stringBuilder; -import static com.hotels.styx.server.netty.codec.CookieUtil.stripTrailingSeparator; import static io.netty.util.internal.ObjectUtil.checkNotNull; import io.netty.handler.codec.http.HttpHeaderDateFormat; @@ -95,37 +91,37 @@ public String encode(Cookie cookie) { validateCookie(name, value); - StringBuilder buf = stringBuilder(); + StringBuilder buf = CookieUtil.stringBuilder(); if (cookie.wrap()) { - addQuoted(buf, name, value); + CookieUtil.addQuoted(buf, name, value); } else { - add(buf, name, value); + CookieUtil.add(buf, name, value); } if (cookie.maxAge() != Long.MIN_VALUE) { if (cookie.maxAge() >= 0) { - add(buf, CookieHeaderNames.MAX_AGE, cookie.maxAge()); + CookieUtil.add(buf, CookieHeaderNames.MAX_AGE, cookie.maxAge()); } Date expires = new Date(cookie.maxAge() * 1000 + System.currentTimeMillis()); - add(buf, CookieHeaderNames.EXPIRES, HttpHeaderDateFormat.get().format(expires)); + CookieUtil.add(buf, CookieHeaderNames.EXPIRES, HttpHeaderDateFormat.get().format(expires)); } if (cookie.path() != null) { - add(buf, CookieHeaderNames.PATH, cookie.path()); + CookieUtil.add(buf, CookieHeaderNames.PATH, cookie.path()); } if (cookie.domain() != null) { - add(buf, CookieHeaderNames.DOMAIN, cookie.domain()); + CookieUtil.add(buf, CookieHeaderNames.DOMAIN, cookie.domain()); } if (cookie.isSecure()) { - add(buf, CookieHeaderNames.SECURE); + CookieUtil.add(buf, CookieHeaderNames.SECURE); } if (cookie.isHttpOnly()) { - add(buf, CookieHeaderNames.HTTPONLY); + CookieUtil.add(buf, CookieHeaderNames.HTTPONLY); } - return stripTrailingSeparator(buf); + return CookieUtil.stripTrailingSeparator(buf); } /** Deduplicate a list of encoded cookies by keeping only the last instance with a given name. diff --git a/components/server/src/test/java/com/hotels/styx/server/netty/codec/ServerCookieEncoderTest.java b/components/api/src/test/java/com/hotels/styx/api/cookies/ServerCookieEncoderTest.java similarity index 97% rename from components/server/src/test/java/com/hotels/styx/server/netty/codec/ServerCookieEncoderTest.java rename to components/api/src/test/java/com/hotels/styx/api/cookies/ServerCookieEncoderTest.java index 995adf621f..31ef322b1d 100644 --- a/components/server/src/test/java/com/hotels/styx/server/netty/codec/ServerCookieEncoderTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/cookies/ServerCookieEncoderTest.java @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package com.hotels.styx.server.netty.codec; +package com.hotels.styx.api.cookies; import io.netty.handler.codec.http.cookie.ClientCookieDecoder; import io.netty.handler.codec.http.cookie.Cookie; diff --git a/components/server/src/main/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslator.java b/components/server/src/main/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslator.java index 542160d77b..7211b12431 100644 --- a/components/server/src/main/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslator.java +++ b/components/server/src/main/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslator.java @@ -24,7 +24,7 @@ import io.netty.handler.codec.http.cookie.DefaultCookie; import static com.hotels.styx.api.HttpHeaderNames.SET_COOKIE; -import static com.hotels.styx.server.netty.codec.ServerCookieEncoder.LAX; +import static com.hotels.styx.api.cookies.ServerCookieEncoder.LAX; import static java.lang.Integer.parseInt; class StyxToNettyResponseTranslator implements ResponseTranslator { From 6ec463ff9077feffa762b7cab2f24e6b7b30d11b Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Mon, 16 Jul 2018 17:24:05 +0100 Subject: [PATCH 03/26] Use new API in HttpRequest --- .../com/hotels/styx/api/FullHttpRequest.java | 3 +- .../java/com/hotels/styx/api/HttpRequest.java | 91 +++------- .../com/hotels/styx/api/HttpResponse.java | 12 +- .../hotels/styx/api/StreamingHttpMessage.java | 18 -- .../hotels/styx/api/cookies/PseudoMap.java | 165 ++++++++++++++++++ .../styx/api/cookies/RequestCookie.java | 14 +- .../hotels/styx/api/FullHttpRequestTest.java | 6 +- .../com/hotels/styx/api/HttpRequestTest.java | 101 ++++------- .../HttpErrorStatusCauseLoggerTest.java | 16 +- .../hotels/styx/client/StyxHttpClient.java | 12 +- .../connectionpool/HttpRequestOperation.java | 19 +- .../styx/client/StickySessionSpec.scala | 14 +- .../styx/client/StyxHttpClientTest.java | 16 +- .../HttpRequestOperationTest.java | 14 +- .../StyxBackendServiceClientFactoryTest.java | 15 +- .../HttpMessageLoggingInterceptorTest.java | 11 +- .../codec/NettyToStyxRequestDecoder.java | 15 -- .../styx/server/routing/AntlrMatcher.java | 4 +- .../codec/NettyToStyxRequestDecoderTest.java | 22 ++- .../routing/antlr/AntlrConditionTest.java | 46 ++--- .../routing/antlr/FunctionResolverTest.java | 11 +- 21 files changed, 335 insertions(+), 290 deletions(-) create mode 100644 components/api/src/main/java/com/hotels/styx/api/cookies/PseudoMap.java diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java index 89c6be5a93..3628334873 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java @@ -373,7 +373,8 @@ public Builder(HttpRequest request, byte[] body) { this.version = request.version(); this.headers = request.headers().newBuilder(); this.body = body; - this.cookies = new ArrayList<>(request.cookies()); +// this.cookies = new ArrayList<>(request.cookies()); + this.cookies = new ArrayList<>(); } Builder(FullHttpRequest request) { diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java index bb7bb50f2c..2bd2632f57 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java @@ -15,7 +15,8 @@ */ package com.hotels.styx.api; -import com.google.common.collect.ImmutableList; +import com.hotels.styx.api.cookies.PseudoMap; +import com.hotels.styx.api.cookies.RequestCookie; import com.hotels.styx.api.messages.HttpMethod; import com.hotels.styx.api.messages.HttpVersion; import io.netty.buffer.ByteBuf; @@ -23,14 +24,12 @@ import rx.Observable; import java.net.InetSocketAddress; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; import static com.hotels.styx.api.FlowControlDisableOperator.disableFlowControl; import static com.hotels.styx.api.HttpHeaderNames.CONNECTION; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; @@ -46,8 +45,6 @@ import static com.hotels.styx.api.messages.HttpMethod.httpMethod; import static com.hotels.styx.api.messages.HttpVersion.HTTP_1_1; import static com.hotels.styx.api.messages.HttpVersion.httpVersion; -import static com.hotels.styx.api.support.CookiesSupport.findCookie; -import static com.hotels.styx.api.support.CookiesSupport.isCookieHeader; import static io.netty.buffer.ByteBufUtil.getBytes; import static io.netty.buffer.Unpooled.compositeBuffer; import static io.netty.buffer.Unpooled.copiedBuffer; @@ -55,8 +52,10 @@ import static java.lang.Integer.parseInt; import static java.lang.String.format; import static java.net.InetSocketAddress.createUnresolved; +import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; import static java.util.UUID.randomUUID; +import static java.util.stream.Collectors.toSet; /** * HTTP request with a fully aggregated/decoded body. @@ -71,7 +70,6 @@ public class HttpRequest implements StreamingHttpMessage { private final HttpHeaders headers; private final boolean secure; private final StyxObservable body; - private final List cookies; HttpRequest(Builder builder) { this.id = builder.id == null ? randomUUID() : builder.id; @@ -82,7 +80,6 @@ public class HttpRequest implements StreamingHttpMessage { this.secure = builder.secure; this.headers = builder.headers.build(); this.body = requireNonNull(builder.body); - this.cookies = ImmutableList.copyOf(builder.cookies); } /** @@ -191,23 +188,6 @@ public HttpHeaders headers() { return headers; } - @Override - public List cookies() { - return cookies; - } - - /* - * Returns an {@link Optional} containing the {@link HttpCookie} with the specified {@code name} - * if such a cookie exists. - * - * @param name the name of the cookie - * @return returns an optional cookie object from the header - */ - public Optional cookie(String name) { - return findCookie(cookies, name); - } - - @Override public List headers(CharSequence name) { return headers.getAll(name); @@ -362,6 +342,10 @@ private static byte[] decodeAndRelease(CompositeByteBuf aggregate) { } } + public PseudoMap cookies() { + return RequestCookie.decode(headers); + } + @Override public String toString() { return toStringHelper(this) @@ -369,7 +353,6 @@ public String toString() { .add("method", method) .add("uri", url) .add("headers", headers) - .add("cookies", cookies) .add("id", id) .add("secure", secure) .add("clientAddress", clientAddress) @@ -391,13 +374,11 @@ public static final class Builder { private HttpHeaders.Builder headers; private HttpVersion version = HTTP_1_1; private StyxObservable body; - private final List cookies; public Builder() { this.url = Url.Builder.url("/").build(); this.headers = new HttpHeaders.Builder(); this.body = new StyxCoreObservable<>(Observable.empty()); - this.cookies = new ArrayList<>(); } public Builder(HttpMethod method, String uri) { @@ -416,7 +397,6 @@ public Builder(HttpRequest request, StyxObservable body) { this.version = httpVersion(request.version().toString()); this.headers = request.headers().newBuilder(); this.body = body; - this.cookies = new ArrayList<>(request.cookies()); } Builder(HttpRequest request) { @@ -428,7 +408,6 @@ public Builder(HttpRequest request, StyxObservable body) { this.version = request.version(); this.headers = request.headers().newBuilder(); this.body = request.body(); - this.cookies = new ArrayList<>(request.cookies()); } Builder(FullHttpRequest request) { @@ -440,7 +419,9 @@ public Builder(HttpRequest request, StyxObservable body) { this.version = request.version(); this.headers = request.headers().newBuilder(); this.body = StyxCoreObservable.of(copiedBuffer(request.body())); - this.cookies = new ArrayList<>(request.cookies()); + RequestCookie.encode(headers, request.cookies().stream() + .map(cookie -> RequestCookie.cookie(cookie.name(), cookie.value())) + .collect(toSet())); } /** @@ -485,7 +466,6 @@ public Builder id(Object id) { * @return {@code this} */ public Builder header(CharSequence name, Object value) { - checkNotCookie(name); this.headers.set(name, value); return this; } @@ -511,7 +491,6 @@ public Builder headers(HttpHeaders headers) { * @return {@code this} */ public Builder addHeader(CharSequence name, Object value) { - checkNotCookie(name); this.headers.add(name, value); return this; } @@ -566,41 +545,6 @@ public Builder clientAddress(InetSocketAddress clientAddress) { return this; } - /** - * Adds a response cookie (adds a new Set-Cookie header). - * - * @param cookie cookie to add - * @return {@code this} - */ - public Builder addCookie(HttpCookie cookie) { - cookies.add(checkNotNull(cookie)); - return this; - } - - /** - * Adds a response cookie (adds a new Set-Cookie header). - * - * @param name cookie name - * @param value cookie value - * @return {@code this} - */ - public Builder addCookie(String name, String value) { - return addCookie(HttpCookie.cookie(name, value)); - } - - /** - * Removes a cookie if present (removes its Set-Cookie header). - * - * @param name cookie name - * @return {@code this} - */ - public Builder removeCookie(String name) { - findCookie(cookies, name) - .ifPresent(cookies::remove); - - return this; - } - /** * Sets whether the request is be secure. * @@ -631,6 +575,15 @@ public Builder enableKeepAlive() { return header(CONNECTION, KEEP_ALIVE); } + public Builder cookies(RequestCookie... cookies) { + return cookies(asList(cookies)); + } + + private Builder cookies(List cookies) { + RequestCookie.encode(headers, cookies); + return this; + } + /** * Builds a new full request based on the settings configured in this builder. * If {@code validate} is set to true: @@ -661,10 +614,6 @@ private void ensureMethodIsValid() { checkArgument(isMethodValid(), "Unrecognised HTTP method=%s", this.method); } - private static void checkNotCookie(CharSequence name) { - checkArgument(!isCookieHeader(name.toString()), "Cookies must be set with addCookie method"); - } - private boolean isMethodValid() { return METHODS.contains(this.method); } diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java index 6f0421db66..a75b4e0706 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java @@ -114,7 +114,6 @@ public HttpHeaders headers() { return headers; } - @Override public List cookies() { return cookies; } @@ -176,6 +175,17 @@ private static byte[] decodeAndRelease(CompositeByteBuf aggregate) { } } + /** + * Return the single cookie with the specified {@code name}. + * + * @param name cookie name + * @return the cookie if present + */ + public Optional cookie(String name) { + return findCookie(cookies(), name); + } + + @Override public String toString() { return toStringHelper(this) diff --git a/components/api/src/main/java/com/hotels/styx/api/StreamingHttpMessage.java b/components/api/src/main/java/com/hotels/styx/api/StreamingHttpMessage.java index 3f0c7f63c5..ac97feee0b 100644 --- a/components/api/src/main/java/com/hotels/styx/api/StreamingHttpMessage.java +++ b/components/api/src/main/java/com/hotels/styx/api/StreamingHttpMessage.java @@ -25,7 +25,6 @@ import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_TYPE; -import static com.hotels.styx.api.support.CookiesSupport.findCookie; /** * All behaviour common to both streaming requests and streaming responses. @@ -45,13 +44,6 @@ public interface StreamingHttpMessage { */ HttpHeaders headers(); - /** - * Return all cookies in this response. - * - * @return all cookies. - */ - List cookies(); - /** * Returns the body of this message in its encoded form. * @@ -79,16 +71,6 @@ default List headers(CharSequence name) { return headers().getAll(name); } - /** - * Return the single cookie with the specified {@code name}. - * - * @param name cookie name - * @return the cookie if present - */ - default Optional cookie(String name) { - return findCookie(cookies(), name); - } - /** * Return the {@code 'Content-Length'} header value. * diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/PseudoMap.java b/components/api/src/main/java/com/hotels/styx/api/cookies/PseudoMap.java new file mode 100644 index 0000000000..39bb15b39a --- /dev/null +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/PseudoMap.java @@ -0,0 +1,165 @@ +/* + Copyright (C) 2013-2018 Expedia Inc. + + 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 com.hotels.styx.api.cookies; + +import com.google.common.collect.ImmutableSet; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Optional; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toSet; + +/** + * A set that provides methods for accessing elements like a map. Note that as it is not really a map there is no guarantee + * that a "key" will only match one element. + * + * @param "key" type + * @param element type + */ +public class PseudoMap implements Set { + private final Set set; + private final BiPredicate searcher; + + public PseudoMap(Set set, BiPredicate searcher) { + this.set = ImmutableSet.copyOf(set); + this.searcher = requireNonNull(searcher); + } + + public Set matching(K key) { + return set.stream() + .filter(element -> searcher.test(key, element)) + .collect(toSet()); + } + + public Optional firstMatch(K key) { + return set.stream() + .filter(element -> searcher.test(key, element)) + .findFirst(); + } + + @Override + public int size() { + return set.size(); + } + + @Override + public boolean isEmpty() { + return set.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return set.contains(o); + } + + @Override + public Iterator iterator() { + return set.iterator(); + } + + @Override + public Object[] toArray() { + return set.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return set.toArray(a); + } + + @Override + public boolean add(V e) { + return set.add(e); + } + + @Override + public boolean remove(Object o) { + return set.remove(o); + } + + @Override + public boolean containsAll(Collection c) { + return set.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + return set.addAll(c); + } + + @Override + public boolean retainAll(Collection c) { + return set.retainAll(c); + } + + @Override + public boolean removeAll(Collection c) { + return set.removeAll(c); + } + + @Override + public void clear() { + set.clear(); + } + + @Override + public boolean equals(Object o) { + return set.equals(o); + } + + @Override + public int hashCode() { + return set.hashCode(); + } + + @Override + public Spliterator spliterator() { + return set.spliterator(); + } + + @Override + public boolean removeIf(Predicate filter) { + return set.removeIf(filter); + } + + @Override + public Stream stream() { + return set.stream(); + } + + @Override + public Stream parallelStream() { + return set.parallelStream(); + } + + @Override + public void forEach(Consumer action) { + set.forEach(action); + } + + @Override + public String toString() { + return set.toString(); + } +} diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java index 599134594e..df086d9e70 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java @@ -65,12 +65,16 @@ public static RequestCookie cookie(String name, String value) { return new RequestCookie(name, value); } - public static Set decode(HttpHeaders headers) { - return headers.getAll(COOKIE).stream() + public static PseudoMap decode(HttpHeaders headers) { + return wrap(headers.getAll(COOKIE).stream() .map(ServerCookieDecoder.LAX::decode) .flatMap(Collection::stream) .map(RequestCookie::convert) - .collect(toSet()); + .collect(toSet())); + } + + private static PseudoMap wrap(Set cookies) { + return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); } public static void encode(HttpHeaders.Builder headers, Collection cookies) { @@ -78,7 +82,9 @@ public static void encode(HttpHeaders.Builder headers, Collection .map(RequestCookie::convert) .collect(toSet()); - headers.set(COOKIE, ClientCookieEncoder.LAX.encode(nettyCookies)); + if (!nettyCookies.isEmpty()) { + headers.set(COOKIE, ClientCookieEncoder.LAX.encode(nettyCookies)); + } } private static Cookie convert(RequestCookie cookie) { diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java index f8d56388fb..a92ddd17fe 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java @@ -16,6 +16,7 @@ package com.hotels.styx.api; import com.google.common.collect.ImmutableMap; +import com.hotels.styx.api.cookies.RequestCookie; import com.hotels.styx.api.messages.HttpMethod; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -75,8 +76,9 @@ public void convertsToStreamingHttpRequest() throws Exception { assertThat(streaming.version(), is(HTTP_1_1)); assertThat(streaming.headers(), containsInAnyOrder( header("Content-Length", "6"), - header("HeaderName", "HeaderValue"))); - assertThat(streaming.cookies(), contains(cookie("CookieName", "CookieValue"))); + header("HeaderName", "HeaderValue"), + header("Cookie", "CookieName=CookieValue"))); + assertThat(streaming.cookies(), contains(RequestCookie.cookie("CookieName", "CookieValue"))); String body = streaming.toFullRequest(0x10000) .asCompletableFuture() diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java index 5a7feda97f..ce7a661ade 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java @@ -25,16 +25,15 @@ import java.util.concurrent.ExecutionException; import java.util.stream.Stream; -import static com.hotels.styx.api.HttpCookie.cookie; import static com.hotels.styx.api.HttpHeader.header; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; -import static com.hotels.styx.api.HttpHeaderNames.COOKIE; import static com.hotels.styx.api.HttpHeaderNames.HOST; import static com.hotels.styx.api.HttpRequest.get; import static com.hotels.styx.api.HttpRequest.patch; import static com.hotels.styx.api.HttpRequest.post; import static com.hotels.styx.api.HttpRequest.put; import static com.hotels.styx.api.Url.Builder.url; +import static com.hotels.styx.api.cookies.RequestCookie.cookie; import static com.hotels.styx.api.messages.HttpMethod.DELETE; import static com.hotels.styx.api.messages.HttpMethod.GET; import static com.hotels.styx.api.messages.HttpMethod.POST; @@ -44,11 +43,9 @@ import static com.hotels.styx.support.matchers.IsOptional.isValue; import static com.hotels.styx.support.matchers.MapMatcher.isMap; import static io.netty.buffer.Unpooled.copiedBuffer; -import static java.lang.String.valueOf; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; @@ -57,7 +54,6 @@ import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -import static rx.Observable.just; public class HttpRequestTest { @Test @@ -66,7 +62,7 @@ public void decodesToFullHttpRequest() throws Exception { .secure(true) .version(HTTP_1_0) .header("HeaderName", "HeaderValue") - .addCookie("CookieName", "CookieValue") + .cookies(cookie("CookieName", "CookieValue")) .build(); FullHttpRequest full = streamingRequest.toFullRequest(0x1000) @@ -77,8 +73,10 @@ public void decodesToFullHttpRequest() throws Exception { assertThat(full.url(), is(url("/foo/bar").build())); assertThat(full.isSecure(), is(true)); assertThat(full.version(), is(HTTP_1_0)); - assertThat(full.headers(), contains(header("HeaderName", "HeaderValue"))); - assertThat(full.cookies(), contains(cookie("CookieName", "CookieValue"))); + assertThat(full.headers(), containsInAnyOrder( + header("HeaderName", "HeaderValue"), + header("Cookie", "CookieName=CookieValue"))); +// assertThat(full.cookies(), contains(HttpCookie.cookie("CookieName", "CookieValue"))); assertThat(full.body(), is(bytes("foobar"))); } @@ -131,7 +129,7 @@ public void createsARequestWithDefaultValues() throws Exception { assertThat(request.headers("any"), is(emptyIterable())); assertThat(bytesToString(request.body()), is("")); - assertThat(request.cookie("any"), isAbsent()); + assertThat(request.cookies().firstMatch("any"), isAbsent()); assertThat(request.header("any"), isAbsent()); assertThat(request.keepAlive(), is(true)); assertThat(request.method(), is(GET)); @@ -145,11 +143,11 @@ public void canUseBuilderToSetRequestProperties() { .version(HTTP_1_0) .id("id") .header("headerName", "a") - .addCookie("cfoo", "bar") + .cookies(cookie("cfoo", "bar")) .build(); - assertThat(request.toString(), is("HttpRequest{version=HTTP/1.0, method=PATCH, uri=https://hotels.com, " + - "headers=[headerName=a, Host=hotels.com], cookies=[cfoo=bar], id=id, secure=true, clientAddress=127.0.0.1:0}")); + assertThat(request.toString(), is("HttpRequest{version=HTTP/1.0, method=PATCH, uri=https://hotels.com, headers=[headerName=a, Cookie=cfoo=bar, Host=hotels.com]," + + " id=id, secure=true, clientAddress=127.0.0.1:0}")); assertThat(request.headers("headerName"), is(singletonList("a"))); } @@ -194,7 +192,7 @@ public void decodesQueryParamsContainingEncodedEquals() { @Test public void createsRequestBuilderFromRequest() { HttpRequest originalRequest = get("/home") - .addCookie(cookie("fred", "blogs")) + .cookies(cookie("fred", "blogs")) .header("some", "header") .build(); @@ -249,33 +247,36 @@ public void returnsEmptyListWhenThereIsNoSuchParameter() { @Test public void canExtractCookies() { HttpRequest request = get("/") - .addCookie("cookie1", "foo") - .addCookie("cookie3", "baz") - .addCookie("cookie2", "bar") + .cookies( + cookie("cookie1", "foo"), + cookie("cookie3", "baz"), + cookie("cookie2", "bar")) .build(); - assertThat(request.cookie("cookie1"), isValue(cookie("cookie1", "foo"))); - assertThat(request.cookie("cookie2"), isValue(cookie("cookie2", "bar"))); - assertThat(request.cookie("cookie3"), isValue(cookie("cookie3", "baz"))); + assertThat(request.cookies().firstMatch("cookie1"), isValue(cookie("cookie1", "foo"))); + assertThat(request.cookies().firstMatch("cookie2"), isValue(cookie("cookie2", "bar"))); + assertThat(request.cookies().firstMatch("cookie3"), isValue(cookie("cookie3", "baz"))); } @Test public void cannotExtractNonExistentCookie() { HttpRequest request = get("/") - .addCookie("cookie1", "foo") - .addCookie("cookie3", "baz") - .addCookie("cookie2", "bar") + .cookies( + cookie("cookie1", "foo"), + cookie("cookie3", "baz"), + cookie("cookie2", "bar")) .build(); - assertThat(request.cookie("cookie4"), isAbsent()); + assertThat(request.cookies().firstMatch("cookie4"), isAbsent()); } @Test public void extractsAllCookies() { HttpRequest request = get("/") - .addCookie("cookie1", "foo") - .addCookie("cookie3", "baz") - .addCookie("cookie2", "bar") + .cookies( + cookie("cookie1", "foo"), + cookie("cookie3", "baz"), + cookie("cookie2", "bar")) .build(); assertThat(request.cookies(), containsInAnyOrder( @@ -304,50 +305,8 @@ public void canRemoveAHeader() { assertThat(shouldRemoveHeader.headers(), contains(header("a", "b"))); } - @Test(expectedExceptions = IllegalArgumentException.class, dataProvider = "cookieHeaderName") - public void willNotAllowCookieHeaderToBeSet(CharSequence cookieHeaderName) { - get("/").header(cookieHeaderName, "Value"); - } - - @Test(expectedExceptions = IllegalArgumentException.class, dataProvider = "cookieHeaderName") - public void willNotAllowCookieHeaderToBeAdded(CharSequence cookieHeaderName) { - get("/").addHeader(cookieHeaderName, "Value"); - } - - @DataProvider(name = "cookieHeaderName") - private Object[][] cookieHeaderName() { - return new Object[][]{ - {COOKIE}, - {"Cookie"}, - {"cookie"}, - {"COOKIE"} - }; - } - - @Test - public void removesCookies() { - HttpRequest request = get("/") - .addCookie("lang", "en_US|en-us_hotels_com") - .addCookie("styx_origin_hpt", "hpt1") - .removeCookie("lang") - .build(); - assertThat(request.cookies(), contains(cookie("styx_origin_hpt", "hpt1"))); - } - - @Test - public void removesACookieSetInCookie() { - HttpRequest request = get("/") - .addCookie("lang", "en_US|en-us_hotels_com") - .addCookie("styx_origin_hpt", "hpt1") - .removeCookie("lang") - .build(); - assertThat(request.cookies(), contains(cookie("styx_origin_hpt", "hpt1"))); - } - @Test public void shouldSetsContentLengthForNonStreamingBodyMessage() { -// assertThat(put("/home").body("").build().header(CONTENT_LENGTH), isValue("0")); -// assertThat(put("/home").body("Hello").build().header(CONTENT_LENGTH), isValue(valueOf(bytes("Hello").length))); assertThat(put("/home").body(StyxObservable.of(copiedBuffer("Hello", UTF_8))).build().header(CONTENT_LENGTH), isAbsent()); } @@ -367,17 +326,17 @@ public void builderSetsRequestContent() throws Exception { @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookie() { - get("/").addCookie(null).build(); + get("/").cookies(null); } @Test(expectedExceptions = IllegalArgumentException.class) public void rejectsNullCookieName() { - get("/").addCookie(null, "value").build(); + get("/").cookies(cookie(null, "value")).build(); } @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookieValue() { - get("/").addCookie("name", null).build(); + get("/").cookies(cookie("name", null)).build(); } @Test(expectedExceptions = IllegalArgumentException.class) diff --git a/components/api/src/test/java/com/hotels/styx/api/metrics/HttpErrorStatusCauseLoggerTest.java b/components/api/src/test/java/com/hotels/styx/api/metrics/HttpErrorStatusCauseLoggerTest.java index f86d3dd887..7a8929504f 100644 --- a/components/api/src/test/java/com/hotels/styx/api/metrics/HttpErrorStatusCauseLoggerTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/metrics/HttpErrorStatusCauseLoggerTest.java @@ -70,7 +70,7 @@ public void logsThrowablesWithStatus5xxExcluding500WithoutStackTrace() { } @Test - public void logsInernalServerErrorWithRequest() throws Exception { + public void logsInternalServerErrorWithRequest() { HttpRequest request = HttpRequest.get("/foo").build(); Exception exception = new Exception("This is just a test"); @@ -79,23 +79,11 @@ public void logsInernalServerErrorWithRequest() throws Exception { assertThat(loggingTestSupport.log(), hasItem( loggingEvent( ERROR, - "Failure status=\"500 Internal Server Error\" during request=HttpRequest\\{version=HTTP/1.1, method=GET, uri=/foo, headers=\\[\\], cookies=\\[\\], id=.*, clientAddress=.*:.*\\}", + "Failure status=\"500 Internal Server Error\" during request=HttpRequest\\{version=HTTP/1.1, method=GET, uri=/foo, headers=\\[\\], id=.*, clientAddress=.*:.*\\}", "java.lang.Exception", "This is just a test"))); } - /* - Expected: a collection containing loggingEvent(level=ERROR, - - message=String matching regex: 'Failure status="500 Internal Server Error" during request=HttpRequest\{ - version=HTTP/1.1, method=GET, uri=/foo, headers=\[\], cookies=\[\], id=.*, clientAddress=.*:.*\}' exception(class=java.lang.Exception, message=String matching regex: 'This is just a test')) - - but: loggingEvent(level=ERROR, - message= 'Failure status="500 Internal Server Error" during request=HttpRequest{ - version=HTTP/1.1, method=GET, uri=/foo, headers=[], cookies=[], id=76e4d1c0-ecac-46e0-a3ac-bd9703be837c, secure=false}' exception(class=java.lang.Exception, message=This is just a test)) - - */ - @Test public void logsOtherExceptionsWithoutRequest() throws Exception { HttpRequest request = HttpRequest.get("/foo").build(); diff --git a/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java b/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java index 8d0c4bfc07..1cad2131dd 100644 --- a/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java +++ b/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java @@ -17,7 +17,6 @@ import com.google.common.collect.ImmutableList; import com.hotels.styx.api.HttpClient; -import com.hotels.styx.api.HttpCookie; import com.hotels.styx.api.HttpRequest; import com.hotels.styx.api.HttpResponse; import com.hotels.styx.api.Id; @@ -25,10 +24,11 @@ import com.hotels.styx.api.client.RemoteHost; import com.hotels.styx.api.client.loadbalancing.spi.LoadBalancer; import com.hotels.styx.api.client.retrypolicy.spi.RetryPolicy; +import com.hotels.styx.api.cookies.RequestCookie; +import com.hotels.styx.api.exceptions.NoAvailableHostsException; import com.hotels.styx.api.messages.HttpResponseStatus; import com.hotels.styx.api.metrics.MetricRegistry; import com.hotels.styx.api.metrics.codahale.CodaHaleMetricRegistry; -import com.hotels.styx.api.exceptions.NoAvailableHostsException; import com.hotels.styx.api.service.BackendService; import com.hotels.styx.api.service.RewriteRule; import com.hotels.styx.client.retry.RetryNTimes; @@ -289,12 +289,12 @@ private Optional selectOrigin(HttpRequest rewrittenRequest) { @Override public Optional preferredOrigins() { if (nonNull(originsRestrictionCookieName)) { - return rewrittenRequest.cookie(originsRestrictionCookieName) - .map(HttpCookie::value) + return rewrittenRequest.cookies().firstMatch(originsRestrictionCookieName) + .map(RequestCookie::value) .map(Optional::of) - .orElse(rewrittenRequest.cookie("styx_origin_" + id).map(HttpCookie::value)); + .orElse(rewrittenRequest.cookies().firstMatch("styx_origin_" + id).map(RequestCookie::value)); } else { - return rewrittenRequest.cookie("styx_origin_" + id).map(HttpCookie::value); + return rewrittenRequest.cookies().firstMatch("styx_origin_" + id).map(RequestCookie::value); } } diff --git a/components/client/src/main/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperation.java b/components/client/src/main/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperation.java index ea6236120e..421fb99374 100644 --- a/components/client/src/main/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperation.java +++ b/components/client/src/main/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperation.java @@ -20,9 +20,9 @@ import com.hotels.styx.api.HttpRequest; import com.hotels.styx.api.HttpResponse; import com.hotels.styx.api.client.Origin; +import com.hotels.styx.api.exceptions.TransportLostException; import com.hotels.styx.api.messages.HttpMethod; import com.hotels.styx.api.messages.HttpVersion; -import com.hotels.styx.api.exceptions.TransportLostException; import com.hotels.styx.client.Operation; import com.hotels.styx.client.OriginStatsFactory; import com.hotels.styx.common.logging.HttpRequestMessageLogger; @@ -34,8 +34,6 @@ import io.netty.handler.codec.http.DefaultHttpContent; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.cookie.ClientCookieEncoder; -import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.codec.http.cookie.DefaultCookie; import io.netty.handler.timeout.IdleStateHandler; import org.slf4j.Logger; @@ -50,7 +48,6 @@ import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkNotNull; -import static com.hotels.styx.api.HttpHeaderNames.COOKIE; import static com.hotels.styx.api.HttpHeaderNames.HOST; import static com.hotels.styx.api.StyxInternalObservables.toRxObservable; import static com.hotels.styx.api.service.BackendService.DEFAULT_RESPONSE_TIMEOUT_MILLIS; @@ -119,13 +116,13 @@ static DefaultHttpRequest toNettyRequest(HttpRequest request) { request.headers().forEach((name, value) -> nettyRequest.headers().add(name, value)); - Cookie[] cookies = request.cookies().stream() - .map(HttpRequestOperation::styxCookieToNettyCookie) - .toArray(Cookie[]::new); - - if (cookies.length > 0) { - nettyRequest.headers().set(COOKIE, ClientCookieEncoder.LAX.encode(cookies)); - } +// Cookie[] cookies = request.cookies().stream() +// .map(HttpRequestOperation::styxCookieToNettyCookie) +// .toArray(Cookie[]::new); +// +// if (cookies.length > 0) { +// nettyRequest.headers().set(COOKIE, ClientCookieEncoder.LAX.encode(cookies)); +// } return nettyRequest; } diff --git a/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala b/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala index 6b45c1a258..12e719bd9a 100644 --- a/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala +++ b/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala @@ -24,6 +24,7 @@ import com.hotels.styx.api.HttpRequest.get import com.hotels.styx.api.Id.id import com.hotels.styx.api.client.loadbalancing.spi.LoadBalancer import com.hotels.styx.api.client.{ActiveOrigins, Origin} +import com.hotels.styx.api.cookies.RequestCookie import com.hotels.styx.api.messages.HttpResponseStatus.OK import com.hotels.styx.api.service.{BackendService, StickySessionConfig} import com.hotels.styx.client.OriginsInventory.newOriginsInventoryBuilder @@ -127,7 +128,7 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers .build val request: HttpRequest = get("/") - .addCookie("styx_origin_app", "app-02") + .cookies(RequestCookie.cookie("styx_origin_app", "app-02")) .build val response1 = waitForResponse(client.sendRequest(request)) @@ -145,9 +146,10 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers .build val request: HttpRequest = get("/") - .addCookie("other_cookie1", "foo") - .addCookie("styx_origin_app", "app-02") - .addCookie("other_cookie2", "bar") + .cookies( + RequestCookie.cookie("other_cookie1", "foo"), + RequestCookie.cookie("styx_origin_app", "app-02"), + RequestCookie.cookie("other_cookie2", "bar")) .build() @@ -166,7 +168,7 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers .build val request: HttpRequest = get("/") - .addCookie("styx_origin_app", "h3") + .cookies(RequestCookie.cookie("styx_origin_app", "h3")) .build val response = waitForResponse(client.sendRequest(request)) @@ -184,7 +186,7 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers .build val request: HttpRequest = get("/") - .addCookie("styx_origin_app", "app-02") + .cookies(RequestCookie.cookie("styx_origin_app", "app-02")) .build val response = waitForResponse(client.sendRequest(request)) diff --git a/components/client/src/test/unit/java/com/hotels/styx/client/StyxHttpClientTest.java b/components/client/src/test/unit/java/com/hotels/styx/client/StyxHttpClientTest.java index 35f9521cde..f74d92b219 100644 --- a/components/client/src/test/unit/java/com/hotels/styx/client/StyxHttpClientTest.java +++ b/components/client/src/test/unit/java/com/hotels/styx/client/StyxHttpClientTest.java @@ -26,6 +26,7 @@ import com.hotels.styx.api.client.RemoteHost; import com.hotels.styx.api.client.loadbalancing.spi.LoadBalancer; import com.hotels.styx.api.client.retrypolicy.spi.RetryPolicy; +import com.hotels.styx.api.cookies.RequestCookie; import com.hotels.styx.api.metrics.MetricRegistry; import com.hotels.styx.api.metrics.codahale.CodaHaleMetricRegistry; import com.hotels.styx.api.exceptions.NoAvailableHostsException; @@ -52,6 +53,7 @@ import static com.hotels.styx.api.Id.GENERIC_APP; import static com.hotels.styx.api.client.Origin.newOriginBuilder; import static com.hotels.styx.api.client.RemoteHost.remoteHost; +import static com.hotels.styx.api.cookies.RequestCookie.cookie; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_REQUEST; import static com.hotels.styx.api.messages.HttpResponseStatus.INTERNAL_SERVER_ERROR; import static com.hotels.styx.api.messages.HttpResponseStatus.NOT_IMPLEMENTED; @@ -387,7 +389,9 @@ public void prefersStickyOrigins() { .build(); HttpResponse response = styxHttpClient.sendRequest( - get("/foo").addCookie("styx_origin_" + Id.GENERIC_APP, "Origin-Y").build()) + get("/foo") + .cookies(cookie("styx_origin_" + Id.GENERIC_APP, "Origin-Y")) + .build()) .toBlocking() .first(); @@ -412,7 +416,9 @@ public void prefersRestrictedOrigins() { .build(); HttpResponse response = styxHttpClient.sendRequest( - get("/foo").addCookie("restrictedOrigin", "Origin-Y").build()) + get("/foo") + .cookies(cookie("restrictedOrigin", "Origin-Y")) + .build()) .toBlocking() .first(); @@ -437,8 +443,10 @@ public void prefersRestrictedOriginsOverStickyOriginsWhenBothAreConfigured() { HttpResponse response = styxHttpClient.sendRequest( get("/foo") - .addCookie("restrictedOrigin", "Origin-Y") - .addCookie("styx_origin_" + Id.GENERIC_APP, "Origin-X") + .cookies( + cookie("restrictedOrigin", "Origin-Y"), + cookie("styx_origin_" + Id.GENERIC_APP, "Origin-X") + ) .build()) .toBlocking() .first(); diff --git a/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperationTest.java b/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperationTest.java index 567600fbae..a8a2f1515d 100644 --- a/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperationTest.java +++ b/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperationTest.java @@ -16,12 +16,13 @@ package com.hotels.styx.client.netty.connectionpool; import com.hotels.styx.api.HttpRequest; -import com.hotels.styx.api.messages.HttpMethod; import io.netty.handler.codec.http.DefaultHttpRequest; import org.testng.annotations.Test; +import static com.hotels.styx.api.cookies.RequestCookie.cookie; import static com.hotels.styx.api.messages.HttpMethod.GET; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.contains; import static org.hamcrest.core.Is.is; @@ -31,8 +32,10 @@ public void shouldTransformStyxRequestToNettyRequestWithAllRelevantInformation() HttpRequest request = new HttpRequest.Builder() .method(GET) .header("X-Forwarded-Proto", "https") - .addCookie("HASESSION_V3", "asdasdasd") - .addCookie("has", "123456789") + .cookies( + cookie("HASESSION_V3", "asdasdasd"), + cookie("has", "123456789") + ) .uri("https://www.example.com/foo%2Cbar?foo,baf=2") .build(); @@ -40,7 +43,10 @@ public void shouldTransformStyxRequestToNettyRequestWithAllRelevantInformation() assertThat(nettyRequest.method(), is(io.netty.handler.codec.http.HttpMethod.GET)); assertThat(nettyRequest.uri(), is("https://www.example.com/foo%2Cbar?foo%2Cbaf=2")); assertThat(nettyRequest.headers().get("X-Forwarded-Proto"), is("https")); - assertThat(nettyRequest.headers().getAll("Cookie"), contains("HASESSION_V3=asdasdasd; has=123456789")); + assertThat(nettyRequest.headers().getAll("Cookie"), anyOf( + contains("HASESSION_V3=asdasdasd; has=123456789"), + contains("has=123456789; HASESSION_V3=asdasdasd") + )); } @Test diff --git a/components/proxy/src/test/java/com/hotels/styx/proxy/StyxBackendServiceClientFactoryTest.java b/components/proxy/src/test/java/com/hotels/styx/proxy/StyxBackendServiceClientFactoryTest.java index b59f044d91..a307e91447 100644 --- a/components/proxy/src/test/java/com/hotels/styx/proxy/StyxBackendServiceClientFactoryTest.java +++ b/components/proxy/src/test/java/com/hotels/styx/proxy/StyxBackendServiceClientFactoryTest.java @@ -18,6 +18,7 @@ import com.hotels.styx.Environment; import com.hotels.styx.StyxConfig; import com.hotels.styx.api.HttpClient; +import com.hotels.styx.api.HttpRequest; import com.hotels.styx.api.HttpResponse; import com.hotels.styx.api.client.Connection; import com.hotels.styx.api.client.Origin; @@ -37,6 +38,7 @@ import static com.hotels.styx.api.Id.GENERIC_APP; import static com.hotels.styx.api.Id.id; import static com.hotels.styx.api.client.Origin.newOriginBuilder; +import static com.hotels.styx.api.cookies.RequestCookie.cookie; import static com.hotels.styx.api.messages.HttpResponseStatus.OK; import static com.hotels.styx.api.service.BackendService.newBackendServiceBuilder; import static com.hotels.styx.api.service.StickySessionConfig.newStickySessionConfigBuilder; @@ -49,7 +51,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static rx.Observable.just; -import com.hotels.styx.api.HttpRequest; public class StyxBackendServiceClientFactoryTest { private Environment environment; @@ -121,9 +122,9 @@ public void usesTheOriginSpecifiedInTheStickySessionCookie() { .build(), new OriginStatsFactory(new CodaHaleMetricRegistry())); - HttpRequest requestz = get("/some-req").addCookie(STICKY_COOKIE, id("z").toString()).build(); - HttpRequest requestx = get("/some-req").addCookie(STICKY_COOKIE, id("x").toString()).build(); - HttpRequest requesty = get("/some-req").addCookie(STICKY_COOKIE, id("y").toString()).build(); + HttpRequest requestz = get("/some-req").cookies(cookie(STICKY_COOKIE, id("z").toString())).build(); + HttpRequest requestx = get("/some-req").cookies(cookie(STICKY_COOKIE, id("x").toString())).build(); + HttpRequest requesty = get("/some-req").cookies(cookie(STICKY_COOKIE, id("y").toString())).build(); HttpResponse responsez = styxHttpClient.sendRequest(requestz).toBlocking().first(); HttpResponse responsex = styxHttpClient.sendRequest(requestx).toBlocking().first(); @@ -166,9 +167,9 @@ public void usesTheOriginSpecifiedInTheOriginsRestrictionCookie() { .build(), new OriginStatsFactory(new CodaHaleMetricRegistry())); - HttpRequest requestz = get("/some-req").addCookie(ORIGINS_RESTRICTION_COOKIE, id("z").toString()).build(); - HttpRequest requestx = get("/some-req").addCookie(ORIGINS_RESTRICTION_COOKIE, id("x").toString()).build(); - HttpRequest requesty = get("/some-req").addCookie(ORIGINS_RESTRICTION_COOKIE, id("y").toString()).build(); + HttpRequest requestz = get("/some-req").cookies(cookie(ORIGINS_RESTRICTION_COOKIE, id("z").toString())).build(); + HttpRequest requestx = get("/some-req").cookies(cookie(ORIGINS_RESTRICTION_COOKIE, id("x").toString())).build(); + HttpRequest requesty = get("/some-req").cookies(cookie(ORIGINS_RESTRICTION_COOKIE, id("y").toString())).build(); HttpResponse responsez = styxHttpClient.sendRequest(requestz).toBlocking().first(); HttpResponse responsex = styxHttpClient.sendRequest(requestx).toBlocking().first(); diff --git a/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java b/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java index 8c849f516e..92797fd2d0 100644 --- a/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java +++ b/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java @@ -28,6 +28,7 @@ import static ch.qos.logback.classic.Level.INFO; import static com.hotels.styx.api.HttpRequest.get; import static com.hotels.styx.api.HttpResponse.response; +import static com.hotels.styx.api.cookies.RequestCookie.cookie; import static com.hotels.styx.api.messages.HttpResponseStatus.OK; import static com.hotels.styx.common.StyxFutures.await; import static com.hotels.styx.support.matchers.LoggingEventMatcher.loggingEvent; @@ -53,7 +54,7 @@ public void after() { public void logsRequestsAndResponses() { HttpRequest request = get("/") .header("ReqHeader", "ReqHeaderValue") - .addCookie("ReqCookie", "ReqCookieValue") + .cookies(cookie("ReqCookie", "ReqCookieValue")) .build(); consume(interceptor.intercept(request, respondWith( @@ -62,7 +63,7 @@ public void logsRequestsAndResponses() { .addCookie("RespCookie", "RespCookieValue") ))); - String requestPattern = "request=\\{method=GET, secure=false, uri=/, origin=\"N/A\", headers=\\[ReqHeader=ReqHeaderValue\\], cookies=\\[ReqCookie=ReqCookieValue\\]\\}"; + String requestPattern = "request=\\{method=GET, secure=false, uri=/, origin=\"N/A\", headers=\\[ReqHeader=ReqHeaderValue, Cookie=ReqCookie=ReqCookieValue\\], cookies=\\[ReqCookie=ReqCookieValue\\]\\}"; String responsePattern = "response=\\{status=200 OK, headers=\\[RespHeader=RespHeaderValue\\], cookies=\\[RespCookie=RespCookieValue\\]\\}"; assertThat(responseLogSupport.log(), contains( @@ -75,7 +76,7 @@ public void logsRequestsAndResponsesShort() { interceptor = new HttpMessageLoggingInterceptor(false); HttpRequest request = get("/") .header("ReqHeader", "ReqHeaderValue") - .addCookie("ReqCookie", "ReqCookieValue") + .cookies(cookie("ReqCookie", "ReqCookieValue")) .build(); consume(interceptor.intercept(request, respondWith( @@ -97,12 +98,12 @@ public void logsSecureRequests() { HttpRequest request = get("/") .secure(true) .header("ReqHeader", "ReqHeaderValue") - .addCookie("ReqCookie", "ReqCookieValue") + .cookies(cookie("ReqCookie", "ReqCookieValue")) .build(); consume(interceptor.intercept(request, respondWith(response(OK)))); - String requestPattern = "request=\\{method=GET, secure=true, uri=/, origin=\"N/A\", headers=\\[ReqHeader=ReqHeaderValue\\], cookies=\\[ReqCookie=ReqCookieValue\\]\\}"; + String requestPattern = "request=\\{method=GET, secure=true, uri=/, origin=\"N/A\", headers=\\[ReqHeader=ReqHeaderValue, Cookie=ReqCookie=ReqCookieValue\\], cookies=\\[ReqCookie=ReqCookieValue\\]\\}"; String responsePattern = "response=\\{status=200 OK, headers=\\[\\], cookies=\\[\\]\\}"; assertThat(responseLogSupport.log(), contains( diff --git a/components/server/src/main/java/com/hotels/styx/server/netty/codec/NettyToStyxRequestDecoder.java b/components/server/src/main/java/com/hotels/styx/server/netty/codec/NettyToStyxRequestDecoder.java index f5d4d74f15..74bed44155 100644 --- a/components/server/src/main/java/com/hotels/styx/server/netty/codec/NettyToStyxRequestDecoder.java +++ b/components/server/src/main/java/com/hotels/styx/server/netty/codec/NettyToStyxRequestDecoder.java @@ -16,7 +16,6 @@ package com.hotels.styx.server.netty.codec; import com.google.common.annotations.VisibleForTesting; -import com.hotels.styx.api.HttpCookie; import com.hotels.styx.api.Url; import com.hotels.styx.api.messages.HttpVersion; import com.hotels.styx.server.BadRequestException; @@ -43,7 +42,6 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayDeque; -import java.util.Collection; import java.util.List; import java.util.Queue; import java.util.Set; @@ -51,13 +49,10 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Iterables.size; -import static com.hotels.styx.api.HttpHeaderNames.COOKIE; import static com.hotels.styx.api.HttpHeaderNames.EXPECT; import static com.hotels.styx.api.HttpHeaderNames.HOST; import static com.hotels.styx.api.StyxInternalObservables.fromRxObservable; import static com.hotels.styx.api.Url.Builder.url; -import static com.hotels.styx.api.common.Strings.quote; -import static com.hotels.styx.api.support.CookiesSupport.isCookieHeader; import static com.hotels.styx.server.UniqueIdSuppliers.UUID_VERSION_ONE_SUPPLIER; import static com.hotels.styx.server.netty.codec.UnwiseCharsEncoder.IGNORE; import static java.util.stream.StreamSupport.stream; @@ -184,18 +179,8 @@ com.hotels.styx.api.HttpRequest.Builder makeAStyxRequestFrom(HttpRequest request .body(fromRxObservable(content)); stream(request.headers().spliterator(), false) - .filter(entry -> !isCookieHeader(entry.getKey())) .forEach(entry -> requestBuilder.addHeader(entry.getKey(), entry.getValue())); - request.headers().getAll(COOKIE).stream() - .map(header -> decodeCookieHeader(header, request)) - .flatMap(Collection::stream) - .map(nettyCookie -> { - String cookieValue = nettyCookie.wrap() ? quote(nettyCookie.value()) : nettyCookie.value(); - return HttpCookie.cookie(nettyCookie.name(), cookieValue); - }) - .forEach(requestBuilder::addCookie); - return requestBuilder; } diff --git a/components/server/src/main/java/com/hotels/styx/server/routing/AntlrMatcher.java b/components/server/src/main/java/com/hotels/styx/server/routing/AntlrMatcher.java index 0553298d45..b03a6a479d 100644 --- a/components/server/src/main/java/com/hotels/styx/server/routing/AntlrMatcher.java +++ b/components/server/src/main/java/com/hotels/styx/server/routing/AntlrMatcher.java @@ -15,8 +15,8 @@ */ package com.hotels.styx.server.routing; -import com.hotels.styx.api.HttpCookie; import com.hotels.styx.api.HttpRequest; +import com.hotels.styx.api.cookies.RequestCookie; import com.hotels.styx.server.routing.antlr.AntlrConditionParser; import static com.hotels.styx.api.HttpHeaderNames.USER_AGENT; @@ -31,7 +31,7 @@ public final class AntlrMatcher implements Matcher { .registerFunction("userAgent", request -> request.header(USER_AGENT).orElse("")) .registerFunction("protocol", request -> request.isSecure() ? "https" : "http") .registerFunction("header", (request, input) -> request.header(input).orElse("")) - .registerFunction("cookie", (request, input) -> request.cookie(input).map(HttpCookie::value).orElse("")) + .registerFunction("cookie", (request, input) -> request.cookies().firstMatch(input).map(RequestCookie::value).orElse("")) .build(); private final Condition condition; diff --git a/components/server/src/test/java/com/hotels/styx/server/netty/codec/NettyToStyxRequestDecoderTest.java b/components/server/src/test/java/com/hotels/styx/server/netty/codec/NettyToStyxRequestDecoderTest.java index 65a11abefb..6ce84a7f57 100644 --- a/components/server/src/test/java/com/hotels/styx/server/netty/codec/NettyToStyxRequestDecoderTest.java +++ b/components/server/src/test/java/com/hotels/styx/server/netty/codec/NettyToStyxRequestDecoderTest.java @@ -17,9 +17,9 @@ import com.google.common.base.Strings; import com.hotels.styx.api.HttpHeader; -import com.hotels.styx.server.UniqueIdSupplier; import com.hotels.styx.api.StyxObservable; import com.hotels.styx.server.BadRequestException; +import com.hotels.styx.server.UniqueIdSupplier; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; @@ -49,9 +49,9 @@ import static com.google.common.base.Charsets.UTF_8; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Sets.newHashSet; -import static com.hotels.styx.api.HttpCookie.cookie; -import static com.hotels.styx.server.UniqueIdSuppliers.fixedUniqueIdSupplier; import static com.hotels.styx.api.StyxInternalObservables.toRxObservable; +import static com.hotels.styx.api.cookies.RequestCookie.cookie; +import static com.hotels.styx.server.UniqueIdSuppliers.fixedUniqueIdSupplier; import static com.hotels.styx.support.netty.HttpMessageSupport.httpMessageToBytes; import static com.hotels.styx.support.netty.HttpMessageSupport.httpRequest; import static com.hotels.styx.support.netty.HttpMessageSupport.httpRequestAsBuf; @@ -263,9 +263,11 @@ public void canHandleNettyCookies() { com.hotels.styx.api.HttpRequest expected = new com.hotels.styx.api.HttpRequest.Builder( com.hotels.styx.api.messages.HttpMethod.GET, "http://foo.com/") - .addCookie(cookie("ABC01", "\"1\"")) - .addCookie(cookie("ABC02", "1")) - .addCookie(cookie("guid", "xxxxx-xxx-xxx-xxx-xxxxxxx")) + .cookies( + cookie("ABC01", "\"1\""), + cookie("ABC02", "1"), + cookie("guid", "xxxxx-xxx-xxx-xxx-xxxxxxx") + ) .build(); assertThat(newHashSet(styxRequest.cookies()), is(newHashSet(expected.cookies()))); } @@ -286,9 +288,11 @@ public void acceptsMalformedCookiesWithRelaxedValidation() { com.hotels.styx.api.HttpRequest expected = new com.hotels.styx.api.HttpRequest.Builder( com.hotels.styx.api.messages.HttpMethod.GET, "http://foo.com/") - .addCookie(cookie("ABC01", "\"1\"")) - .addCookie(cookie("ABC02", "1")) - .addCookie(cookie("guid", "a,b")) + .cookies( + cookie("ABC01", "\"1\""), + cookie("ABC02", "1"), + cookie("guid", "a,b") + ) .build(); assertThat(newHashSet(styxRequest.cookies()), is(newHashSet(expected.cookies()))); } diff --git a/components/server/src/test/java/com/hotels/styx/server/routing/antlr/AntlrConditionTest.java b/components/server/src/test/java/com/hotels/styx/server/routing/antlr/AntlrConditionTest.java index c52f9b26e8..3c6b54011e 100644 --- a/components/server/src/test/java/com/hotels/styx/server/routing/antlr/AntlrConditionTest.java +++ b/components/server/src/test/java/com/hotels/styx/server/routing/antlr/AntlrConditionTest.java @@ -15,25 +15,20 @@ */ package com.hotels.styx.server.routing.antlr; -import com.hotels.styx.api.HttpCookie; -import com.hotels.styx.api.HttpCookieAttribute; -import com.hotels.styx.server.routing.Condition; +import com.hotels.styx.api.HttpRequest; +import com.hotels.styx.api.cookies.RequestCookie; import com.hotels.styx.api.messages.HttpMethod; +import com.hotels.styx.server.routing.Condition; import org.testng.annotations.Test; -import static com.hotels.styx.api.HttpCookie.cookie; -import static com.hotels.styx.api.HttpCookieAttribute.domain; -import static com.hotels.styx.api.HttpCookieAttribute.httpOnly; -import static com.hotels.styx.api.HttpCookieAttribute.maxAge; -import static com.hotels.styx.api.HttpCookieAttribute.path; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; import static com.hotels.styx.api.HttpHeaderNames.HOST; import static com.hotels.styx.api.HttpHeaderNames.USER_AGENT; import static com.hotels.styx.api.HttpRequest.get; import static com.hotels.styx.api.HttpRequest.post; +import static com.hotels.styx.api.cookies.RequestCookie.cookie; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; -import com.hotels.styx.api.HttpRequest; public class AntlrConditionTest { final Condition.Parser parser = new AntlrConditionParser.Builder() @@ -41,9 +36,8 @@ public class AntlrConditionTest { .registerFunction("path", HttpRequest::path) .registerFunction("userAgent", request -> request.header(USER_AGENT).orElse("")) .registerFunction("header", (request, input) -> request.header(input).orElse("")) - .registerFunction("cookie", (request, input) -> { - return request.cookie(input).map(HttpCookie::value).orElse(""); - }) + .registerFunction("cookie", (request, input) -> + request.cookies().firstMatch(input).map(RequestCookie::value).orElse("")) .build(); private static HttpRequest.Builder newRequest(String uri) { @@ -399,21 +393,13 @@ public void notExpressionHasHigherPrecedenceThanAndExpression() { public void cookieValueIsPresent() { Condition condition = condition("cookie('TheCookie')"); HttpRequest request = newRequest() - .addCookie(cookie("TheCookie", "foobar-foobar-baz", - domain("localhost"), - httpOnly(), - path("/path"), - maxAge(365))) + .cookies(cookie("TheCookie", "foobar-foobar-baz")) .header("App-Name", "app3") .build(); assertThat(condition.evaluate(request), is(true)); request = newRequest() - .addCookie(cookie("AnotherCookie", "foobar-foobar-baz", - domain("localhost"), - httpOnly(), - path("/path"), - maxAge(365))) + .cookies(cookie("AnotherCookie", "foobar-foobar-baz")) .header("App-Name", "app3") .build(); assertThat(condition.evaluate(request), is(false)); @@ -429,21 +415,13 @@ public void cookieValueIsPresent() { public void cookieValueMatchesWithString() { Condition condition = condition("cookie('TheCookie') == 'foobar-foobar-baz'"); HttpRequest request = newRequest() - .addCookie(cookie("TheCookie", "foobar-foobar-baz", - HttpCookieAttribute.domain("localhost"), - HttpCookieAttribute.httpOnly(), - HttpCookieAttribute.path("/path"), - HttpCookieAttribute.maxAge(365))) + .cookies(cookie("TheCookie", "foobar-foobar-baz")) .header("App-Name", "app3") .build(); assertThat(condition.evaluate(request), is(true)); request = newRequest() - .addCookie(cookie("AnotherCookie", "foobar-baz", - HttpCookieAttribute.domain("localhost"), - HttpCookieAttribute.httpOnly(), - HttpCookieAttribute.path("/path"), - HttpCookieAttribute.maxAge(365))) + .cookies(cookie("AnotherCookie", "foobar-baz")) .header("App-Name", "app3") .build(); assertThat(condition.evaluate(request), is(false)); @@ -459,13 +437,13 @@ public void cookieValueMatchesWithRegexp() { Condition condition = condition("cookie('TheCookie') =~ 'foobar-.*-baz'"); HttpRequest request = newRequest() - .addCookie(cookie("TheCookie", "foobar-foobar-baz")) + .cookies(cookie("TheCookie", "foobar-foobar-baz")) .header("App-Name", "app3") .build(); assertThat(condition.evaluate(request), is(true)); request = newRequest() - .addCookie(cookie("AnotherCookie", "foobar-x-baz")) + .cookies(cookie("AnotherCookie", "foobar-x-baz")) .header("App-Name", "app3") .build(); assertThat(condition.evaluate(request), is(false)); diff --git a/components/server/src/test/java/com/hotels/styx/server/routing/antlr/FunctionResolverTest.java b/components/server/src/test/java/com/hotels/styx/server/routing/antlr/FunctionResolverTest.java index 181d775f11..9c3d2689f2 100644 --- a/components/server/src/test/java/com/hotels/styx/server/routing/antlr/FunctionResolverTest.java +++ b/components/server/src/test/java/com/hotels/styx/server/routing/antlr/FunctionResolverTest.java @@ -16,17 +16,18 @@ package com.hotels.styx.server.routing.antlr; import com.google.common.collect.ImmutableMap; -import com.hotels.styx.api.HttpCookie; +import com.hotels.styx.api.HttpRequest; +import com.hotels.styx.api.cookies.RequestCookie; import org.testng.annotations.Test; import java.util.Map; import static com.hotels.styx.api.HttpRequest.get; +import static com.hotels.styx.api.cookies.RequestCookie.cookie; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import com.hotels.styx.api.HttpRequest; public class FunctionResolverTest { Map zeroArgumentFunctions = ImmutableMap.of( @@ -35,7 +36,7 @@ public class FunctionResolverTest { Map oneArgumentFunctions = ImmutableMap.of( "header", (request, name) -> request.header(name).orElse(""), - "cookie", (request, name) -> request.cookie(name).map(HttpCookie::value).orElse("")); + "cookie", (request, name) -> request.cookies().firstMatch(name).map(RequestCookie::value).orElse("")); FunctionResolver functionResolver = new FunctionResolver(zeroArgumentFunctions, oneArgumentFunctions); @@ -59,7 +60,7 @@ public void throwsExceptionIfZeroArgumentFunctionDoesNotExist() { public void resolvesOneArgumentFunctions() { HttpRequest request = get("/foo") .header("Host", "www.hotels.com") - .addCookie("lang", "en_US|en-us_hotels_com") + .cookies(cookie("lang", "en_US|en-us_hotels_com")) .build(); assertThat(functionResolver.resolveFunction("header", singletonList("Host")).call(request), is("www.hotels.com")); @@ -71,7 +72,7 @@ public void resolvesOneArgumentFunctions() { public void throwsExceptionIfOneArgumentFunctionDoesNotExist() { HttpRequest request = get("/foo") .header("Host", "www.hotels.com") - .addCookie("lang", "en_US|en-us_hotels_com") + .cookies(cookie("lang", "en_US|en-us_hotels_com")) .build(); functionResolver.resolveFunction("foobar", singletonList("barfoo")).call(request); From 6efa49bd38f08fc1cae09eae9edfafa23ce1b8ac Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Mon, 16 Jul 2018 17:44:30 +0100 Subject: [PATCH 04/26] Use new API in FullHttpRequest --- .../com/hotels/styx/api/FullHttpMessage.java | 18 ----- .../com/hotels/styx/api/FullHttpRequest.java | 59 +++------------ .../com/hotels/styx/api/FullHttpResponse.java | 5 +- .../hotels/styx/api/FullHttpRequestTest.java | 72 +++++++------------ .../com/hotels/styx/proxy/HeadersSpec.scala | 4 +- .../styx/proxy/OriginRestrictionSpec.scala | 10 +-- 6 files changed, 50 insertions(+), 118 deletions(-) diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpMessage.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpMessage.java index 339fc3a410..ad527a7bc1 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpMessage.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpMessage.java @@ -23,7 +23,6 @@ import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_TYPE; -import static com.hotels.styx.api.support.CookiesSupport.findCookie; /** * All behaviour common to both full requests and full responses. @@ -43,13 +42,6 @@ public interface FullHttpMessage { */ HttpHeaders headers(); - /** - * Return all cookies in this response. - * - * @return all cookies. - */ - List cookies(); - /** * Returns the body of this message in its unencoded form. * @@ -89,16 +81,6 @@ default List headers(CharSequence name) { return headers().getAll(name); } - /** - * Return the single cookie with the specified {@code name}. - * - * @param name cookie name - * @return the cookie if present - */ - default Optional cookie(String name) { - return findCookie(cookies(), name); - } - /** * Return the {@code 'Content-Length'} header value. * diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java index 3628334873..368ed64eb2 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java @@ -15,7 +15,8 @@ */ package com.hotels.styx.api; -import com.google.common.collect.ImmutableList; +import com.hotels.styx.api.cookies.PseudoMap; +import com.hotels.styx.api.cookies.RequestCookie; import com.hotels.styx.api.messages.HttpMethod; import com.hotels.styx.api.messages.HttpVersion; import io.netty.buffer.Unpooled; @@ -23,14 +24,12 @@ import java.net.InetSocketAddress; import java.nio.charset.Charset; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; import static com.hotels.styx.api.HttpHeaderNames.CONNECTION; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; import static com.hotels.styx.api.HttpHeaderNames.HOST; @@ -43,10 +42,10 @@ import static com.hotels.styx.api.messages.HttpMethod.POST; import static com.hotels.styx.api.messages.HttpMethod.PUT; import static com.hotels.styx.api.messages.HttpVersion.HTTP_1_1; -import static com.hotels.styx.api.support.CookiesSupport.findCookie; import static com.hotels.styx.api.support.CookiesSupport.isCookieHeader; import static java.lang.Integer.parseInt; import static java.net.InetSocketAddress.createUnresolved; +import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; import static java.util.UUID.randomUUID; @@ -63,7 +62,6 @@ public class FullHttpRequest implements FullHttpMessage { private final HttpHeaders headers; private final boolean secure; private final byte[] body; - private final List cookies; FullHttpRequest(Builder builder) { this.id = builder.id == null ? randomUUID() : builder.id; @@ -74,7 +72,6 @@ public class FullHttpRequest implements FullHttpMessage { this.secure = builder.secure; this.headers = builder.headers.build(); this.body = requireNonNull(builder.body); - this.cookies = ImmutableList.copyOf(builder.cookies); } /** @@ -147,11 +144,6 @@ public HttpHeaders headers() { return headers; } - @Override - public List cookies() { - return cookies; - } - @Override public List headers(CharSequence name) { return headers.getAll(name); @@ -320,6 +312,10 @@ public HttpRequest toStreamingRequest() { } } + public PseudoMap cookies() { + return RequestCookie.decode(headers); + } + @Override public String toString() { return toStringHelper(this) @@ -327,7 +323,6 @@ public String toString() { .add("method", method) .add("uri", url) .add("headers", headers) - .add("cookies", cookies) .add("id", id) .add("secure", secure) .toString(); @@ -348,13 +343,11 @@ public static final class Builder { private HttpHeaders.Builder headers; private HttpVersion version = HTTP_1_1; private byte[] body; - private final List cookies; public Builder() { this.url = Url.Builder.url("/").build(); this.headers = new HttpHeaders.Builder(); this.body = new byte[0]; - this.cookies = new ArrayList<>(); } public Builder(HttpMethod method, String uri) { @@ -373,8 +366,6 @@ public Builder(HttpRequest request, byte[] body) { this.version = request.version(); this.headers = request.headers().newBuilder(); this.body = body; -// this.cookies = new ArrayList<>(request.cookies()); - this.cookies = new ArrayList<>(); } Builder(FullHttpRequest request) { @@ -386,7 +377,6 @@ public Builder(HttpRequest request, byte[] body) { this.version = request.version(); this.headers = request.headers().newBuilder(); this.body = request.body(); - this.cookies = new ArrayList<>(request.cookies()); } /** @@ -549,41 +539,14 @@ public Builder method(HttpMethod method) { return this; } - /** - * Adds a response cookie (adds a new Set-Cookie header). - * - * @param cookie cookie to add - * @return {@code this} - */ - public Builder addCookie(HttpCookie cookie) { - cookies.add(checkNotNull(cookie)); - return this; - } - - /** - * Adds a response cookie (adds a new Set-Cookie header). - * - * @param name cookie name - * @param value cookie value - * @return {@code this} - */ - public Builder addCookie(String name, String value) { - return addCookie(HttpCookie.cookie(name, value)); + public Builder cookies(RequestCookie... cookies) { + return cookies(asList(cookies)); } - /** - * Removes a cookie if present (removes its Set-Cookie header). - * - * @param name name of the cookie - * @return {@code this} - */ - public Builder removeCookie(String name) { - findCookie(cookies, name) - .ifPresent(cookies::remove); - + private Builder cookies(List cookies) { + RequestCookie.encode(headers, cookies); return this; } - /** * Sets whether the request is be secure. * diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java index baae91fe7a..6aeb53ff96 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java @@ -92,11 +92,14 @@ public HttpHeaders headers() { return headers; } - @Override public List cookies() { return cookies; } + public Optional cookie(String name) { + return findCookie(cookies(), name); + } + /** * Returns the body of this message in its unencoded form. * diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java index a92ddd17fe..bec9d3acf3 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java @@ -16,26 +16,23 @@ package com.hotels.styx.api; import com.google.common.collect.ImmutableMap; -import com.hotels.styx.api.cookies.RequestCookie; import com.hotels.styx.api.messages.HttpMethod; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import rx.observers.TestSubscriber; import java.util.Optional; -import java.util.concurrent.ExecutionException; import static com.hotels.styx.api.FullHttpRequest.get; import static com.hotels.styx.api.FullHttpRequest.patch; import static com.hotels.styx.api.FullHttpRequest.put; -import static com.hotels.styx.api.HttpCookie.cookie; import static com.hotels.styx.api.HttpHeader.header; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; import static com.hotels.styx.api.HttpHeaderNames.COOKIE; import static com.hotels.styx.api.HttpHeaderNames.HOST; import static com.hotels.styx.api.Url.Builder.url; +import static com.hotels.styx.api.cookies.RequestCookie.cookie; import static com.hotels.styx.api.messages.HttpMethod.DELETE; import static com.hotels.styx.api.messages.HttpMethod.GET; import static com.hotels.styx.api.messages.HttpMethod.POST; @@ -65,7 +62,7 @@ public void convertsToStreamingHttpRequest() throws Exception { .secure(true) .version(HTTP_1_1) .header("HeaderName", "HeaderValue") - .addCookie("CookieName", "CookieValue") + .cookies(cookie("CookieName", "CookieValue")) .build(); HttpRequest streaming = fullRequest.toStreamingRequest(); @@ -78,7 +75,7 @@ public void convertsToStreamingHttpRequest() throws Exception { header("Content-Length", "6"), header("HeaderName", "HeaderValue"), header("Cookie", "CookieName=CookieValue"))); - assertThat(streaming.cookies(), contains(RequestCookie.cookie("CookieName", "CookieValue"))); + assertThat(streaming.cookies(), contains(cookie("CookieName", "CookieValue"))); String body = streaming.toFullRequest(0x10000) .asCompletableFuture() @@ -128,7 +125,7 @@ public void createsARequestWithDefaultValues() { assertThat(request.headers("any"), is(emptyIterable())); assertThat(request.body().length, is(0)); - assertThat(request.cookie("any"), isAbsent()); + assertThat(request.cookies().firstMatch("any"), isAbsent()); assertThat(request.header("any"), isAbsent()); assertThat(request.keepAlive(), is(true)); assertThat(request.method(), is(GET)); @@ -142,11 +139,11 @@ public void canUseBuilderToSetRequestProperties() { .version(HTTP_1_1) .id("id") .header("headerName", "a") - .addCookie("cfoo", "bar") + .cookies(cookie("cfoo", "bar")) .build(); assertThat(request.toString(), is("FullHttpRequest{version=HTTP/1.1, method=PATCH, uri=https://hotels.com, " + - "headers=[headerName=a, Host=hotels.com], cookies=[cfoo=bar], id=id, secure=true}")); + "headers=[headerName=a, Cookie=cfoo=bar, Host=hotels.com], id=id, secure=true}")); assertThat(request.headers("headerName"), is(singletonList("a"))); } @@ -287,7 +284,7 @@ public void decodesQueryParamsContainingEncodedEquals() { @Test public void createsRequestBuilderFromRequest() { FullHttpRequest originalRequest = get("/home") - .addCookie(cookie("fred", "blogs")) + .cookies(cookie("fred", "blogs")) .header("some", "header") .build(); @@ -342,33 +339,36 @@ public void returnsEmptyListWhenThereIsNoSuchParameter() { @Test public void canExtractCookies() { FullHttpRequest request = get("/") - .addCookie("cookie1", "foo") - .addCookie("cookie3", "baz") - .addCookie("cookie2", "bar") + .cookies( + cookie("cookie1", "foo"), + cookie("cookie3", "baz"), + cookie("cookie2", "bar")) .build(); - assertThat(request.cookie("cookie1"), isValue(cookie("cookie1", "foo"))); - assertThat(request.cookie("cookie2"), isValue(cookie("cookie2", "bar"))); - assertThat(request.cookie("cookie3"), isValue(cookie("cookie3", "baz"))); + assertThat(request.cookies().firstMatch("cookie1"), isValue(cookie("cookie1", "foo"))); + assertThat(request.cookies().firstMatch("cookie2"), isValue(cookie("cookie2", "bar"))); + assertThat(request.cookies().firstMatch("cookie3"), isValue(cookie("cookie3", "baz"))); } @Test public void cannotExtractNonExistentCookie() { FullHttpRequest request = get("/") - .addCookie("cookie1", "foo") - .addCookie("cookie3", "baz") - .addCookie("cookie2", "bar") + .cookies( + cookie("cookie1", "foo"), + cookie("cookie3", "baz"), + cookie("cookie2", "bar")) .build(); - assertThat(request.cookie("cookie4"), isAbsent()); + assertThat(request.cookies().firstMatch("cookie4"), isAbsent()); } @Test public void extractsAllCookies() { FullHttpRequest request = get("/") - .addCookie("cookie1", "foo") - .addCookie("cookie3", "baz") - .addCookie("cookie2", "bar") + .cookies( + cookie("cookie1", "foo"), + cookie("cookie3", "baz"), + cookie("cookie2", "bar")) .build(); assertThat(request.cookies(), containsInAnyOrder( @@ -417,26 +417,6 @@ private Object[][] cookieHeaderName() { }; } - @Test - public void removesCookies() { - FullHttpRequest request = get("/") - .addCookie("lang", "en_US|en-us_hotels_com") - .addCookie("styx_origin_hpt", "hpt1") - .removeCookie("lang") - .build(); - assertThat(request.cookies(), contains(cookie("styx_origin_hpt", "hpt1"))); - } - - @Test - public void removesACookieSetInCookie() { - FullHttpRequest request = get("/") - .addCookie("lang", "en_US|en-us_hotels_com") - .addCookie("styx_origin_hpt", "hpt1") - .removeCookie("lang") - .build(); - assertThat(request.cookies(), contains(cookie("styx_origin_hpt", "hpt1"))); - } - @Test public void shouldSetsContentLengthForNonStreamingBodyMessage() { assertThat(put("/home").body("", UTF_8).build().header(CONTENT_LENGTH), isValue("0")); @@ -456,17 +436,17 @@ private static byte[] bytes(String content) { @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookie() { - get("/").addCookie(null).build(); + get("/").cookies(null); } @Test(expectedExceptions = IllegalArgumentException.class) public void rejectsNullCookieName() { - get("/").addCookie(null, "value").build(); + get("/").cookies(cookie(null, "value")).build(); } @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookieValue() { - get("/").addCookie("name", null).build(); + get("/").cookies(cookie("name", null)).build(); } @Test(expectedExceptions = IllegalArgumentException.class) diff --git a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala index bfdd78bad4..ee476d3f15 100644 --- a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala +++ b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala @@ -21,6 +21,8 @@ import com.github.tomakehurst.wiremock.client.WireMock._ import com.hotels.styx.api.FullHttpRequest.get import com.hotels.styx.api.HttpCookieAttribute.{domain, httpOnly, path} import com.hotels.styx.api.HttpHeaderNames.X_FORWARDED_FOR +import com.hotels.styx.api.cookies.RequestCookie +import com.hotels.styx.api.cookies.RequestCookie.cookie import com.hotels.styx.api.messages.HttpResponseStatus._ import com.hotels.styx.api.{HttpCookie, HttpHeaderValues} import com.hotels.styx.support.NettyOrigins @@ -387,7 +389,7 @@ class HeadersSpec extends FunSpec val req = get("/quotedCookies") .addHeader(HOST, styxServer.proxyHost) - .addCookie("test-cookie", "\"hu_hotels_com,HCOM_HU,hu_HU,\"") + .cookies(cookie("test-cookie", "\"hu_hotels_com,HCOM_HU,hu_HU,\"")) .build() val resp = decodedRequest(req) diff --git a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/OriginRestrictionSpec.scala b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/OriginRestrictionSpec.scala index 04cb8e4084..8f7867b3c7 100644 --- a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/OriginRestrictionSpec.scala +++ b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/OriginRestrictionSpec.scala @@ -18,6 +18,8 @@ package com.hotels.styx.proxy import com.github.tomakehurst.wiremock.client.WireMock._ import com.hotels.styx.api.HttpHeaderNames._ import com.hotels.styx.api.FullHttpRequest.get +import com.hotels.styx.api.cookies.RequestCookie +import com.hotels.styx.api.cookies.RequestCookie.cookie import com.hotels.styx.api.messages.HttpResponseStatus._ import com.hotels.styx.client.StyxHeaderConfig.ORIGIN_ID_DEFAULT import com.hotels.styx.support.backends.FakeHttpServer @@ -54,7 +56,7 @@ class OriginRestrictionSpec extends FunSpec it("Routes to origin indicated by cookie.") { val request = get("/app/") .header(HOST, styxServer.proxyHost) - .addCookie("originRestrictionCookie", "h2") + .cookies(cookie("originRestrictionCookie", "h2")) .build() val response = decodedRequest(request) @@ -66,7 +68,7 @@ class OriginRestrictionSpec extends FunSpec it("Routes to range of origins indicated by cookie.") { val request = get("/app/") .header(HOST, styxServer.proxyHost) - .addCookie("originRestrictionCookie", "h(2|3)") + .cookies(cookie("originRestrictionCookie", "h(2|3)")) .build() val response = decodedRequest(request) @@ -77,7 +79,7 @@ class OriginRestrictionSpec extends FunSpec it("If nothing matches treat as no hosts available") { val request = get("/app/") .header(HOST, styxServer.proxyHost) - .addCookie("originRestrictionCookie", "(?!)") + .cookies(cookie("originRestrictionCookie", "(?!)")) .build() val response = decodedRequest(request) @@ -88,7 +90,7 @@ class OriginRestrictionSpec extends FunSpec it("Routes to list of origins indicated by cookie.") { val request = get("/app/") .header(HOST, styxServer.proxyHost) - .addCookie("originRestrictionCookie", "h2,h[3-4]") + .cookies(cookie("originRestrictionCookie", "h2,h[3-4]")) .build() val response = decodedRequest(request) From f2862c3aff281a8215d1aeb870aa7a982c15665d Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Tue, 17 Jul 2018 17:22:22 +0100 Subject: [PATCH 05/26] Use new cookies in responses --- .../com/hotels/styx/api/FullHttpResponse.java | 68 ++--- .../java/com/hotels/styx/api/HttpCookie.java | 6 +- .../com/hotels/styx/api/HttpResponse.java | 71 ++---- .../styx/api/cookies/ResponseCookie.java | 233 +++++++++++++++--- .../hotels/styx/api/FullHttpResponseTest.java | 62 ++--- .../com/hotels/styx/api/HttpResponseTest.java | 63 ++--- .../styx/api/cookies/ResponseCookieTest.java | 2 +- .../hotels/styx/client/StyxHttpClient.java | 11 +- .../NettyToStyxResponsePropagator.java | 49 +--- .../stickysession/StickySessionCookie.java | 6 +- .../styx/client/RetryHandlingSpec.scala | 7 +- .../styx/client/StickySessionSpec.scala | 26 +- .../NettyToStyxResponsePropagatorTest.java | 11 +- .../StickySessionCookieTest.java | 16 +- .../HttpMessageLoggingInterceptorTest.java | 7 +- .../StyxToNettyResponseTranslator.java | 41 +-- .../connectors/HttpResponseWriterTest.java | 4 +- .../StyxToNettyResponseTranslatorTest.java | 6 +- .../com/hotels/styx/proxy/HeadersSpec.scala | 8 +- 19 files changed, 350 insertions(+), 347 deletions(-) diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java index 6aeb53ff96..6d3f2d201d 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java @@ -16,14 +16,14 @@ package com.hotels.styx.api; import com.google.common.base.Objects; -import com.google.common.collect.ImmutableList; import com.google.common.net.MediaType; +import com.hotels.styx.api.cookies.PseudoMap; +import com.hotels.styx.api.cookies.ResponseCookie; import com.hotels.styx.api.messages.HttpResponseStatus; import com.hotels.styx.api.messages.HttpVersion; import rx.Observable; import java.nio.charset.Charset; -import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -35,9 +35,9 @@ import static com.hotels.styx.api.HttpHeaderValues.CHUNKED; import static com.hotels.styx.api.messages.HttpResponseStatus.OK; import static com.hotels.styx.api.messages.HttpVersion.HTTP_1_1; -import static com.hotels.styx.api.support.CookiesSupport.findCookie; import static io.netty.buffer.Unpooled.copiedBuffer; import static java.lang.Integer.parseInt; +import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; /** @@ -48,14 +48,12 @@ public class FullHttpResponse implements FullHttpMessage { private final HttpResponseStatus status; private final HttpHeaders headers; private final byte[] body; - private final List cookies; FullHttpResponse(Builder builder) { this.version = builder.version; this.status = builder.status; this.headers = builder.headers.build(); this.body = requireNonNull(builder.body); - this.cookies = ImmutableList.copyOf(builder.cookies); } /** @@ -92,14 +90,6 @@ public HttpHeaders headers() { return headers; } - public List cookies() { - return cookies; - } - - public Optional cookie(String name) { - return findCookie(cookies(), name); - } - /** * Returns the body of this message in its unencoded form. * @@ -160,19 +150,26 @@ public HttpResponse toStreamingResponse() { } } + public PseudoMap cookies() { + return ResponseCookie.decode(headers); + } + + public Optional cookie(String name) { + return cookies().firstMatch(name); + } + @Override public String toString() { return toStringHelper(this) .add("version", version) .add("status", status) .add("headers", headers) - .add("cookies", cookies) .toString(); } @Override public int hashCode() { - return Objects.hashCode(version, status, headers, cookies); + return Objects.hashCode(version, status, headers); } @Override @@ -186,8 +183,7 @@ public boolean equals(Object obj) { FullHttpResponse other = (FullHttpResponse) obj; return Objects.equal(this.version, other.version) && Objects.equal(this.status, other.status) - && Objects.equal(this.headers, other.headers) - && Objects.equal(this.cookies, other.cookies); + && Objects.equal(this.headers, other.headers); } /** @@ -199,12 +195,10 @@ public static final class Builder { private HttpVersion version = HTTP_1_1; private boolean validate = true; private byte[] body; - private final List cookies; public Builder() { this.headers = new HttpHeaders.Builder(); this.body = new byte[0]; - this.cookies = new ArrayList<>(); } public Builder(HttpResponseStatus status) { @@ -217,7 +211,6 @@ public Builder(FullHttpResponse response) { this.version = response.version(); this.headers = response.headers().newBuilder(); this.body = response.body(); - this.cookies = new ArrayList<>(response.cookies()); } public Builder(HttpResponse response, byte[] decoded) { @@ -225,7 +218,6 @@ public Builder(HttpResponse response, byte[] decoded) { this.version = response.version(); this.headers = response.headers().newBuilder(); this.body = decoded; - this.cookies = new ArrayList<>(response.cookies()); } /** @@ -326,38 +318,12 @@ public Builder setChunked() { return this; } - /** - * Adds a response cookie (adds a new Set-Cookie header). - * - * @param cookie cookie to add - * @return {@code this} - */ - public Builder addCookie(HttpCookie cookie) { - cookies.add(requireNonNull(cookie)); - return this; - } - - /** - * Adds a response cookie (adds a new Set-Cookie header). - * - * @param name cookie name - * @param value cookie value - * @return {@code this} - */ - public Builder addCookie(String name, String value) { - return addCookie(HttpCookie.cookie(name, value)); + public Builder cookies(ResponseCookie... cookies) { + return cookies(asList(cookies)); } - /** - * Removes a cookie if present (removes its Set-Cookie header). - * - * @param name name of the cookie - * @return {@code this} - */ - public Builder removeCookie(String name) { - findCookie(cookies, name) - .ifPresent(cookies::remove); - + private Builder cookies(List cookies) { + ResponseCookie.encode(headers, cookies); return this; } diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpCookie.java b/components/api/src/main/java/com/hotels/styx/api/HttpCookie.java index de222f36f2..8047bbe70f 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpCookie.java @@ -24,7 +24,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.collect.Iterables.isEmpty; -import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Sets.newHashSet; /** * An HttpCookie represents an HTTP cookie as sent in the {@code Set-Cookie} header of an @@ -71,7 +71,7 @@ private static Collection nonNulls(X... array) { checkNotNull(item); } - return newArrayList(array); + return newHashSet(array); } /** @@ -83,7 +83,7 @@ private static Collection nonNulls(X... array) { * @return a cookie */ public static HttpCookie cookie(String name, String value, Iterable attributes) { - return new HttpCookie(name, value, attributes); + return new HttpCookie(name, value, newHashSet(attributes)); } /** diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java index a75b4e0706..18dc8f723a 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java @@ -16,8 +16,9 @@ package com.hotels.styx.api; import com.google.common.base.Objects; -import com.google.common.collect.ImmutableList; import com.google.common.net.MediaType; +import com.hotels.styx.api.cookies.PseudoMap; +import com.hotels.styx.api.cookies.ResponseCookie; import com.hotels.styx.api.messages.HttpResponseStatus; import com.hotels.styx.api.messages.HttpVersion; import io.netty.buffer.ByteBuf; @@ -26,13 +27,11 @@ import rx.Observable; import java.nio.charset.Charset; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; import static com.hotels.styx.api.FlowControlDisableOperator.disableFlowControl; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_TYPE; @@ -42,13 +41,13 @@ import static com.hotels.styx.api.messages.HttpResponseStatus.statusWithCode; import static com.hotels.styx.api.messages.HttpVersion.HTTP_1_1; import static com.hotels.styx.api.messages.HttpVersion.httpVersion; -import static com.hotels.styx.api.support.CookiesSupport.findCookie; import static io.netty.buffer.ByteBufUtil.getBytes; import static io.netty.buffer.Unpooled.compositeBuffer; import static io.netty.buffer.Unpooled.copiedBuffer; import static io.netty.util.ReferenceCountUtil.release; import static java.lang.Integer.parseInt; import static java.lang.String.format; +import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; /** @@ -59,14 +58,12 @@ public class HttpResponse implements StreamingHttpMessage { private final HttpResponseStatus status; private final HttpHeaders headers; private final StyxObservable body; - private final List cookies; HttpResponse(Builder builder) { this.version = builder.version; this.status = builder.status; this.headers = builder.headers.build(); this.body = builder.body; - this.cookies = ImmutableList.copyOf(builder.cookies); } /** @@ -114,10 +111,6 @@ public HttpHeaders headers() { return headers; } - public List cookies() { - return cookies; - } - @Override public StyxObservable body() { return body; @@ -175,16 +168,13 @@ private static byte[] decodeAndRelease(CompositeByteBuf aggregate) { } } - /** - * Return the single cookie with the specified {@code name}. - * - * @param name cookie name - * @return the cookie if present - */ - public Optional cookie(String name) { - return findCookie(cookies(), name); + public PseudoMap cookies() { + return ResponseCookie.decode(headers); } + public Optional cookie(String name) { + return cookies().firstMatch(name); + } @Override public String toString() { @@ -192,13 +182,12 @@ public String toString() { .add("version", version) .add("status", status) .add("headers", headers) - .add("cookies", cookies) .toString(); } @Override public int hashCode() { - return Objects.hashCode(version, status, headers, cookies); + return Objects.hashCode(version, status, headers); } @Override @@ -212,8 +201,7 @@ public boolean equals(Object obj) { HttpResponse other = (HttpResponse) obj; return Objects.equal(this.version, other.version) && Objects.equal(this.status, other.status) - && Objects.equal(this.headers, other.headers) - && Objects.equal(this.cookies, other.cookies); + && Objects.equal(this.headers, other.headers); } /** @@ -225,12 +213,10 @@ public static final class Builder { private HttpVersion version = HTTP_1_1; private boolean validate = true; private StyxObservable body; - private final List cookies; public Builder() { this.headers = new HttpHeaders.Builder(); this.body = new StyxCoreObservable<>(Observable.empty()); - this.cookies = new ArrayList<>(); } public Builder(HttpResponseStatus status) { @@ -243,7 +229,6 @@ public Builder(HttpResponse response) { this.version = response.version(); this.headers = response.headers().newBuilder(); this.body = response.body(); - this.cookies = new ArrayList<>(response.cookies()); } public Builder(FullHttpResponse response, StyxObservable decoded) { @@ -251,7 +236,6 @@ public Builder(FullHttpResponse response, StyxObservable decoded) { this.version = httpVersion(response.version().toString()); this.headers = response.headers().newBuilder(); this.body = decoded; - this.cookies = new ArrayList<>(response.cookies()); } /** @@ -280,7 +264,7 @@ public Builder body(StyxObservable content) { * Sets the message body by encoding a {@link StyxObservable} of {@link String}s into bytes. * * @param contentObservable message body content. - * @param charset character set + * @param charset character set * @return {@code this} */ public Builder body(StyxObservable contentObservable, Charset charset) { @@ -321,37 +305,12 @@ public Builder setChunked() { return this; } - /** - * Adds a response cookie (adds a new Set-Cookie header). - * - * @param cookie cookie to add - * @return {@code this} - */ - public Builder addCookie(HttpCookie cookie) { - cookies.add(checkNotNull(cookie)); - return this; + public Builder cookies(ResponseCookie... cookies) { + return cookies(asList(cookies)); } - /** - * Adds a response cookie (adds a new Set-Cookie header). - * - * @param name cookie name - * @param value cookie value - * @return {@code this} - */ - public Builder addCookie(String name, String value) { - return addCookie(HttpCookie.cookie(name, value)); - } - - /** - * Removes a cookie if present (removes its Set-Cookie header). - * - * @param name name of the cookie - * @return {@code this} - */ - public Builder removeCookie(String name) { - findCookie(cookies, name) - .ifPresent(cookies::remove); + private Builder cookies(List cookies) { + ResponseCookie.encode(headers, cookies); return this; } diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java index 5b54933c54..bee71c3815 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java @@ -19,26 +19,26 @@ import com.google.common.base.Objects; import com.hotels.styx.api.HttpCookieAttribute; import com.hotels.styx.api.HttpHeaders; +import com.hotels.styx.api.HttpResponse; import io.netty.handler.codec.http.cookie.ClientCookieDecoder; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.codec.http.cookie.DefaultCookie; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; +import java.util.Optional; import java.util.Set; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.collect.Iterables.isEmpty; -import static com.google.common.collect.Lists.newArrayList; -import static com.hotels.styx.api.HttpCookieAttribute.domain; -import static com.hotels.styx.api.HttpCookieAttribute.httpOnly; -import static com.hotels.styx.api.HttpCookieAttribute.maxAge; -import static com.hotels.styx.api.HttpCookieAttribute.path; -import static com.hotels.styx.api.HttpCookieAttribute.secure; +import static com.google.common.collect.Sets.newHashSet; import static com.hotels.styx.api.HttpHeaderNames.SET_COOKIE; import static com.hotels.styx.api.common.Strings.quote; +import static io.netty.handler.codec.http.cookie.Cookie.UNDEFINED_MAX_AGE; +import static java.lang.Long.parseLong; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toSet; /** @@ -46,25 +46,32 @@ */ public final class ResponseCookie { private static final Joiner JOINER_ON_SEMI_COLON_AND_SPACE = Joiner.on("; "); + private final String name; private final String value; + + // attributes + private final String domain; + private final Long maxAge; + private final String path; + private final boolean httpOnly; + private final boolean secure; private final int hashCode; - private final Iterable attributes; - /** - * Constructs a cookie with a name, value and attributes. - * - * @param name cookie name - * @param value cookie value - * @param attributes cookie attributes - */ - private ResponseCookie(String name, String value, Iterable attributes) { - checkArgument(!isNullOrEmpty(name), "name cannot be null or empty"); - checkNotNull(value, "value cannot be null"); - this.name = name; - this.value = value; - this.attributes = checkNotNull(attributes); - this.hashCode = Objects.hashCode(name, value, attributes); + private ResponseCookie(Builder builder) { + if (builder.name == null || builder.name.isEmpty()) { + throw new IllegalArgumentException(); + } + + this.name = builder.name; + this.value = builder.value; + + this.domain = builder.domain; + this.maxAge = builder.maxAge; + this.path = builder.path; + this.httpOnly = builder.httpOnly; + this.secure = builder.secure; + this.hashCode = Objects.hashCode(name, value, domain, maxAge, path, secure, httpOnly); } /** @@ -76,16 +83,16 @@ private ResponseCookie(String name, String value, Iterable * @return a cookie */ public static ResponseCookie cookie(String name, String value, HttpCookieAttribute... attributes) { - return new ResponseCookie(name, value, nonNulls(attributes)); + return cookie(name, value, nonNulls(attributes)); } // throws exception if any values are null - private static Collection nonNulls(X... array) { + private static Set nonNulls(X... array) { for (X item : array) { checkNotNull(item); } - return newArrayList(array); + return newHashSet(array); } /** @@ -97,14 +104,40 @@ private static Collection nonNulls(X... array) { * @return a cookie */ public static ResponseCookie cookie(String name, String value, Iterable attributes) { - return new ResponseCookie(name, value, attributes); + ResponseCookie.Builder builder = new ResponseCookie.Builder(name, value); + + attributes.forEach(attribute -> { + switch (attribute.name()) { + case "Domain": + builder.domain(attribute.value()); + break; + case "Path": + builder.path(attribute.value()); + break; + case "Max-Age": + builder.maxAge(parseLong(attribute.value())); + break; + case "Secure": + builder.secure(true); + break; + case "HttpOnly": + builder.httpOnly(true); + break; + } + }); + + return builder.build(); } - public static Set decode(HttpHeaders headers) { - return headers.getAll(SET_COOKIE).stream() + public static PseudoMap decode(HttpHeaders headers) { + return wrap(headers.getAll(SET_COOKIE).stream() .map(ClientCookieDecoder.LAX::decode) .map(ResponseCookie::convert) - .collect(toSet()); + .collect(toSet())); + } + + private static PseudoMap wrap(Set cookies) { + return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); } public static void encode(HttpHeaders.Builder headers, Collection cookies) { @@ -115,27 +148,41 @@ public static void encode(HttpHeaders.Builder headers, Collection attributes = new ArrayList() { { if (!isNullOrEmpty(cookie.domain())) { - add(domain(cookie.domain())); - } - if (!isNullOrEmpty(cookie.path())) { - add(path(cookie.path())); + add(HttpCookieAttribute.domain(cookie.domain())); } if (cookie.maxAge() != Long.MIN_VALUE) { - add(maxAge((int) cookie.maxAge())); + add(HttpCookieAttribute.maxAge((int) cookie.maxAge())); + } + if (!isNullOrEmpty(cookie.path())) { + add(HttpCookieAttribute.path(cookie.path())); } if (cookie.isHttpOnly()) { - add(httpOnly()); + add(HttpCookieAttribute.httpOnly()); } if (cookie.isSecure()) { - add(secure()); + add(HttpCookieAttribute.secure()); } } }; @@ -166,10 +213,34 @@ public String value() { * * @return cookie attributes */ - public Iterable attributes() { + // TODO delete after clean-up + public Set attributes() { + Set attributes = new HashSet<>(); + + if (!isNullOrEmpty(domain)) { + attributes.add(HttpCookieAttribute.domain(domain)); + } + + if (!isNullOrEmpty(path)) { + attributes.add(HttpCookieAttribute.path(path)); + } + + if (maxAge != null && maxAge != UNDEFINED_MAX_AGE) { + attributes.add(HttpCookieAttribute.maxAge(maxAge.intValue())); + } + + if (secure) { + attributes.add(HttpCookieAttribute.secure()); + } + + if (httpOnly) { + attributes.add(HttpCookieAttribute.httpOnly()); + } + return attributes; } + @Override public int hashCode() { return hashCode; @@ -184,7 +255,7 @@ public boolean equals(Object obj) { return false; } ResponseCookie other = (ResponseCookie) obj; - return Objects.equal(name, other.name) && Objects.equal(value, other.value) && Objects.equal(attributes, other.attributes); + return Objects.equal(name, other.name) && Objects.equal(value, other.value) && Objects.equal(attributes(), other.attributes()); } @Override @@ -193,8 +264,88 @@ public String toString() { .append(name) .append('=') .append(value) - .append(isEmpty(attributes) ? "" : "; "); + .append(isEmpty(attributes()) ? "" : "; "); + + return JOINER_ON_SEMI_COLON_AND_SPACE.appendTo(builder, attributes()).toString(); + } + + public Optional maxAge() { + return Optional.ofNullable(maxAge).filter(value -> value != UNDEFINED_MAX_AGE); + } + + public Optional path() { + return Optional.ofNullable(path); + } + + public boolean httpOnly() { + return httpOnly; + } + + public Optional domain() { + return Optional.ofNullable(domain); + } + + public boolean secure() { + return secure; + } + + /** + * Builds response cookie. + */ + public static class Builder { + private String name; + private String value; + + // attributes + private String domain; + private Long maxAge; + private String path; + private boolean httpOnly; + private boolean secure; - return JOINER_ON_SEMI_COLON_AND_SPACE.appendTo(builder, attributes).toString(); + public Builder(String name, String value) { + this.name = requireNonNull(name); + this.value = requireNonNull(value); + } + + public Builder name(String name) { + this.name = requireNonNull(name); + return this; + } + + public Builder value(String value) { + this.value = requireNonNull(value); + return this; + } + + public Builder domain(String domain) { + this.domain = requireNonNull(domain); + return this; + } + + public Builder maxAge(long maxAge) { + this.maxAge = maxAge; + return this; + } + + public Builder path(String path) { + this.path = requireNonNull(path); + return this; + } + + public Builder httpOnly(boolean httpOnly) { + this.httpOnly = httpOnly; + return this; + } + + public Builder secure(boolean secure) { + this.secure = secure; + return this; + } + + public ResponseCookie build() { + return new ResponseCookie(this); + } } + } diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java index 235621348e..d77e48f9bd 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java @@ -15,7 +15,7 @@ */ package com.hotels.styx.api; -import com.google.common.collect.Iterables; +import com.hotels.styx.api.cookies.ResponseCookie; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.testng.annotations.DataProvider; @@ -23,17 +23,17 @@ import rx.observers.TestSubscriber; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ExecutionException; import static com.hotels.styx.api.FullHttpRequest.get; import static com.hotels.styx.api.FullHttpResponse.response; -import static com.hotels.styx.api.HttpCookie.cookie; import static com.hotels.styx.api.HttpCookieAttribute.domain; import static com.hotels.styx.api.HttpCookieAttribute.maxAge; import static com.hotels.styx.api.HttpCookieAttribute.path; import static com.hotels.styx.api.HttpHeader.header; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; -import static com.hotels.styx.api.HttpHeaderNames.LOCATION; +import static com.hotels.styx.api.cookies.ResponseCookie.cookie; import static com.hotels.styx.api.matchers.HttpHeadersMatcher.isNotCacheable; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_GATEWAY; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_REQUEST; @@ -64,7 +64,7 @@ public void convertsToStreamingHttpResponse() throws Exception { FullHttpResponse response = response(CREATED) .version(HTTP_1_1) .header("HeaderName", "HeaderValue") - .addCookie("CookieName", "CookieValue") + .cookies(cookie("CookieName", "CookieValue")) .body("message content", UTF_8) .build(); @@ -74,8 +74,10 @@ public void convertsToStreamingHttpResponse() throws Exception { assertThat(streaming.status(), is(CREATED)); assertThat(streaming.headers(), containsInAnyOrder( header("Content-Length", "15"), - header("HeaderName", "HeaderValue"))); - assertThat(streaming.cookies(), contains(cookie("CookieName", "CookieValue"))); + header("HeaderName", "HeaderValue"), + header("Set-Cookie", "CookieName=CookieValue") + )); + assertThat(streaming.cookies(), contains(ResponseCookie.cookie("CookieName", "CookieValue"))); String body = streaming.toFullResponse(0x100000) .asCompletableFuture() @@ -111,7 +113,7 @@ public void createsResponseWithMinimalInformation() { @Test public void setsASingleOutboundCookie() { FullHttpResponse response = FullHttpResponse.response() - .addCookie(cookie("user", "QSplbl9HX1VL", domain(".hotels.com"), path("/"), maxAge(3600))) + .cookies(cookie("user", "QSplbl9HX1VL", domain(".hotels.com"), path("/"), maxAge(3600))) .build(); assertThat(response.cookie("user"), isValue(cookie("user", "QSplbl9HX1VL", domain(".hotels.com"), path("/"), maxAge(3600)))); @@ -120,22 +122,25 @@ public void setsASingleOutboundCookie() { @Test public void setsMultipleOutboundCookies() { FullHttpResponse response = FullHttpResponse.response() - .addCookie("a", "b") - .addCookie("c", "d") + .cookies( + cookie("a", "b"), + cookie("c", "d")) .build(); - Iterable cookies = response.cookies(); - assertThat(Iterables.size(cookies), is(2)); + Set cookies = response.cookies(); - assertThat(Iterables.get(cookies, 0), is(cookie("a", "b"))); - assertThat(Iterables.get(cookies, 1), is(cookie("c", "d"))); + assertThat(cookies, containsInAnyOrder( + cookie("a", "b"), + cookie("c", "d") + )); } @Test public void getASingleCookieValue() { FullHttpResponse response = FullHttpResponse.response() - .addCookie("a", "b") - .addCookie("c", "d") + .cookies( + cookie("a", "b"), + cookie("c", "d")) .build(); assertThat(response.cookie("c"), isValue(cookie("c", "d"))); @@ -155,25 +160,6 @@ public void canRemoveAHeader() { assertThat(shouldRemoveHeader.headers(), contains(header("a", "b"))); } - @Test - public void removesACookie() { - FullHttpResponse response = new FullHttpResponse.Builder(seeOther("/home")) - .addCookie(cookie("a", "b")) - .addCookie(cookie("c", "d")) - .build(); - FullHttpResponse shouldClearCookie = response.newBuilder() - .removeCookie("a") - .build(); - - assertThat(shouldClearCookie.cookies(), contains(cookie("c", "d"))); - } - - private static FullHttpResponse seeOther(String newLocation) { - return response(SEE_OTHER) - .header(LOCATION, newLocation) - .build(); - } - @Test public void canRemoveResponseBody() { FullHttpResponse response = response(NO_CONTENT) @@ -246,17 +232,17 @@ public void shouldCheckIfCurrentResponseIsARedirectToOtherResource(com.hotels.st @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookie() { - FullHttpResponse.response().addCookie(null).build(); + FullHttpResponse.response().cookies(null).build(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookieName() { - FullHttpResponse.response().addCookie(null, "value").build(); + FullHttpResponse.response().cookies(cookie(null, "value")).build(); } @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookieValue() { - FullHttpResponse.response().addCookie("name", null).build(); + FullHttpResponse.response().cookies(cookie("name", null)).build(); } @DataProvider(name = "responses") diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java index 77623d657b..b63c8e2f98 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java @@ -15,24 +15,24 @@ */ package com.hotels.styx.api; -import com.google.common.collect.Iterables; +import com.hotels.styx.api.cookies.ResponseCookie; import com.hotels.styx.api.messages.HttpResponseStatus; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import java.util.stream.Stream; -import static com.hotels.styx.api.HttpCookie.cookie; import static com.hotels.styx.api.HttpCookieAttribute.domain; import static com.hotels.styx.api.HttpCookieAttribute.maxAge; import static com.hotels.styx.api.HttpCookieAttribute.path; import static com.hotels.styx.api.HttpHeader.header; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; -import static com.hotels.styx.api.HttpHeaderNames.LOCATION; +import static com.hotels.styx.api.cookies.ResponseCookie.cookie; import static com.hotels.styx.api.matchers.HttpHeadersMatcher.isNotCacheable; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_GATEWAY; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_REQUEST; @@ -53,8 +53,10 @@ import static java.util.stream.Collectors.toList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.emptyIterable; import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; @@ -65,7 +67,7 @@ public void encodesToFullHttpResponse() throws Exception { HttpResponse response = response(CREATED) .version(HTTP_1_0) .header("HeaderName", "HeaderValue") - .addCookie("CookieName", "CookieValue") + .cookies(cookie("CookieName", "CookieValue")) .body(body("foo", "bar")) .build(); @@ -75,7 +77,7 @@ public void encodesToFullHttpResponse() throws Exception { assertThat(full.status(), is(CREATED)); assertThat(full.version(), is(HTTP_1_0)); - assertThat(full.headers(), contains(header("HeaderName", "HeaderValue"))); + assertThat(full.headers(), contains(header("HeaderName", "HeaderValue"), header("Set-Cookie", "CookieName=CookieValue"))); assertThat(full.cookies(), contains(cookie("CookieName", "CookieValue"))); assertThat(full.body(), is(bytes("foobar"))); @@ -125,34 +127,36 @@ public void createsResponseWithMinimalInformation() throws Exception { @Test public void setsASingleOutboundCookie() { HttpResponse response = response() - .addCookie(cookie("user", "QSplbl9HX1VL", domain(".hotels.com"), path("/"), maxAge(3600))) + .cookies(cookie("user", "QSplbl9HX1VL", domain(".hotels.com"), path("/"), maxAge(3600))) .build(); - assertThat(response.cookie("user"), isValue(cookie("user", "QSplbl9HX1VL", domain(".hotels.com"), path("/"), maxAge(3600)))); + assertThat(response.cookies().firstMatch("user"), isValue(cookie("user", "QSplbl9HX1VL", domain(".hotels.com"), path("/"), maxAge(3600)))); } @Test public void setsMultipleOutboundCookies() { HttpResponse response = response() - .addCookie("a", "b") - .addCookie("c", "d") + .cookies( + cookie("a", "b"), + cookie("c", "d")) .build(); - Iterable cookies = response.cookies(); - assertThat(Iterables.size(cookies), is(2)); + Set cookies = response.cookies(); - assertThat(Iterables.get(cookies, 0), is(cookie("a", "b"))); - assertThat(Iterables.get(cookies, 1), is(cookie("c", "d"))); + assertThat(cookies, containsInAnyOrder( + cookie("a", "b"), + cookie("c", "d"))); } @Test public void getASingleCookieValue() { HttpResponse response = response() - .addCookie("a", "b") - .addCookie("c", "d") + .cookies( + cookie("a", "b"), + cookie("c", "d")) .build(); - assertThat(response.cookie("c"), isValue(cookie("c", "d"))); + assertThat(response.cookies().firstMatch("c"), isValue(cookie("c", "d"))); } @Test @@ -169,25 +173,6 @@ public void canRemoveAHeader() { assertThat(shouldRemoveHeader.headers(), contains(header("a", "b"))); } - @Test - public void removesACookie() { - HttpResponse response = new HttpResponse.Builder(seeOther("/home")) - .addCookie(cookie("a", "b")) - .addCookie(cookie("c", "d")) - .build(); - HttpResponse shouldClearCookie = response.newBuilder() - .removeCookie("a") - .build(); - - assertThat(shouldClearCookie.cookies(), contains(cookie("c", "d"))); - } - - private static HttpResponse seeOther(String newLocation) { - return response(SEE_OTHER) - .header(LOCATION, newLocation) - .build(); - } - @Test public void canRemoveResponseBody() { HttpResponse response = response(NO_CONTENT) @@ -260,17 +245,17 @@ public void shouldCheckIfCurrentResponseIsARedirectToOtherResource(HttpResponseS @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookie() { - response().addCookie(null).build(); + response().cookies(null).build(); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookieName() { - response().addCookie(null, "value").build(); + response().cookies(cookie(null, "value")).build(); } @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookieValue() { - response().addCookie("name", null).build(); + response().cookies(cookie("name", null)).build(); } @DataProvider(name = "responses") diff --git a/components/api/src/test/java/com/hotels/styx/api/cookies/ResponseCookieTest.java b/components/api/src/test/java/com/hotels/styx/api/cookies/ResponseCookieTest.java index 1b29276b73..8f559c82bf 100644 --- a/components/api/src/test/java/com/hotels/styx/api/cookies/ResponseCookieTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/cookies/ResponseCookieTest.java @@ -32,7 +32,7 @@ public void acceptsOnlyNonEmptyName() { cookie("", "value", domain(".hotels.com")); } - @Test(expectedExceptions = IllegalArgumentException.class) + @Test(expectedExceptions = NullPointerException.class) public void acceptsOnlyNonNullName() { cookie(null, "value", domain(".hotels.com")); } diff --git a/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java b/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java index 1cad2131dd..7681d14ba2 100644 --- a/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java +++ b/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java @@ -25,6 +25,7 @@ import com.hotels.styx.api.client.loadbalancing.spi.LoadBalancer; import com.hotels.styx.api.client.retrypolicy.spi.RetryPolicy; import com.hotels.styx.api.cookies.RequestCookie; +import com.hotels.styx.api.cookies.ResponseCookie; import com.hotels.styx.api.exceptions.NoAvailableHostsException; import com.hotels.styx.api.messages.HttpResponseStatus; import com.hotels.styx.api.metrics.MetricRegistry; @@ -309,14 +310,18 @@ public List avoidOrigins() { private HttpResponse addStickySessionIdentifier(HttpResponse httpResponse, Origin origin) { if (this.loadBalancer instanceof StickySessionLoadBalancingStrategy) { int maxAge = backendService.stickySessionConfig().stickySessionTimeoutSeconds(); - return httpResponse.newBuilder() - .addCookie(newStickySessionCookie(id, origin.id(), maxAge)) - .build(); + return addCookie(httpResponse, newStickySessionCookie(id, origin.id(), maxAge)); } else { return httpResponse; } } + private static HttpResponse addCookie(HttpResponse response, ResponseCookie cookie) { + HttpResponse.Builder builder = response.newBuilder(); + ResponseCookie.encode(builder, cookie); + return builder.build(); + } + private HttpRequest rewriteUrl(HttpRequest request) { return rewriteRuleset.rewrite(request); } diff --git a/components/client/src/main/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagator.java b/components/client/src/main/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagator.java index 41e93fe7e0..66e7f38685 100644 --- a/components/client/src/main/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagator.java +++ b/components/client/src/main/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagator.java @@ -16,12 +16,9 @@ package com.hotels.styx.client.netty.connectionpool; import com.google.common.annotations.VisibleForTesting; -import com.hotels.styx.api.HttpCookie; -import com.hotels.styx.api.HttpCookieAttribute; import com.hotels.styx.api.HttpRequest; import com.hotels.styx.api.HttpResponse; import com.hotels.styx.api.client.Origin; -import com.hotels.styx.api.messages.HttpResponseStatus; import com.hotels.styx.api.exceptions.ResponseTimeoutException; import com.hotels.styx.api.exceptions.TransportLostException; import com.hotels.styx.client.BadHttpResponseException; @@ -33,29 +30,19 @@ import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.LastHttpContent; -import io.netty.handler.codec.http.cookie.ClientCookieDecoder; -import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.timeout.IdleStateEvent; import org.slf4j.Logger; import rx.Observable; import rx.Producer; import rx.Subscriber; -import java.util.ArrayList; import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import static com.google.common.base.Strings.isNullOrEmpty; -import static com.hotels.styx.api.HttpCookieAttribute.domain; -import static com.hotels.styx.api.HttpCookieAttribute.httpOnly; -import static com.hotels.styx.api.HttpCookieAttribute.maxAge; -import static com.hotels.styx.api.HttpCookieAttribute.path; -import static com.hotels.styx.api.HttpCookieAttribute.secure; -import static com.hotels.styx.api.HttpHeaderNames.SET_COOKIE; +import static com.hotels.styx.api.HttpResponse.response; import static com.hotels.styx.api.StyxInternalObservables.fromRxObservable; -import static com.hotels.styx.api.common.Strings.quote; -import static com.hotels.styx.api.support.CookiesSupport.isCookieHeader; +import static com.hotels.styx.api.messages.HttpResponseStatus.statusWithCode; import static io.netty.util.ReferenceCountUtil.retain; import static java.lang.String.format; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -225,17 +212,11 @@ private void emitResponseError(Throwable cause) { @VisibleForTesting static HttpResponse.Builder toStyxResponse(io.netty.handler.codec.http.HttpResponse nettyResponse) { - HttpResponse.Builder responseBuilder = HttpResponse.response(HttpResponseStatus.statusWithCode(nettyResponse.getStatus().code())); + HttpResponse.Builder responseBuilder = response(statusWithCode(nettyResponse.getStatus().code())); stream(nettyResponse.headers().spliterator(), false) - .filter(header -> !isCookieHeader(header.getKey())) .forEach(header -> responseBuilder.addHeader(header.getKey(), header.getValue())); - nettyResponse.headers().getAll(SET_COOKIE).stream() - .map(ClientCookieDecoder.LAX::decode) - .map(NettyToStyxResponsePropagator::nettyCookieToStyxCookie) - .forEach(responseBuilder::addCookie); - return responseBuilder; } @@ -249,30 +230,6 @@ private static HttpResponse toStyxResponse(io.netty.handler.codec.http.HttpRespo } } - private static HttpCookie nettyCookieToStyxCookie(Cookie cookie) { - Iterable attributes = new ArrayList() { - { - if (!isNullOrEmpty(cookie.domain())) { - add(domain(cookie.domain())); - } - if (!isNullOrEmpty(cookie.path())) { - add(path(cookie.path())); - } - if (cookie.maxAge() != Long.MIN_VALUE) { - add(maxAge((int) cookie.maxAge())); - } - if (cookie.isHttpOnly()) { - add(httpOnly()); - } - if (cookie.isSecure()) { - add(secure()); - } - } - }; - String value = cookie.wrap() ? quote(cookie.value()) : cookie.value(); - return HttpCookie.cookie(cookie.name(), value, attributes); - } - private static class InitializeNettyContentProducerOnSubscribe implements Observable.OnSubscribe { private final EventLoop eventLoop; private final FlowControllingHttpContentProducer producer; diff --git a/components/client/src/main/java/com/hotels/styx/client/stickysession/StickySessionCookie.java b/components/client/src/main/java/com/hotels/styx/client/stickysession/StickySessionCookie.java index 8a17707c5c..c0b8e995bd 100644 --- a/components/client/src/main/java/com/hotels/styx/client/stickysession/StickySessionCookie.java +++ b/components/client/src/main/java/com/hotels/styx/client/stickysession/StickySessionCookie.java @@ -16,14 +16,14 @@ package com.hotels.styx.client.stickysession; import com.google.common.collect.ImmutableList; -import com.hotels.styx.api.HttpCookie; import com.hotels.styx.api.HttpCookieAttribute; import com.hotels.styx.api.Id; +import com.hotels.styx.api.cookies.ResponseCookie; -import static com.hotels.styx.api.HttpCookie.cookie; import static com.hotels.styx.api.HttpCookieAttribute.httpOnly; import static com.hotels.styx.api.HttpCookieAttribute.maxAge; import static com.hotels.styx.api.HttpCookieAttribute.path; +import static com.hotels.styx.api.cookies.ResponseCookie.cookie; /** * Provides methods for handling sticky-session cookies used to identify which origin has "stuck". @@ -40,7 +40,7 @@ private StickySessionCookie() { * @param maxAge maxAge attribute for cookie * @return a new cookie */ - public static HttpCookie newStickySessionCookie(Id applicationId, Id originId, int maxAge) { + public static ResponseCookie newStickySessionCookie(Id applicationId, Id originId, int maxAge) { return cookie(stickySessionCookieName(applicationId), originId.toString(), attributes(maxAge)); } diff --git a/components/client/src/test/integration/scala/com/hotels/styx/client/RetryHandlingSpec.scala b/components/client/src/test/integration/scala/com/hotels/styx/client/RetryHandlingSpec.scala index 2a0ae4b355..2378b4bb6a 100644 --- a/components/client/src/test/integration/scala/com/hotels/styx/client/RetryHandlingSpec.scala +++ b/components/client/src/test/integration/scala/com/hotels/styx/client/RetryHandlingSpec.scala @@ -163,7 +163,12 @@ class RetryHandlingSpec extends FunSuite with BeforeAndAfterAll with Matchers wi val response = waitForResponse(client.sendRequest(request)) - response.cookie("styx_origin_generic-app").get().toString should fullyMatch regex "styx_origin_generic-app=HEALTHY_ORIGIN_TWO; Max-Age=.*; Path=/; HttpOnly" + val cookie = response.cookie("styx_origin_generic-app").get() + + cookie.value() should be("HEALTHY_ORIGIN_TWO") + cookie.path().get() should be("/") + cookie.httpOnly() should be(true) + cookie.maxAge().isPresent should be(true) } private def respondWithHeadersOnly(): ResponseDefinitionBuilder = aResponse diff --git a/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala b/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala index 12e719bd9a..f96f6a52f1 100644 --- a/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala +++ b/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala @@ -106,7 +106,12 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers val response = waitForResponse(client.sendRequest(request)) response.status() should be(OK) - response.cookie("styx_origin_app").get().toString should fullyMatch regex "styx_origin_app=app-0[12]; Max-Age=.*; Path=/; HttpOnly" + val cookie = response.cookie("styx_origin_app").get() + cookie.value() should fullyMatch regex "app-0[12]" + + cookie.path().get() should be("/") + cookie.httpOnly() should be(true) + cookie.maxAge().isPresent should be(true) } test("Responds without sticky session cookie when sticky session is not enabled") { @@ -175,7 +180,14 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers response.status() should be(OK) response.cookies().asScala should have size (1) - response.cookie("styx_origin_app").get().toString should fullyMatch regex "styx_origin_app=app-0[12]; Max-Age=.*; Path=/; HttpOnly" + + val cookie = response.cookie("styx_origin_app").get() + + cookie.value() should fullyMatch regex "app-0[12]" + + cookie.path().get() should be("/") + cookie.httpOnly() should be(true) + cookie.maxAge().isPresent should be(true) } test("Routes to new origin when the origin indicated by sticky session cookie is no longer available.") { @@ -192,8 +204,14 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers val response = waitForResponse(client.sendRequest(request)) response.status() should be(OK) - response.cookies() should have size (1) - response.cookie("styx_origin_app").get().toString should fullyMatch regex "styx_origin_app=app-02; Max-Age=.*; Path=/; HttpOnly" + response.cookies() should have size 1 + val cookie = response.cookie("styx_origin_app").get() + + cookie.value() should be("app-02") + + cookie.path().get() should be("/") + cookie.httpOnly() should be(true) + cookie.maxAge().isPresent should be(true) } private def healthCheckIntervalFor(appId: String) = 1000 diff --git a/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagatorTest.java b/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagatorTest.java index 70575040c4..0a5739b03d 100644 --- a/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagatorTest.java +++ b/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagatorTest.java @@ -43,15 +43,16 @@ import java.util.Optional; import static com.google.common.base.Charsets.UTF_8; -import static com.hotels.styx.api.HttpCookie.cookie; import static com.hotels.styx.api.HttpCookieAttribute.domain; import static com.hotels.styx.api.HttpCookieAttribute.httpOnly; import static com.hotels.styx.api.HttpCookieAttribute.path; import static com.hotels.styx.api.Id.GENERIC_APP; import static com.hotels.styx.api.StyxInternalObservables.toRxObservable; import static com.hotels.styx.api.client.Origin.newOriginBuilder; +import static com.hotels.styx.api.cookies.ResponseCookie.cookie; import static com.hotels.styx.api.support.HostAndPorts.localhost; import static com.hotels.styx.client.netty.connectionpool.NettyToStyxResponsePropagator.toStyxResponse; +import static com.hotels.styx.support.matchers.IsOptional.isValue; import static io.netty.buffer.Unpooled.copiedBuffer; import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; @@ -244,8 +245,8 @@ public void shouldConvertNettyCookieHeaderToStyxCookies() { nettyResponse.headers().add("Set-Cookie", "SESSID=sessId; Domain=.foo.com; Path=/; HttpOnly"); HttpResponse styxResponse = toStyxResponse(nettyResponse).build(); - assertThat(styxResponse.header("Set-Cookie"), is(Optional.empty())); - assertThat(styxResponse.cookie("SESSID"), equalTo( + assertThat(styxResponse.header("Set-Cookie"), isValue("SESSID=sessId; Domain=.foo.com; Path=/; HttpOnly")); + assertThat(styxResponse.cookies().firstMatch("SESSID"), equalTo( Optional.of(cookie("SESSID", "sessId", domain(".foo.com"), path("/"), httpOnly())))); } @@ -255,8 +256,8 @@ public void shouldLeaveIntactQuotedCookieValues() { nettyResponse.headers().add("Set-Cookie", "SESSID=\"sessId\"; Domain=.foo.com; Path=/; HttpOnly"); HttpResponse styxResponse = toStyxResponse(nettyResponse).build(); - assertThat(styxResponse.header("Set-Cookie"), is(Optional.empty())); - assertThat(styxResponse.cookie("SESSID"), equalTo( + assertThat(styxResponse.header("Set-Cookie"), isValue("SESSID=\"sessId\"; Domain=.foo.com; Path=/; HttpOnly")); + assertThat(styxResponse.cookies().firstMatch("SESSID"), equalTo( Optional.of(cookie("SESSID", "\"sessId\"", domain(".foo.com"), path("/"), httpOnly())))); } diff --git a/components/client/src/test/unit/java/com/hotels/styx/client/stickysession/StickySessionCookieTest.java b/components/client/src/test/unit/java/com/hotels/styx/client/stickysession/StickySessionCookieTest.java index d32cb676f9..ef26590013 100644 --- a/components/client/src/test/unit/java/com/hotels/styx/client/stickysession/StickySessionCookieTest.java +++ b/components/client/src/test/unit/java/com/hotels/styx/client/stickysession/StickySessionCookieTest.java @@ -15,20 +15,28 @@ */ package com.hotels.styx.client.stickysession; -import com.hotels.styx.api.HttpCookie; -import com.hotels.styx.client.stickysession.StickySessionCookie; +import com.hotels.styx.api.cookies.ResponseCookie; import org.testng.annotations.Test; +import java.util.Optional; + import static com.hotels.styx.api.Id.id; import static com.hotels.styx.client.stickysession.StickySessionCookie.newStickySessionCookie; +import static com.hotels.styx.support.matchers.IsOptional.isValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; public class StickySessionCookieTest { @Test public void createsCookieFromApplicationOriginAndMaxAge() { - HttpCookie cookie = newStickySessionCookie(id("app"), id("app-01"), 86400); - assertThat(cookie.toString(), is("styx_origin_app=app-01; Max-Age=86400; Path=/; HttpOnly")); + ResponseCookie cookie = newStickySessionCookie(id("app"), id("app-01"), 86400); + + assertThat(cookie.name(), is("styx_origin_app")); + assertThat(cookie.value(), is("app-01")); + + assertThat(cookie.maxAge(), isValue(86400L)); + assertThat(cookie.path(), isValue("/")); + assertThat(cookie.httpOnly(), is(true)); } @Test diff --git a/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java b/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java index 92797fd2d0..a67c7da0e8 100644 --- a/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java +++ b/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java @@ -19,6 +19,7 @@ import com.hotels.styx.api.HttpRequest; import com.hotels.styx.api.HttpResponse; import com.hotels.styx.api.StyxObservable; +import com.hotels.styx.api.cookies.ResponseCookie; import com.hotels.styx.support.api.HttpMessageBodies; import com.hotels.styx.support.matchers.LoggingTestSupport; import org.testng.annotations.AfterMethod; @@ -60,11 +61,11 @@ public void logsRequestsAndResponses() { consume(interceptor.intercept(request, respondWith( response(OK) .header("RespHeader", "RespHeaderValue") - .addCookie("RespCookie", "RespCookieValue") + .cookies(ResponseCookie.cookie("RespCookie", "RespCookieValue")) ))); String requestPattern = "request=\\{method=GET, secure=false, uri=/, origin=\"N/A\", headers=\\[ReqHeader=ReqHeaderValue, Cookie=ReqCookie=ReqCookieValue\\], cookies=\\[ReqCookie=ReqCookieValue\\]\\}"; - String responsePattern = "response=\\{status=200 OK, headers=\\[RespHeader=RespHeaderValue\\], cookies=\\[RespCookie=RespCookieValue\\]\\}"; + String responsePattern = "response=\\{status=200 OK, headers=\\[RespHeader=RespHeaderValue\\, Set-Cookie=RespCookie=RespCookieValue], cookies=\\[RespCookie=RespCookieValue\\]\\}"; assertThat(responseLogSupport.log(), contains( loggingEvent(INFO, "requestId=" + request.id() + ", " + requestPattern), @@ -82,7 +83,7 @@ public void logsRequestsAndResponsesShort() { consume(interceptor.intercept(request, respondWith( response(OK) .header("RespHeader", "RespHeaderValue") - .addCookie("RespCookie", "RespCookieValue") + .cookies(ResponseCookie.cookie("RespCookie", "RespCookieValue")) ))); String requestPattern = "request=\\{method=GET, secure=false, uri=/, origin=\"N/A\"}"; diff --git a/components/server/src/main/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslator.java b/components/server/src/main/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslator.java index 7211b12431..6b4d879846 100644 --- a/components/server/src/main/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslator.java +++ b/components/server/src/main/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslator.java @@ -15,17 +15,10 @@ */ package com.hotels.styx.server.netty.connectors; -import com.hotels.styx.api.HttpCookie; import io.netty.handler.codec.http.DefaultHttpResponse; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; -import io.netty.handler.codec.http.cookie.Cookie; -import io.netty.handler.codec.http.cookie.DefaultCookie; - -import static com.hotels.styx.api.HttpHeaderNames.SET_COOKIE; -import static com.hotels.styx.api.cookies.ServerCookieEncoder.LAX; -import static java.lang.Integer.parseInt; class StyxToNettyResponseTranslator implements ResponseTranslator { @@ -38,43 +31,11 @@ public HttpResponse toNettyResponse(com.hotels.styx.api.HttpResponse httpRespons httpResponse.headers().forEach(httpHeader -> nettyResponse.headers().add(httpHeader.name(), httpHeader.value())); - httpResponse.cookies().stream() - .map(StyxToNettyResponseTranslator::toNettyCookie) - .map(LAX::encode) - .forEach(setCookieHeader -> nettyResponse.headers().add(SET_COOKIE, setCookieHeader)); - return nettyResponse; } - private HttpVersion toNettyVersion(com.hotels.styx.api.messages.HttpVersion version) { + private static HttpVersion toNettyVersion(com.hotels.styx.api.messages.HttpVersion version) { return HttpVersion.valueOf(version.toString()); } - - - private static Cookie toNettyCookie(HttpCookie cookie) { - Cookie nettyCookie = new DefaultCookie(cookie.name(), cookie.value()); - - cookie.attributes().forEach(attribute -> { - switch (attribute.name().toLowerCase()) { - case "domain": - nettyCookie.setDomain(attribute.value()); - break; - case "path": - nettyCookie.setPath(attribute.value()); - break; - case "max-age": - nettyCookie.setMaxAge(parseInt(attribute.value())); - break; - case "httponly": - nettyCookie.setHttpOnly(true); - break; - case "secure": - nettyCookie.setSecure(true); - break; - } - }); - return nettyCookie; - } - } diff --git a/components/server/src/test/java/com/hotels/styx/server/netty/connectors/HttpResponseWriterTest.java b/components/server/src/test/java/com/hotels/styx/server/netty/connectors/HttpResponseWriterTest.java index 2f0fb80298..aa4cc71d45 100644 --- a/components/server/src/test/java/com/hotels/styx/server/netty/connectors/HttpResponseWriterTest.java +++ b/components/server/src/test/java/com/hotels/styx/server/netty/connectors/HttpResponseWriterTest.java @@ -40,9 +40,9 @@ import java.util.concurrent.atomic.AtomicLong; import static com.google.common.base.Charsets.UTF_8; -import static com.hotels.styx.api.HttpCookie.cookie; import static com.hotels.styx.api.HttpResponse.response; import static com.hotels.styx.api.StyxInternalObservables.fromRxObservable; +import static com.hotels.styx.api.cookies.ResponseCookie.cookie; import static com.hotels.styx.api.messages.HttpResponseStatus.OK; import static io.netty.buffer.Unpooled.copiedBuffer; import static io.netty.handler.codec.http.LastHttpContent.EMPTY_LAST_CONTENT; @@ -328,7 +328,7 @@ protected void channelRead0(ChannelHandlerContext ctx, HttpResponse response) th } ); - HttpResponse.Builder response = response(OK).addCookie(cookie(",,,,", ",,,,")); + HttpResponse.Builder response = response(OK).cookies(cookie(",,,,", ",,,,")); ch.writeInbound(response.body(fromRxObservable(contentObservable.doOnUnsubscribe(() -> unsubscribed.set(true)))).build()); assertThat(channelRead.get(), is(true)); } diff --git a/components/server/src/test/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslatorTest.java b/components/server/src/test/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslatorTest.java index ecc3bea946..61d7cd61d9 100644 --- a/components/server/src/test/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslatorTest.java +++ b/components/server/src/test/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslatorTest.java @@ -21,12 +21,12 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import static com.hotels.styx.api.HttpCookie.cookie; import static com.hotels.styx.api.HttpCookieAttribute.domain; import static com.hotels.styx.api.HttpCookieAttribute.httpOnly; import static com.hotels.styx.api.HttpCookieAttribute.maxAge; import static com.hotels.styx.api.HttpCookieAttribute.path; import static com.hotels.styx.api.HttpCookieAttribute.secure; +import static com.hotels.styx.api.cookies.ResponseCookie.cookie; import static com.hotels.styx.api.messages.HttpResponseStatus.OK; import static com.hotels.styx.api.messages.HttpVersion.HTTP_1_1; import static java.util.Collections.singleton; @@ -59,7 +59,7 @@ public void shouldCreateNettyResponseWithHostHeader() { @Test(dataProvider = "attributes") public void shouldCreateNettyResponseWithCookieWithAttributes(HttpCookieAttribute attribute, String attributeString) { HttpResponse styxResponse = new HttpResponse.Builder(OK) - .addCookie(cookie("cookie-test", "cookie-value", singleton(attribute))) + .cookies(cookie("cookie-test", "cookie-value", singleton(attribute))) .build(); io.netty.handler.codec.http.HttpResponse nettyResponse = translator.toNettyResponse(styxResponse); assertTrue(nettyResponse.headers().containsValue("Set-Cookie", "cookie-test=cookie-value; " + attributeString, @@ -79,7 +79,7 @@ public static Object[][] attributes() { @Test() public void shouldCreateNettyResponseWithCookieWithMaxAge() { HttpResponse styxResponse = new HttpResponse.Builder(OK) - .addCookie(cookie("cookie-test", "cookie-value", singleton(maxAge(1)))) + .cookies(cookie("cookie-test", "cookie-value", singleton(maxAge(1)))) .build(); io.netty.handler.codec.http.HttpResponse nettyResponse = translator.toNettyResponse(styxResponse); assertTrue(nettyResponse.headers().get("Set-Cookie").startsWith("cookie-test=cookie-value; Max-Age=1; Expires=")); diff --git a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala index ee476d3f15..a2fd533d40 100644 --- a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala +++ b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala @@ -21,7 +21,7 @@ import com.github.tomakehurst.wiremock.client.WireMock._ import com.hotels.styx.api.FullHttpRequest.get import com.hotels.styx.api.HttpCookieAttribute.{domain, httpOnly, path} import com.hotels.styx.api.HttpHeaderNames.X_FORWARDED_FOR -import com.hotels.styx.api.cookies.RequestCookie +import com.hotels.styx.api.cookies.{RequestCookie, ResponseCookie} import com.hotels.styx.api.cookies.RequestCookie.cookie import com.hotels.styx.api.messages.HttpResponseStatus._ import com.hotels.styx.api.{HttpCookie, HttpHeaderValues} @@ -398,7 +398,7 @@ class HeadersSpec extends FunSpec .withHeader("Cookie", equalTo("test-cookie=\"hu_hotels_com,HCOM_HU,hu_HU,\"")) .withHeader("Host", equalTo(styxServer.proxyHost))) - assert(resp.cookie("test-cookie").get == HttpCookie.cookie("test-cookie", "\"hu_hotels_com,HCOM_HU,hu_HU,\"", domain(".example.com"), path("/"))) + assert(resp.cookie("test-cookie").get == ResponseCookie.cookie("test-cookie", "\"hu_hotels_com,HCOM_HU,hu_HU,\"", domain(".example.com"), path("/"))) } it("should handle http only cookies") { @@ -417,8 +417,8 @@ class HeadersSpec extends FunSpec val resp = decodedRequest(req) assert(resp.cookies().size() == 2) - assert(resp.cookie("abc").get == HttpCookie.cookie("abc", "1", domain(".example.com"), path("/"))) - assert(resp.cookie("SESSID").get == HttpCookie.cookie("SESSID", "sessid", domain(".example.com"), path("/"), httpOnly())) + assert(resp.cookie("abc").get == ResponseCookie.cookie("abc", "1", domain(".example.com"), path("/"))) + assert(resp.cookie("SESSID").get == ResponseCookie.cookie("SESSID", "sessid", domain(".example.com"), path("/"), httpOnly())) } } From b7037926a7b9f9eca4a47c0c1b85af7ebb4a200a Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Tue, 17 Jul 2018 17:30:06 +0100 Subject: [PATCH 06/26] Remove old cookie class --- .../com/hotels/styx/api/FullHttpRequest.java | 7 - .../java/com/hotels/styx/api/HttpCookie.java | 143 ------------------ .../styx/api/support/CookiesSupport.java | 57 ------- .../hotels/styx/api/FullHttpRequestTest.java | 10 -- .../com/hotels/styx/api/HttpCookieTest.java | 82 ---------- .../styx/api/support/CookiesSupportTest.java | 58 ------- .../connectionpool/HttpRequestOperation.java | 14 -- .../com/hotels/styx/proxy/HeadersSpec.scala | 4 +- 8 files changed, 2 insertions(+), 373 deletions(-) delete mode 100644 components/api/src/main/java/com/hotels/styx/api/HttpCookie.java delete mode 100644 components/api/src/main/java/com/hotels/styx/api/support/CookiesSupport.java delete mode 100644 components/api/src/test/java/com/hotels/styx/api/HttpCookieTest.java delete mode 100644 components/api/src/test/java/com/hotels/styx/api/support/CookiesSupportTest.java diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java index 368ed64eb2..be8c362c74 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java @@ -42,7 +42,6 @@ import static com.hotels.styx.api.messages.HttpMethod.POST; import static com.hotels.styx.api.messages.HttpMethod.PUT; import static com.hotels.styx.api.messages.HttpVersion.HTTP_1_1; -import static com.hotels.styx.api.support.CookiesSupport.isCookieHeader; import static java.lang.Integer.parseInt; import static java.net.InetSocketAddress.createUnresolved; import static java.util.Arrays.asList; @@ -463,7 +462,6 @@ public Builder id(Object id) { * @return {@code this} */ public Builder header(CharSequence name, Object value) { - checkNotCookie(name); this.headers.set(name, value); return this; } @@ -489,7 +487,6 @@ public Builder headers(HttpHeaders headers) { * @return {@code this} */ public Builder addHeader(CharSequence name, Object value) { - checkNotCookie(name); this.headers.add(name, value); return this; } @@ -607,10 +604,6 @@ private void ensureMethodIsValid() { checkArgument(isMethodValid(), "Unrecognised HTTP method=%s", this.method); } - private static void checkNotCookie(CharSequence name) { - checkArgument(!isCookieHeader(name.toString()), "Cookies must be set with addCookie method"); - } - private boolean isMethodValid() { return METHODS.contains(this.method); } diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpCookie.java b/components/api/src/main/java/com/hotels/styx/api/HttpCookie.java deleted file mode 100644 index 8047bbe70f..0000000000 --- a/components/api/src/main/java/com/hotels/styx/api/HttpCookie.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - Copyright (C) 2013-2018 Expedia Inc. - - 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 com.hotels.styx.api; - -import com.google.common.base.Joiner; -import com.google.common.base.Objects; - -import java.util.Collection; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Strings.isNullOrEmpty; -import static com.google.common.collect.Iterables.isEmpty; -import static com.google.common.collect.Sets.newHashSet; - -/** - * An HttpCookie represents an HTTP cookie as sent in the {@code Set-Cookie} header of an - * HTTP response or the {@code Cookie} header of an HTTP request. - */ -public final class HttpCookie { - private static final Joiner JOINER_ON_SEMI_COLON_AND_SPACE = Joiner.on("; "); - private final String name; - private final String value; - private final int hashCode; - private final Iterable attributes; - - /** - * Constructs a cookie with a name, value and attributes. - * - * @param name cookie name - * @param value cookie value - * @param attributes cookie attributes - */ - private HttpCookie(String name, String value, Iterable attributes) { - checkArgument(!isNullOrEmpty(name), "name cannot be null or empty"); - checkNotNull(value, "value cannot be null"); - this.name = name; - this.value = value; - this.attributes = checkNotNull(attributes); - this.hashCode = Objects.hashCode(name, value, attributes); - } - - /** - * Constructs a cookie with a name, value and attributes. - * - * @param name cookie name - * @param value cookie value - * @param attributes cookie attributes - * @return a cookie - */ - public static HttpCookie cookie(String name, String value, HttpCookieAttribute... attributes) { - return new HttpCookie(name, value, nonNulls(attributes)); - } - - // throws exception if any values are null - private static Collection nonNulls(X... array) { - for (X item : array) { - checkNotNull(item); - } - - return newHashSet(array); - } - - /** - * Constructs a cookie with a name, value and attributes. - * - * @param name cookie name - * @param value cookie value - * @param attributes cookie attributes - * @return a cookie - */ - public static HttpCookie cookie(String name, String value, Iterable attributes) { - return new HttpCookie(name, value, newHashSet(attributes)); - } - - /** - * Returns cookie name. - * - * @return cookie name - */ - public String name() { - return name; - } - - /** - * Returns cookie value. - * - * @return cookie value - */ - public String value() { - return value; - } - - /** - * Returns cookie attributes. - * - * @return cookie attributes - */ - public Iterable attributes() { - return attributes; - } - - @Override - public int hashCode() { - return hashCode; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - HttpCookie other = (HttpCookie) obj; - return Objects.equal(name, other.name) && Objects.equal(value, other.value) && Objects.equal(attributes, other.attributes); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder() - .append(name) - .append('=') - .append(value) - .append(isEmpty(attributes) ? "" : "; "); - - return JOINER_ON_SEMI_COLON_AND_SPACE.appendTo(builder, attributes).toString(); - } -} diff --git a/components/api/src/main/java/com/hotels/styx/api/support/CookiesSupport.java b/components/api/src/main/java/com/hotels/styx/api/support/CookiesSupport.java deleted file mode 100644 index d1869919b9..0000000000 --- a/components/api/src/main/java/com/hotels/styx/api/support/CookiesSupport.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright (C) 2013-2018 Expedia Inc. - - 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 com.hotels.styx.api.support; - -import com.hotels.styx.api.HttpCookie; - -import java.util.Collection; -import java.util.Optional; - -import static java.util.Objects.requireNonNull; - -/** - * Does some validation for cookie header names. - */ -public final class CookiesSupport { - private CookiesSupport() { - } - - /** - * Determine whether a header is a cookie header, i.e. server cookie ("Set-Cookie") or client cookie ("Cookie") - * - * @param header header name - * @return if the header is a cookie header - */ - public static boolean isCookieHeader(String header) { - return "Set-Cookie".equalsIgnoreCase(header) || "Cookie".equalsIgnoreCase(header); - } - - /** - * Find a cookie with the specified {@code name}. - * - * @param cookies list of cookies - * @param name cookie name - * @return the cookie if present - */ - public static Optional findCookie(Collection cookies, String name){ - requireNonNull(cookies); - requireNonNull(name); - return cookies - .stream() - .filter(cookie -> name.equals(cookie.name())) - .findFirst(); - } -} diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java index bec9d3acf3..0f9da65de4 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java @@ -397,16 +397,6 @@ public void canRemoveAHeader() { assertThat(shouldRemoveHeader.headers(), contains(header("a", "b"))); } - @Test(expectedExceptions = IllegalArgumentException.class, dataProvider = "cookieHeaderName") - public void willNotAllowCookieHeaderToBeSet(CharSequence cookieHeaderName) { - get("/").header(cookieHeaderName, "Value"); - } - - @Test(expectedExceptions = IllegalArgumentException.class, dataProvider = "cookieHeaderName") - public void willNotAllowCookieHeaderToBeAdded(CharSequence cookieHeaderName) { - get("/").addHeader(cookieHeaderName, "Value"); - } - @DataProvider(name = "cookieHeaderName") private Object[][] cookieHeaderName() { return new Object[][]{ diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpCookieTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpCookieTest.java deleted file mode 100644 index 6ba98bb427..0000000000 --- a/components/api/src/test/java/com/hotels/styx/api/HttpCookieTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - Copyright (C) 2013-2018 Expedia Inc. - - 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 com.hotels.styx.api; - -import org.testng.annotations.Test; - -import static com.hotels.styx.api.HttpCookie.cookie; -import static com.hotels.styx.api.HttpCookieAttribute.domain; -import static com.hotels.styx.api.HttpCookieAttribute.maxAge; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; - -public class HttpCookieTest { - - @Test(expectedExceptions = IllegalArgumentException.class) - public void acceptsOnlyNonEmptyName() { - cookie("", "value", domain(".hotels.com")); - } - - @Test(expectedExceptions = IllegalArgumentException.class) - public void acceptsOnlyNonNullName() { - cookie(null, "value", domain(".hotels.com")); - } - - @Test(expectedExceptions = NullPointerException.class) - public void acceptsOnlyNonNullValue() { - cookie("name", null, domain(".hotels.com")); - } - - @Test - public void createsCookieWithOneAttribute() { - HttpCookie cookie = cookie("name", "value", domain(".hotels.com")); - assertThat(cookie.toString(), is("name=value; Domain=.hotels.com")); - assertThat(cookie.attributes(), contains(domain(".hotels.com"))); - } - - @Test - public void createsCookieWithMultipleAttribute() { - HttpCookie cookie = cookie("name", "value", domain(".hotels.com"), maxAge(4000)); - assertThat(cookie.toString(), is("name=value; Domain=.hotels.com; Max-Age=4000")); - assertThat(cookie.attributes(), containsInAnyOrder(domain(".hotels.com"), maxAge(4000))); - } - - @Test - public void equalsBehavesCorrectly() { - HttpCookie base = cookie("name", "value", domain(".hotels.com")); - HttpCookie same = cookie("name", "value", domain(".hotels.com")); - HttpCookie different = cookie("name", "value", domain(".hotels.com"), maxAge(4000)); - - assertThat(base.equals(null), is(false)); - assertThat(base.equals(base), is(true)); - assertThat(base.equals(same), is(true)); - assertThat(base.equals(different), is(false)); - } - - @Test(enabled = false) - public void overwritesDuplicateAttributeTypes() { - HttpCookie cookie = cookie("name", "value", maxAge(1234), maxAge(2345)); - - assertThat(cookie.attributes(), contains(maxAge(2345))); - } - - @Test(expectedExceptions = NullPointerException.class) - public void attributesCannotBeNull() { - cookie("name", "value", domain(".hotels.com"), null, maxAge(4000)); - } -} diff --git a/components/api/src/test/java/com/hotels/styx/api/support/CookiesSupportTest.java b/components/api/src/test/java/com/hotels/styx/api/support/CookiesSupportTest.java deleted file mode 100644 index 0c3867f877..0000000000 --- a/components/api/src/test/java/com/hotels/styx/api/support/CookiesSupportTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright (C) 2013-2018 Expedia Inc. - - 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 com.hotels.styx.api.support; - -import com.google.common.collect.ImmutableList; -import com.hotels.styx.api.HttpCookie; -import com.hotels.styx.api.HttpCookieAttribute; -import org.testng.annotations.Test; - -import java.util.List; -import java.util.Optional; - -import static com.hotels.styx.api.support.CookiesSupport.findCookie; -import static com.hotels.styx.api.support.CookiesSupport.isCookieHeader; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; - -public class CookiesSupportTest { - - @Test - public void onlyRequestAndResponseCookieHeaderIsAllowed() { - assertThat(isCookieHeader("Set-Cookie"), is(true)); - assertThat(isCookieHeader("set-cookie"), is(true)); - assertThat(isCookieHeader("cookie"), is(true)); - assertThat(isCookieHeader("Cookie"), is(true)); - - assertThat(isCookieHeader(""), is(false)); - assertThat(isCookieHeader("Cooksie"), is(false)); - assertThat(isCookieHeader("cookiex"), is(false)); - } - - @Test - public void canFindCookieName(){ - HttpCookie expectedCookie = HttpCookie.cookie("cookie1", "value1", HttpCookieAttribute.httpOnly()); - List cookies = ImmutableList.of(expectedCookie); - assertThat(findCookie(cookies, expectedCookie.name()),is(Optional.of(expectedCookie))); - } - - @Test - public void cookieNamesAreCaseSensitive(){ - HttpCookie expectedCookie = HttpCookie.cookie("cookie1", "value1", HttpCookieAttribute.httpOnly()); - List cookies = ImmutableList.of(expectedCookie); - assertThat(findCookie(cookies, expectedCookie.name().toUpperCase()),is(Optional.empty())); - } -} \ No newline at end of file diff --git a/components/client/src/main/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperation.java b/components/client/src/main/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperation.java index 421fb99374..64ed267b21 100644 --- a/components/client/src/main/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperation.java +++ b/components/client/src/main/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperation.java @@ -16,7 +16,6 @@ package com.hotels.styx.client.netty.connectionpool; import com.google.common.annotations.VisibleForTesting; -import com.hotels.styx.api.HttpCookie; import com.hotels.styx.api.HttpRequest; import com.hotels.styx.api.HttpResponse; import com.hotels.styx.api.client.Origin; @@ -34,7 +33,6 @@ import io.netty.handler.codec.http.DefaultHttpContent; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.cookie.DefaultCookie; import io.netty.handler.timeout.IdleStateHandler; import org.slf4j.Logger; import rx.Observable; @@ -102,10 +100,6 @@ public HttpRequestOperation(HttpRequest request, OriginStatsFactory originStatsF this.httpRequestMessageLogger = new HttpRequestMessageLogger("com.hotels.styx.http-messages.outbound", longFormat); } - private static DefaultCookie styxCookieToNettyCookie(HttpCookie cookie) { - return new DefaultCookie(cookie.name(), cookie.value()); - } - @VisibleForTesting static DefaultHttpRequest toNettyRequest(HttpRequest request) { HttpVersion version = request.version(); @@ -116,14 +110,6 @@ static DefaultHttpRequest toNettyRequest(HttpRequest request) { request.headers().forEach((name, value) -> nettyRequest.headers().add(name, value)); -// Cookie[] cookies = request.cookies().stream() -// .map(HttpRequestOperation::styxCookieToNettyCookie) -// .toArray(Cookie[]::new); -// -// if (cookies.length > 0) { -// nettyRequest.headers().set(COOKIE, ClientCookieEncoder.LAX.encode(cookies)); -// } - return nettyRequest; } diff --git a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala index a2fd533d40..293596fbf0 100644 --- a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala +++ b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala @@ -21,10 +21,10 @@ import com.github.tomakehurst.wiremock.client.WireMock._ import com.hotels.styx.api.FullHttpRequest.get import com.hotels.styx.api.HttpCookieAttribute.{domain, httpOnly, path} import com.hotels.styx.api.HttpHeaderNames.X_FORWARDED_FOR -import com.hotels.styx.api.cookies.{RequestCookie, ResponseCookie} +import com.hotels.styx.api.HttpHeaderValues import com.hotels.styx.api.cookies.RequestCookie.cookie +import com.hotels.styx.api.cookies.ResponseCookie import com.hotels.styx.api.messages.HttpResponseStatus._ -import com.hotels.styx.api.{HttpCookie, HttpHeaderValues} import com.hotels.styx.support.NettyOrigins import com.hotels.styx.support.backends.FakeHttpServer import com.hotels.styx.support.configuration.{HttpBackend, Origins} From c4f8e37c903e55c24ee84791bd4903b7d11e5bae Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Wed, 18 Jul 2018 11:44:10 +0100 Subject: [PATCH 07/26] Remove HttpCookieAttribute class --- .../hotels/styx/api/HttpCookieAttribute.java | 133 ------------- .../styx/api/cookies/ResponseCookie.java | 177 +++++------------- .../hotels/styx/api/FullHttpResponseTest.java | 8 +- .../styx/api/HttpCookieAttributeTest.java | 64 ------- .../com/hotels/styx/api/HttpResponseTest.java | 9 +- .../styx/api/cookies/ResponseCookieTest.java | 34 +--- .../stickysession/StickySessionCookie.java | 17 +- .../netty/HttpRequestMessageLoggerTest.java | 6 +- .../NettyToStyxResponsePropagatorTest.java | 17 +- .../logging/HttpRequestMessageLogger.java | 11 +- .../HttpMessageLoggingInterceptorTest.java | 8 +- .../StyxToNettyResponseTranslatorTest.java | 59 +++--- .../com/hotels/styx/proxy/HeadersSpec.scala | 9 +- .../styx/proxy/HttpMessageLoggingSpec.scala | 8 +- .../HttpOutboundMessageLoggingSpec.scala | 4 +- 15 files changed, 124 insertions(+), 440 deletions(-) delete mode 100644 components/api/src/main/java/com/hotels/styx/api/HttpCookieAttribute.java delete mode 100644 components/api/src/test/java/com/hotels/styx/api/HttpCookieAttributeTest.java diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpCookieAttribute.java b/components/api/src/main/java/com/hotels/styx/api/HttpCookieAttribute.java deleted file mode 100644 index 89d7922d9d..0000000000 --- a/components/api/src/main/java/com/hotels/styx/api/HttpCookieAttribute.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - Copyright (C) 2013-2018 Expedia Inc. - - 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 com.hotels.styx.api; - -import com.google.common.base.Objects; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * Represents an HTTP cookie attribute. Instances of this class are immutable and can only be created using static factory methods. - */ -public final class HttpCookieAttribute { - private static final HttpCookieAttribute SECURE = new HttpCookieAttribute("Secure", null); - private static final HttpCookieAttribute HTTPONLY = new HttpCookieAttribute("HttpOnly", null); - private static final String DOMAIN = "Domain"; - private static final String PATH = "Path"; - private static final String MAX_AGE = "Max-Age"; - - private final String name; - private final String value; - private final int hashCode; - - private HttpCookieAttribute(String name, String value) { - this.name = checkNotNull(name); - this.value = value; - this.hashCode = Objects.hashCode(name, value); - } - - /** - * Creates a Domain attribute. - * - * @param domain domain value - * @return created attribute - */ - public static HttpCookieAttribute domain(String domain) { - return new HttpCookieAttribute(DOMAIN, domain); - } - - /** - * Creates a Path attribute. - * - * @param path path value - * @return created attribute - */ - public static HttpCookieAttribute path(String path) { - return new HttpCookieAttribute(PATH, checkNotNull(path)); - } - - /** - * Creates an Max-Age attribute. - * - * @param maxAge Max-Age value - * @return created attribute - */ - public static HttpCookieAttribute maxAge(int maxAge) { - return new HttpCookieAttribute(MAX_AGE, Integer.toString(maxAge)); - } - - /** - * Creates an Secure attribute. - * - * @return created attribute - */ - public static HttpCookieAttribute secure() { - return SECURE; - } - - /** - * Creates an HttpOnly attribute. - * - * @return created attribute - */ - public static HttpCookieAttribute httpOnly() { - return HTTPONLY; - } - - /** - * Attribute name, e.g. Domain, Path. - * - * @return name - */ - public String name() { - return name; - } - - /** - * Attribute value if applicable. Attributes that do not have values, such as Secure will return null instead. - * - * @return value - */ - public String value() { - return value; - } - - @Override - public int hashCode() { - return hashCode; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - HttpCookieAttribute other = (HttpCookieAttribute) obj; - return Objects.equal(name, other.name) && Objects.equal(value, other.value); - } - - @Override - public String toString() { - if (value == null) { - return name; - } - - return name + "=" + value; - } -} diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java index bee71c3815..332a7dcc2a 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java @@ -16,28 +16,22 @@ package com.hotels.styx.api.cookies; import com.google.common.base.Joiner; -import com.google.common.base.Objects; -import com.hotels.styx.api.HttpCookieAttribute; import com.hotels.styx.api.HttpHeaders; import com.hotels.styx.api.HttpResponse; import io.netty.handler.codec.http.cookie.ClientCookieDecoder; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.codec.http.cookie.DefaultCookie; -import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; +import java.util.Objects; import java.util.Optional; import java.util.Set; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Strings.isNullOrEmpty; -import static com.google.common.collect.Iterables.isEmpty; -import static com.google.common.collect.Sets.newHashSet; +import static com.google.common.base.Objects.toStringHelper; import static com.hotels.styx.api.HttpHeaderNames.SET_COOKIE; import static com.hotels.styx.api.common.Strings.quote; import static io.netty.handler.codec.http.cookie.Cookie.UNDEFINED_MAX_AGE; -import static java.lang.Long.parseLong; +import static java.util.Objects.hash; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toSet; @@ -71,62 +65,22 @@ private ResponseCookie(Builder builder) { this.path = builder.path; this.httpOnly = builder.httpOnly; this.secure = builder.secure; - this.hashCode = Objects.hashCode(name, value, domain, maxAge, path, secure, httpOnly); + this.hashCode = hash(name, value, domain, maxAge, path, secure, httpOnly); } /** * Constructs a cookie with a name, value and attributes. * - * @param name cookie name - * @param value cookie value - * @param attributes cookie attributes + * @param name cookie name + * @param value cookie value * @return a cookie */ - public static ResponseCookie cookie(String name, String value, HttpCookieAttribute... attributes) { - return cookie(name, value, nonNulls(attributes)); + public static ResponseCookie cookie(String name, String value) { + return ResponseCookie.responseCookie(name, value).build(); } - // throws exception if any values are null - private static Set nonNulls(X... array) { - for (X item : array) { - checkNotNull(item); - } - - return newHashSet(array); - } - - /** - * Constructs a cookie with a name, value and attributes. - * - * @param name cookie name - * @param value cookie value - * @param attributes cookie attributes - * @return a cookie - */ - public static ResponseCookie cookie(String name, String value, Iterable attributes) { - ResponseCookie.Builder builder = new ResponseCookie.Builder(name, value); - - attributes.forEach(attribute -> { - switch (attribute.name()) { - case "Domain": - builder.domain(attribute.value()); - break; - case "Path": - builder.path(attribute.value()); - break; - case "Max-Age": - builder.maxAge(parseLong(attribute.value())); - break; - case "Secure": - builder.secure(true); - break; - case "HttpOnly": - builder.httpOnly(true); - break; - } - }); - - return builder.build(); + public static ResponseCookie.Builder responseCookie(String name, String value) { + return new ResponseCookie.Builder(name, value); } public static PseudoMap decode(HttpHeaders headers) { @@ -167,27 +121,15 @@ private static Cookie convert(ResponseCookie cookie) { } private static ResponseCookie convert(Cookie cookie) { - Iterable attributes = new ArrayList() { - { - if (!isNullOrEmpty(cookie.domain())) { - add(HttpCookieAttribute.domain(cookie.domain())); - } - if (cookie.maxAge() != Long.MIN_VALUE) { - add(HttpCookieAttribute.maxAge((int) cookie.maxAge())); - } - if (!isNullOrEmpty(cookie.path())) { - add(HttpCookieAttribute.path(cookie.path())); - } - if (cookie.isHttpOnly()) { - add(HttpCookieAttribute.httpOnly()); - } - if (cookie.isSecure()) { - add(HttpCookieAttribute.secure()); - } - } - }; String value = cookie.wrap() ? quote(cookie.value()) : cookie.value(); - return cookie(cookie.name(), value, attributes); + + return responseCookie(cookie.name(), value) + .domain(cookie.domain()) + .path(cookie.path()) + .maxAge(cookie.maxAge()) + .httpOnly(cookie.isHttpOnly()) + .secure(cookie.isSecure()) + .build(); } /** @@ -208,65 +150,42 @@ public String value() { return value; } - /** - * Returns cookie attributes. - * - * @return cookie attributes - */ - // TODO delete after clean-up - public Set attributes() { - Set attributes = new HashSet<>(); - - if (!isNullOrEmpty(domain)) { - attributes.add(HttpCookieAttribute.domain(domain)); - } - - if (!isNullOrEmpty(path)) { - attributes.add(HttpCookieAttribute.path(path)); - } - - if (maxAge != null && maxAge != UNDEFINED_MAX_AGE) { - attributes.add(HttpCookieAttribute.maxAge(maxAge.intValue())); - } - - if (secure) { - attributes.add(HttpCookieAttribute.secure()); + @Override + public boolean equals(Object o) { + if (this == o) { + return true; } - - if (httpOnly) { - attributes.add(HttpCookieAttribute.httpOnly()); + if (o == null || getClass() != o.getClass()) { + return false; } - - return attributes; + ResponseCookie that = (ResponseCookie) o; + return httpOnly == that.httpOnly + && secure == that.secure + && hashCode == that.hashCode + && Objects.equals(name, that.name) + && Objects.equals(value, that.value) + && Objects.equals(domain, that.domain) + && Objects.equals(maxAge, that.maxAge) + && Objects.equals(path, that.path); } - @Override public int hashCode() { - return hashCode; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - ResponseCookie other = (ResponseCookie) obj; - return Objects.equal(name, other.name) && Objects.equal(value, other.value) && Objects.equal(attributes(), other.attributes()); + return hash(name, value, domain, maxAge, path, httpOnly, secure, hashCode); } @Override public String toString() { - StringBuilder builder = new StringBuilder() - .append(name) - .append('=') - .append(value) - .append(isEmpty(attributes()) ? "" : "; "); - - return JOINER_ON_SEMI_COLON_AND_SPACE.appendTo(builder, attributes()).toString(); + return toStringHelper(this) + .add("name", name) + .add("value", value) + .add("domain", domain) + .add("maxAge", maxAge) + .add("path", path) + .add("httpOnly", httpOnly) + .add("secure", secure) + .add("hashCode", hashCode) + .toString(); } public Optional maxAge() { @@ -303,7 +222,7 @@ public static class Builder { private boolean httpOnly; private boolean secure; - public Builder(String name, String value) { + private Builder(String name, String value) { this.name = requireNonNull(name); this.value = requireNonNull(value); } @@ -319,17 +238,17 @@ public Builder value(String value) { } public Builder domain(String domain) { - this.domain = requireNonNull(domain); + this.domain = domain; return this; } public Builder maxAge(long maxAge) { - this.maxAge = maxAge; + this.maxAge = maxAge == UNDEFINED_MAX_AGE ? null : maxAge; return this; } public Builder path(String path) { - this.path = requireNonNull(path); + this.path = path; return this; } diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java index d77e48f9bd..0a583dd62d 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java @@ -28,12 +28,10 @@ import static com.hotels.styx.api.FullHttpRequest.get; import static com.hotels.styx.api.FullHttpResponse.response; -import static com.hotels.styx.api.HttpCookieAttribute.domain; -import static com.hotels.styx.api.HttpCookieAttribute.maxAge; -import static com.hotels.styx.api.HttpCookieAttribute.path; import static com.hotels.styx.api.HttpHeader.header; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; import static com.hotels.styx.api.cookies.ResponseCookie.cookie; +import static com.hotels.styx.api.cookies.ResponseCookie.responseCookie; import static com.hotels.styx.api.matchers.HttpHeadersMatcher.isNotCacheable; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_GATEWAY; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_REQUEST; @@ -113,10 +111,10 @@ public void createsResponseWithMinimalInformation() { @Test public void setsASingleOutboundCookie() { FullHttpResponse response = FullHttpResponse.response() - .cookies(cookie("user", "QSplbl9HX1VL", domain(".hotels.com"), path("/"), maxAge(3600))) + .cookies(responseCookie("user", "QSplbl9HX1VL").domain(".hotels.com").path("/").maxAge(3600).build()) .build(); - assertThat(response.cookie("user"), isValue(cookie("user", "QSplbl9HX1VL", domain(".hotels.com"), path("/"), maxAge(3600)))); + assertThat(response.cookie("user"), isValue(responseCookie("user", "QSplbl9HX1VL").domain(".hotels.com").path("/").maxAge(3600).build())); } @Test diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpCookieAttributeTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpCookieAttributeTest.java deleted file mode 100644 index 87808af3a2..0000000000 --- a/components/api/src/test/java/com/hotels/styx/api/HttpCookieAttributeTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - Copyright (C) 2013-2018 Expedia Inc. - - 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 com.hotels.styx.api; - -import org.testng.annotations.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; - -public class HttpCookieAttributeTest { - @Test - public void createsDomainAttribute() { - HttpCookieAttribute attribute = HttpCookieAttribute.domain("hotels.com"); - - assertThat(attribute.name(), is("Domain")); - assertThat(attribute.value(), is("hotels.com")); - } - - @Test - public void createsPathAttribute() { - HttpCookieAttribute attribute = HttpCookieAttribute.path("/"); - - assertThat(attribute.name(), is("Path")); - assertThat(attribute.value(), is("/")); - } - - @Test - public void createsMaxAgeAttribute() { - HttpCookieAttribute attribute = HttpCookieAttribute.maxAge(1234); - - assertThat(attribute.name(), is("Max-Age")); - assertThat(attribute.value(), is("1234")); - } - - @Test - public void createsSecureAttribute() { - HttpCookieAttribute attribute = HttpCookieAttribute.secure(); - - assertThat(attribute.name(), is("Secure")); - assertThat(attribute.value(), is(nullValue())); - } - - @Test - public void createsHttpOnlyAttribute() { - HttpCookieAttribute attribute = HttpCookieAttribute.httpOnly(); - - assertThat(attribute.name(), is("HttpOnly")); - assertThat(attribute.value(), is(nullValue())); - } -} diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java index b63c8e2f98..c724bd476c 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java @@ -27,12 +27,10 @@ import java.util.concurrent.TimeoutException; import java.util.stream.Stream; -import static com.hotels.styx.api.HttpCookieAttribute.domain; -import static com.hotels.styx.api.HttpCookieAttribute.maxAge; -import static com.hotels.styx.api.HttpCookieAttribute.path; import static com.hotels.styx.api.HttpHeader.header; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; import static com.hotels.styx.api.cookies.ResponseCookie.cookie; +import static com.hotels.styx.api.cookies.ResponseCookie.responseCookie; import static com.hotels.styx.api.matchers.HttpHeadersMatcher.isNotCacheable; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_GATEWAY; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_REQUEST; @@ -56,7 +54,6 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.emptyIterable; import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; @@ -127,10 +124,10 @@ public void createsResponseWithMinimalInformation() throws Exception { @Test public void setsASingleOutboundCookie() { HttpResponse response = response() - .cookies(cookie("user", "QSplbl9HX1VL", domain(".hotels.com"), path("/"), maxAge(3600))) + .cookies(responseCookie("user", "QSplbl9HX1VL").domain(".hotels.com").path("/").maxAge(3600).build()) .build(); - assertThat(response.cookies().firstMatch("user"), isValue(cookie("user", "QSplbl9HX1VL", domain(".hotels.com"), path("/"), maxAge(3600)))); + assertThat(response.cookie("user"), isValue(responseCookie("user", "QSplbl9HX1VL").domain(".hotels.com").path("/").maxAge(3600).build())); } @Test diff --git a/components/api/src/test/java/com/hotels/styx/api/cookies/ResponseCookieTest.java b/components/api/src/test/java/com/hotels/styx/api/cookies/ResponseCookieTest.java index 8f559c82bf..3da1f04391 100644 --- a/components/api/src/test/java/com/hotels/styx/api/cookies/ResponseCookieTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/cookies/ResponseCookieTest.java @@ -17,47 +17,21 @@ import org.testng.annotations.Test; -import static com.hotels.styx.api.HttpCookieAttribute.domain; -import static com.hotels.styx.api.HttpCookieAttribute.maxAge; -import static com.hotels.styx.api.cookies.ResponseCookie.cookie; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; +import static com.hotels.styx.api.cookies.ResponseCookie.responseCookie; public class ResponseCookieTest { - @Test(expectedExceptions = IllegalArgumentException.class) public void acceptsOnlyNonEmptyName() { - cookie("", "value", domain(".hotels.com")); + responseCookie("", "value").build(); } @Test(expectedExceptions = NullPointerException.class) public void acceptsOnlyNonNullName() { - cookie(null, "value", domain(".hotels.com")); + responseCookie(null, "value").build(); } @Test(expectedExceptions = NullPointerException.class) public void acceptsOnlyNonNullValue() { - cookie("name", null, domain(".hotels.com")); - } - - @Test - public void createsCookieWithOneAttribute() { - ResponseCookie cookie = cookie("name", "value", domain(".hotels.com")); - assertThat(cookie.toString(), is("name=value; Domain=.hotels.com")); - assertThat(cookie.attributes(), contains(domain(".hotels.com"))); - } - - @Test - public void createsCookieWithMultipleAttribute() { - ResponseCookie cookie = cookie("name", "value", domain(".hotels.com"), maxAge(4000)); - assertThat(cookie.toString(), is("name=value; Domain=.hotels.com; Max-Age=4000")); - assertThat(cookie.attributes(), containsInAnyOrder(domain(".hotels.com"), maxAge(4000))); - } - - @Test(expectedExceptions = NullPointerException.class) - public void attributesCannotBeNull() { - cookie("name", "value", domain(".hotels.com"), null, maxAge(4000)); + responseCookie("name", null).build(); } } \ No newline at end of file diff --git a/components/client/src/main/java/com/hotels/styx/client/stickysession/StickySessionCookie.java b/components/client/src/main/java/com/hotels/styx/client/stickysession/StickySessionCookie.java index c0b8e995bd..cdc9233599 100644 --- a/components/client/src/main/java/com/hotels/styx/client/stickysession/StickySessionCookie.java +++ b/components/client/src/main/java/com/hotels/styx/client/stickysession/StickySessionCookie.java @@ -15,15 +15,10 @@ */ package com.hotels.styx.client.stickysession; -import com.google.common.collect.ImmutableList; -import com.hotels.styx.api.HttpCookieAttribute; import com.hotels.styx.api.Id; import com.hotels.styx.api.cookies.ResponseCookie; -import static com.hotels.styx.api.HttpCookieAttribute.httpOnly; -import static com.hotels.styx.api.HttpCookieAttribute.maxAge; -import static com.hotels.styx.api.HttpCookieAttribute.path; -import static com.hotels.styx.api.cookies.ResponseCookie.cookie; +import static com.hotels.styx.api.cookies.ResponseCookie.responseCookie; /** * Provides methods for handling sticky-session cookies used to identify which origin has "stuck". @@ -41,7 +36,11 @@ private StickySessionCookie() { * @return a new cookie */ public static ResponseCookie newStickySessionCookie(Id applicationId, Id originId, int maxAge) { - return cookie(stickySessionCookieName(applicationId), originId.toString(), attributes(maxAge)); + return responseCookie(stickySessionCookieName(applicationId), originId.toString()) + .maxAge(maxAge) + .path("/") + .httpOnly(true) + .build(); } /** @@ -53,8 +52,4 @@ public static ResponseCookie newStickySessionCookie(Id applicationId, Id originI public static String stickySessionCookieName(Id applicationId) { return "styx_origin_" + applicationId; } - - private static Iterable attributes(int maxAge) { - return ImmutableList.of(maxAge(maxAge), path("/"), httpOnly()); - } } diff --git a/components/client/src/test/unit/java/com/hotels/styx/client/netty/HttpRequestMessageLoggerTest.java b/components/client/src/test/unit/java/com/hotels/styx/client/netty/HttpRequestMessageLoggerTest.java index 02dc93790a..0e315c660c 100644 --- a/components/client/src/test/unit/java/com/hotels/styx/client/netty/HttpRequestMessageLoggerTest.java +++ b/components/client/src/test/unit/java/com/hotels/styx/client/netty/HttpRequestMessageLoggerTest.java @@ -16,6 +16,7 @@ package com.hotels.styx.client.netty; import ch.qos.logback.classic.Level; +import com.hotels.styx.api.HttpRequest; import com.hotels.styx.api.HttpResponse; import com.hotels.styx.api.Id; import com.hotels.styx.api.client.Origin; @@ -25,7 +26,6 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import com.hotels.styx.api.HttpRequest; import static ch.qos.logback.classic.Level.INFO; import static ch.qos.logback.classic.Level.WARN; @@ -78,7 +78,7 @@ public void logsClientSideRequestLongFormat() { new HttpRequestMessageLogger("com.hotels.styx.http-messages.outbound", true).logRequest(styxRequest, origin); assertThat(log.lastMessage(), is(loggingEvent(INFO, - format("requestId=%s, request=\\{method=GET, secure=false, uri=%s, origin=\"%s\", headers=\\[Host=www.hotels.com\\], cookies=\\[\\]\\}", + format("requestId=%s, request=\\{method=GET, secure=false, uri=%s, origin=\"%s\", headers=\\[Host=www.hotels.com\\]\\}", styxRequest.id(), styxRequest.url(), origin.hostAsString())))); } @@ -97,7 +97,7 @@ public void logsClientSideResponseDetailsLongFormat() { HttpResponse styxResponse = response(OK).build(); new HttpRequestMessageLogger("com.hotels.styx.http-messages.outbound", true).logResponse(styxRequest, styxResponse); - assertThat(log.lastMessage(), is(loggingEvent(INFO, format("requestId=%s, response=\\{status=200 OK\\, headers=\\[\\], cookies=\\[\\]}", styxRequest.id())))); + assertThat(log.lastMessage(), is(loggingEvent(INFO, format("requestId=%s, response=\\{status=200 OK\\, headers=\\[\\]}", styxRequest.id())))); } @Test diff --git a/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagatorTest.java b/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagatorTest.java index 0a5739b03d..122d32e7ea 100644 --- a/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagatorTest.java +++ b/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagatorTest.java @@ -43,13 +43,10 @@ import java.util.Optional; import static com.google.common.base.Charsets.UTF_8; -import static com.hotels.styx.api.HttpCookieAttribute.domain; -import static com.hotels.styx.api.HttpCookieAttribute.httpOnly; -import static com.hotels.styx.api.HttpCookieAttribute.path; import static com.hotels.styx.api.Id.GENERIC_APP; import static com.hotels.styx.api.StyxInternalObservables.toRxObservable; import static com.hotels.styx.api.client.Origin.newOriginBuilder; -import static com.hotels.styx.api.cookies.ResponseCookie.cookie; +import static com.hotels.styx.api.cookies.ResponseCookie.responseCookie; import static com.hotels.styx.api.support.HostAndPorts.localhost; import static com.hotels.styx.client.netty.connectionpool.NettyToStyxResponsePropagator.toStyxResponse; import static com.hotels.styx.support.matchers.IsOptional.isValue; @@ -247,7 +244,11 @@ public void shouldConvertNettyCookieHeaderToStyxCookies() { assertThat(styxResponse.header("Set-Cookie"), isValue("SESSID=sessId; Domain=.foo.com; Path=/; HttpOnly")); assertThat(styxResponse.cookies().firstMatch("SESSID"), equalTo( - Optional.of(cookie("SESSID", "sessId", domain(".foo.com"), path("/"), httpOnly())))); + Optional.of(responseCookie("SESSID", "sessId") + .domain(".foo.com") + .path("/") + .httpOnly(true) + .build()))); } @Test @@ -258,7 +259,11 @@ public void shouldLeaveIntactQuotedCookieValues() { assertThat(styxResponse.header("Set-Cookie"), isValue("SESSID=\"sessId\"; Domain=.foo.com; Path=/; HttpOnly")); assertThat(styxResponse.cookies().firstMatch("SESSID"), equalTo( - Optional.of(cookie("SESSID", "\"sessId\"", domain(".foo.com"), path("/"), httpOnly())))); + Optional.of(responseCookie("SESSID", "\"sessId\"") + .domain(".foo.com") + .path("/") + .httpOnly(true) + .build()))); } @Test diff --git a/components/common/src/main/java/com/hotels/styx/common/logging/HttpRequestMessageLogger.java b/components/common/src/main/java/com/hotels/styx/common/logging/HttpRequestMessageLogger.java index b70c4a5196..a312bea469 100644 --- a/components/common/src/main/java/com/hotels/styx/common/logging/HttpRequestMessageLogger.java +++ b/components/common/src/main/java/com/hotels/styx/common/logging/HttpRequestMessageLogger.java @@ -55,11 +55,10 @@ private static Object id(HttpRequest request) { } private static Info information(HttpResponse response, boolean longFormatEnabled) { - Info info = new Info() - .add("status", response.status()); + Info info = new Info().add("status", response.status()); + if (longFormatEnabled) { - info.add("headers", response.headers()) - .add("cookies", response.cookies()); + info.add("headers", response.headers()); } return info; } @@ -70,9 +69,9 @@ private static Info information(HttpRequest request, Origin origin, boolean long .add("secure", request.isSecure()) .add("uri", request.url()) .add("origin", origin != null ? origin.hostAsString() : "N/A"); + if (longFormatEnabled) { - info.add("headers", request.headers()) - .add("cookies", request.cookies()); + info.add("headers", request.headers()); } return info; } diff --git a/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java b/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java index a67c7da0e8..413b2d10f1 100644 --- a/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java +++ b/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java @@ -64,8 +64,8 @@ public void logsRequestsAndResponses() { .cookies(ResponseCookie.cookie("RespCookie", "RespCookieValue")) ))); - String requestPattern = "request=\\{method=GET, secure=false, uri=/, origin=\"N/A\", headers=\\[ReqHeader=ReqHeaderValue, Cookie=ReqCookie=ReqCookieValue\\], cookies=\\[ReqCookie=ReqCookieValue\\]\\}"; - String responsePattern = "response=\\{status=200 OK, headers=\\[RespHeader=RespHeaderValue\\, Set-Cookie=RespCookie=RespCookieValue], cookies=\\[RespCookie=RespCookieValue\\]\\}"; + String requestPattern = "request=\\{method=GET, secure=false, uri=/, origin=\"N/A\", headers=\\[ReqHeader=ReqHeaderValue, Cookie=ReqCookie=ReqCookieValue\\]\\}"; + String responsePattern = "response=\\{status=200 OK, headers=\\[RespHeader=RespHeaderValue\\, Set-Cookie=RespCookie=RespCookieValue]\\}"; assertThat(responseLogSupport.log(), contains( loggingEvent(INFO, "requestId=" + request.id() + ", " + requestPattern), @@ -104,8 +104,8 @@ public void logsSecureRequests() { consume(interceptor.intercept(request, respondWith(response(OK)))); - String requestPattern = "request=\\{method=GET, secure=true, uri=/, origin=\"N/A\", headers=\\[ReqHeader=ReqHeaderValue, Cookie=ReqCookie=ReqCookieValue\\], cookies=\\[ReqCookie=ReqCookieValue\\]\\}"; - String responsePattern = "response=\\{status=200 OK, headers=\\[\\], cookies=\\[\\]\\}"; + String requestPattern = "request=\\{method=GET, secure=true, uri=/, origin=\"N/A\", headers=\\[ReqHeader=ReqHeaderValue, Cookie=ReqCookie=ReqCookieValue\\]\\}"; + String responsePattern = "response=\\{status=200 OK, headers=\\[\\]\\}"; assertThat(responseLogSupport.log(), contains( loggingEvent(INFO, "requestId=" + request.id() + ", " + requestPattern), diff --git a/components/server/src/test/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslatorTest.java b/components/server/src/test/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslatorTest.java index 61d7cd61d9..1b09a71bab 100644 --- a/components/server/src/test/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslatorTest.java +++ b/components/server/src/test/java/com/hotels/styx/server/netty/connectors/StyxToNettyResponseTranslatorTest.java @@ -15,23 +15,19 @@ */ package com.hotels.styx.server.netty.connectors; -import com.hotels.styx.api.HttpCookieAttribute; import com.hotels.styx.api.HttpResponse; -import io.netty.handler.codec.http.HttpResponseStatus; -import org.testng.annotations.DataProvider; +import com.hotels.styx.api.cookies.ResponseCookie; +import io.netty.handler.codec.http.cookie.ClientCookieDecoder; +import io.netty.handler.codec.http.cookie.Cookie; import org.testng.annotations.Test; -import static com.hotels.styx.api.HttpCookieAttribute.domain; -import static com.hotels.styx.api.HttpCookieAttribute.httpOnly; -import static com.hotels.styx.api.HttpCookieAttribute.maxAge; -import static com.hotels.styx.api.HttpCookieAttribute.path; -import static com.hotels.styx.api.HttpCookieAttribute.secure; -import static com.hotels.styx.api.cookies.ResponseCookie.cookie; +import static com.hotels.styx.api.HttpHeaderNames.SET_COOKIE; +import static com.hotels.styx.api.cookies.ResponseCookie.responseCookie; import static com.hotels.styx.api.messages.HttpResponseStatus.OK; import static com.hotels.styx.api.messages.HttpVersion.HTTP_1_1; -import static java.util.Collections.singleton; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; import static org.testng.Assert.assertTrue; public class StyxToNettyResponseTranslatorTest { @@ -56,32 +52,31 @@ public void shouldCreateNettyResponseWithHostHeader() { assertTrue(nettyResponse.headers().containsValue("Host", "localhost", false)); } - @Test(dataProvider = "attributes") - public void shouldCreateNettyResponseWithCookieWithAttributes(HttpCookieAttribute attribute, String attributeString) { - HttpResponse styxResponse = new HttpResponse.Builder(OK) - .cookies(cookie("cookie-test", "cookie-value", singleton(attribute))) + @Test + public void shouldCreateNettyResponseWithCookieWithAttributes() { + ResponseCookie cookie = responseCookie("cookie-test", "cookie-value") + .domain("cookie-domain") + .path("cookie-path") + .maxAge(1234) + .httpOnly(true) + .secure(true) .build(); - io.netty.handler.codec.http.HttpResponse nettyResponse = translator.toNettyResponse(styxResponse); - assertTrue(nettyResponse.headers().containsValue("Set-Cookie", "cookie-test=cookie-value; " + attributeString, - false)); - } - - @DataProvider - public static Object[][] attributes() { - return new Object[][]{ - { domain("cookie-domain"), domain("cookie-domain").toString()}, - { path("cookie-path"), path("cookie-path").toString()}, - { secure(), secure().toString() }, - { httpOnly(), "HTTPOnly" } - }; - } - @Test() - public void shouldCreateNettyResponseWithCookieWithMaxAge() { HttpResponse styxResponse = new HttpResponse.Builder(OK) - .cookies(cookie("cookie-test", "cookie-value", singleton(maxAge(1)))) + .cookies(cookie) .build(); + io.netty.handler.codec.http.HttpResponse nettyResponse = translator.toNettyResponse(styxResponse); - assertTrue(nettyResponse.headers().get("Set-Cookie").startsWith("cookie-test=cookie-value; Max-Age=1; Expires=")); + + String setCookie = nettyResponse.headers().get(SET_COOKIE); + + Cookie nettyCookie = ClientCookieDecoder.LAX.decode(setCookie); + + assertThat(nettyCookie.name(), is("cookie-test")); + assertThat(nettyCookie.value(), is("cookie-value")); + assertThat(nettyCookie.domain(), is("cookie-domain")); + assertThat(nettyCookie.maxAge(), is(1234L)); + assertThat(nettyCookie.isHttpOnly(), is(true)); + assertThat(nettyCookie.isSecure(), is(true)); } } \ No newline at end of file diff --git a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala index 293596fbf0..c818d79abf 100644 --- a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala +++ b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala @@ -19,11 +19,10 @@ import _root_.io.netty.handler.codec.http.HttpHeaders.Names.{UPGRADE, _} import _root_.io.netty.handler.codec.http.HttpHeaders.Values._ import com.github.tomakehurst.wiremock.client.WireMock._ import com.hotels.styx.api.FullHttpRequest.get -import com.hotels.styx.api.HttpCookieAttribute.{domain, httpOnly, path} import com.hotels.styx.api.HttpHeaderNames.X_FORWARDED_FOR import com.hotels.styx.api.HttpHeaderValues import com.hotels.styx.api.cookies.RequestCookie.cookie -import com.hotels.styx.api.cookies.ResponseCookie +import com.hotels.styx.api.cookies.ResponseCookie.responseCookie import com.hotels.styx.api.messages.HttpResponseStatus._ import com.hotels.styx.support.NettyOrigins import com.hotels.styx.support.backends.FakeHttpServer @@ -398,7 +397,7 @@ class HeadersSpec extends FunSpec .withHeader("Cookie", equalTo("test-cookie=\"hu_hotels_com,HCOM_HU,hu_HU,\"")) .withHeader("Host", equalTo(styxServer.proxyHost))) - assert(resp.cookie("test-cookie").get == ResponseCookie.cookie("test-cookie", "\"hu_hotels_com,HCOM_HU,hu_HU,\"", domain(".example.com"), path("/"))) + assert(resp.cookie("test-cookie").get == responseCookie("test-cookie", "\"hu_hotels_com,HCOM_HU,hu_HU,\"").domain(".example.com").path("/").build()) } it("should handle http only cookies") { @@ -417,8 +416,8 @@ class HeadersSpec extends FunSpec val resp = decodedRequest(req) assert(resp.cookies().size() == 2) - assert(resp.cookie("abc").get == ResponseCookie.cookie("abc", "1", domain(".example.com"), path("/"))) - assert(resp.cookie("SESSID").get == ResponseCookie.cookie("SESSID", "sessid", domain(".example.com"), path("/"), httpOnly())) + assert(resp.cookie("abc").get == responseCookie("abc", "1").domain(".example.com").path("/").build()) + assert(resp.cookie("SESSID").get == responseCookie("SESSID", "sessid").domain(".example.com").path("/").httpOnly(true).build()) } } diff --git a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HttpMessageLoggingSpec.scala b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HttpMessageLoggingSpec.scala index 5383b5887f..614caff350 100644 --- a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HttpMessageLoggingSpec.scala +++ b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HttpMessageLoggingSpec.scala @@ -113,10 +113,10 @@ class HttpMessageLoggingSpec extends FunSpec assertThat(logger.log.size(), is(2)) assertThat(logger.log(), hasItem(loggingEvent(INFO, - "requestId=[-a-z0-9]+, request=\\{method=GET, secure=false, uri=http://localhost:[0-9]+/foobar, origin=\"N/A\", headers=\\[Host=localhost:[0-9]+\\], cookies=\\[\\]}"))) + "requestId=[-a-z0-9]+, request=\\{method=GET, secure=false, uri=http://localhost:[0-9]+/foobar, origin=\"N/A\", headers=\\[Host=localhost:[0-9]+\\]}"))) assertThat(logger.log(), hasItem(loggingEvent(INFO, - "requestId=[-a-z0-9]+, response=\\{status=200 OK, headers=\\[Server=Jetty\\(6.1.26\\), " + ORIGIN_ID_DEFAULT + "=generic-app-01, Via=1.1 styx\\], cookies=\\[\\]\\}"))) + "requestId=[-a-z0-9]+, response=\\{status=200 OK, headers=\\[Server=Jetty\\(6.1.26\\), " + ORIGIN_ID_DEFAULT + "=generic-app-01, Via=1.1 styx\\]\\}"))) } } @@ -131,10 +131,10 @@ class HttpMessageLoggingSpec extends FunSpec assertThat(logger.log.size(), is(2)) assertThat(logger.log(), hasItem(loggingEvent(INFO, - "requestId=[-a-z0-9]+, request=\\{method=GET, secure=true, uri=https://localhost:[0-9]+/foobar, origin=\"N/A\", headers=.*, cookies=\\[\\]}"))) + "requestId=[-a-z0-9]+, request=\\{method=GET, secure=true, uri=https://localhost:[0-9]+/foobar, origin=\"N/A\", headers=.*}"))) assertThat(logger.log(), hasItem(loggingEvent(INFO, - "requestId=[-a-z0-9]+, response=\\{status=200 OK, headers=\\[Server=Jetty\\(6.1.26\\), " + ORIGIN_ID_DEFAULT + "=generic-app-01, Via=1.1 styx\\], cookies=\\[\\]\\}"))) + "requestId=[-a-z0-9]+, response=\\{status=200 OK, headers=\\[Server=Jetty\\(6.1.26\\), " + ORIGIN_ID_DEFAULT + "=generic-app-01, Via=1.1 styx\\]\\}"))) } } } diff --git a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HttpOutboundMessageLoggingSpec.scala b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HttpOutboundMessageLoggingSpec.scala index fb7f4b8a97..e73a4158a1 100644 --- a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HttpOutboundMessageLoggingSpec.scala +++ b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HttpOutboundMessageLoggingSpec.scala @@ -112,10 +112,10 @@ class HttpOutboundMessageLoggingSpec extends FunSpec assertThat(logger.log.size(), is(2)) assertThat(logger.log(), hasItem(loggingEvent(INFO, - "requestId=[-a-z0-9]+, request=\\{method=GET, secure=false, uri=http://localhost:[0-9]+/foobar, origin=\"localhost:[0-9]+\", headers=\\[.*\\], cookies=\\[\\]}"))) + "requestId=[-a-z0-9]+, request=\\{method=GET, secure=false, uri=http://localhost:[0-9]+/foobar, origin=\"localhost:[0-9]+\", headers=\\[.*\\]}"))) assertThat(logger.log(), hasItem(loggingEvent(INFO, - "requestId=[-a-z0-9]+, response=\\{status=200 OK, headers=\\[Transfer-Encoding=chunked, Server=Jetty\\(6.1.26\\)\\], cookies=\\[\\]\\}"))) + "requestId=[-a-z0-9]+, response=\\{status=200 OK, headers=\\[Transfer-Encoding=chunked, Server=Jetty\\(6.1.26\\)\\]\\}"))) } } } From 3777c4dbb112c23fe5c64f554199e2f371b030b2 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Wed, 18 Jul 2018 13:46:50 +0100 Subject: [PATCH 08/26] Renamed RequestCookie.cookie to requestCookie to match naming scheme with ResponseCookie.responseCookie --- .../java/com/hotels/styx/api/HttpRequest.java | 2 +- .../styx/api/cookies/RequestCookie.java | 4 +- .../hotels/styx/api/FullHttpRequestTest.java | 44 +++++++++---------- .../com/hotels/styx/api/HttpRequestTest.java | 42 +++++++++--------- .../styx/api/cookies/RequestCookieTest.java | 8 ++-- .../styx/client/StickySessionSpec.scala | 12 ++--- .../styx/client/StyxHttpClientTest.java | 11 +++-- .../HttpRequestOperationTest.java | 6 +-- .../StyxBackendServiceClientFactoryTest.java | 14 +++--- .../HttpMessageLoggingInterceptorTest.java | 8 ++-- .../codec/NettyToStyxRequestDecoderTest.java | 14 +++--- .../routing/antlr/AntlrConditionTest.java | 14 +++--- .../routing/antlr/FunctionResolverTest.java | 6 +-- .../com/hotels/styx/proxy/HeadersSpec.scala | 4 +- .../styx/proxy/OriginRestrictionSpec.scala | 10 ++--- 15 files changed, 99 insertions(+), 100 deletions(-) diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java index 2bd2632f57..3e5a9d1089 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java @@ -420,7 +420,7 @@ public Builder(HttpRequest request, StyxObservable body) { this.headers = request.headers().newBuilder(); this.body = StyxCoreObservable.of(copiedBuffer(request.body())); RequestCookie.encode(headers, request.cookies().stream() - .map(cookie -> RequestCookie.cookie(cookie.name(), cookie.value())) + .map(cookie -> RequestCookie.requestCookie(cookie.name(), cookie.value())) .collect(toSet())); } diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java index df086d9e70..fdb83df82a 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java @@ -61,7 +61,7 @@ private RequestCookie(String name, String value) { * @param value cookie value * @return a cookie */ - public static RequestCookie cookie(String name, String value) { + public static RequestCookie requestCookie(String name, String value) { return new RequestCookie(name, value); } @@ -95,7 +95,7 @@ private static RequestCookie convert(Cookie nettyCookie) { String name = nettyCookie.name(); String value = nettyCookie.wrap() ? quote(nettyCookie.value()) : nettyCookie.value(); - return cookie(name, value); + return requestCookie(name, value); } /** diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java index 0f9da65de4..f1995f4458 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java @@ -32,7 +32,7 @@ import static com.hotels.styx.api.HttpHeaderNames.COOKIE; import static com.hotels.styx.api.HttpHeaderNames.HOST; import static com.hotels.styx.api.Url.Builder.url; -import static com.hotels.styx.api.cookies.RequestCookie.cookie; +import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; import static com.hotels.styx.api.messages.HttpMethod.DELETE; import static com.hotels.styx.api.messages.HttpMethod.GET; import static com.hotels.styx.api.messages.HttpMethod.POST; @@ -62,7 +62,7 @@ public void convertsToStreamingHttpRequest() throws Exception { .secure(true) .version(HTTP_1_1) .header("HeaderName", "HeaderValue") - .cookies(cookie("CookieName", "CookieValue")) + .cookies(requestCookie("CookieName", "CookieValue")) .build(); HttpRequest streaming = fullRequest.toStreamingRequest(); @@ -75,7 +75,7 @@ public void convertsToStreamingHttpRequest() throws Exception { header("Content-Length", "6"), header("HeaderName", "HeaderValue"), header("Cookie", "CookieName=CookieValue"))); - assertThat(streaming.cookies(), contains(cookie("CookieName", "CookieValue"))); + assertThat(streaming.cookies(), contains(requestCookie("CookieName", "CookieValue"))); String body = streaming.toFullRequest(0x10000) .asCompletableFuture() @@ -139,7 +139,7 @@ public void canUseBuilderToSetRequestProperties() { .version(HTTP_1_1) .id("id") .header("headerName", "a") - .cookies(cookie("cfoo", "bar")) + .cookies(requestCookie("cfoo", "bar")) .build(); assertThat(request.toString(), is("FullHttpRequest{version=HTTP/1.1, method=PATCH, uri=https://hotels.com, " + @@ -284,7 +284,7 @@ public void decodesQueryParamsContainingEncodedEquals() { @Test public void createsRequestBuilderFromRequest() { FullHttpRequest originalRequest = get("/home") - .cookies(cookie("fred", "blogs")) + .cookies(requestCookie("fred", "blogs")) .header("some", "header") .build(); @@ -340,23 +340,23 @@ public void returnsEmptyListWhenThereIsNoSuchParameter() { public void canExtractCookies() { FullHttpRequest request = get("/") .cookies( - cookie("cookie1", "foo"), - cookie("cookie3", "baz"), - cookie("cookie2", "bar")) + requestCookie("cookie1", "foo"), + requestCookie("cookie3", "baz"), + requestCookie("cookie2", "bar")) .build(); - assertThat(request.cookies().firstMatch("cookie1"), isValue(cookie("cookie1", "foo"))); - assertThat(request.cookies().firstMatch("cookie2"), isValue(cookie("cookie2", "bar"))); - assertThat(request.cookies().firstMatch("cookie3"), isValue(cookie("cookie3", "baz"))); + assertThat(request.cookies().firstMatch("cookie1"), isValue(requestCookie("cookie1", "foo"))); + assertThat(request.cookies().firstMatch("cookie2"), isValue(requestCookie("cookie2", "bar"))); + assertThat(request.cookies().firstMatch("cookie3"), isValue(requestCookie("cookie3", "baz"))); } @Test public void cannotExtractNonExistentCookie() { FullHttpRequest request = get("/") .cookies( - cookie("cookie1", "foo"), - cookie("cookie3", "baz"), - cookie("cookie2", "bar")) + requestCookie("cookie1", "foo"), + requestCookie("cookie3", "baz"), + requestCookie("cookie2", "bar")) .build(); assertThat(request.cookies().firstMatch("cookie4"), isAbsent()); @@ -366,15 +366,15 @@ public void cannotExtractNonExistentCookie() { public void extractsAllCookies() { FullHttpRequest request = get("/") .cookies( - cookie("cookie1", "foo"), - cookie("cookie3", "baz"), - cookie("cookie2", "bar")) + requestCookie("cookie1", "foo"), + requestCookie("cookie3", "baz"), + requestCookie("cookie2", "bar")) .build(); assertThat(request.cookies(), containsInAnyOrder( - cookie("cookie1", "foo"), - cookie("cookie2", "bar"), - cookie("cookie3", "baz"))); + requestCookie("cookie1", "foo"), + requestCookie("cookie2", "bar"), + requestCookie("cookie3", "baz"))); } @Test @@ -431,12 +431,12 @@ public void rejectsNullCookie() { @Test(expectedExceptions = IllegalArgumentException.class) public void rejectsNullCookieName() { - get("/").cookies(cookie(null, "value")).build(); + get("/").cookies(requestCookie(null, "value")).build(); } @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookieValue() { - get("/").cookies(cookie("name", null)).build(); + get("/").cookies(requestCookie("name", null)).build(); } @Test(expectedExceptions = IllegalArgumentException.class) diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java index ce7a661ade..23f328183b 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java @@ -33,7 +33,7 @@ import static com.hotels.styx.api.HttpRequest.post; import static com.hotels.styx.api.HttpRequest.put; import static com.hotels.styx.api.Url.Builder.url; -import static com.hotels.styx.api.cookies.RequestCookie.cookie; +import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; import static com.hotels.styx.api.messages.HttpMethod.DELETE; import static com.hotels.styx.api.messages.HttpMethod.GET; import static com.hotels.styx.api.messages.HttpMethod.POST; @@ -62,7 +62,7 @@ public void decodesToFullHttpRequest() throws Exception { .secure(true) .version(HTTP_1_0) .header("HeaderName", "HeaderValue") - .cookies(cookie("CookieName", "CookieValue")) + .cookies(requestCookie("CookieName", "CookieValue")) .build(); FullHttpRequest full = streamingRequest.toFullRequest(0x1000) @@ -143,7 +143,7 @@ public void canUseBuilderToSetRequestProperties() { .version(HTTP_1_0) .id("id") .header("headerName", "a") - .cookies(cookie("cfoo", "bar")) + .cookies(requestCookie("cfoo", "bar")) .build(); assertThat(request.toString(), is("HttpRequest{version=HTTP/1.0, method=PATCH, uri=https://hotels.com, headers=[headerName=a, Cookie=cfoo=bar, Host=hotels.com]," + @@ -192,7 +192,7 @@ public void decodesQueryParamsContainingEncodedEquals() { @Test public void createsRequestBuilderFromRequest() { HttpRequest originalRequest = get("/home") - .cookies(cookie("fred", "blogs")) + .cookies(requestCookie("fred", "blogs")) .header("some", "header") .build(); @@ -248,23 +248,23 @@ public void returnsEmptyListWhenThereIsNoSuchParameter() { public void canExtractCookies() { HttpRequest request = get("/") .cookies( - cookie("cookie1", "foo"), - cookie("cookie3", "baz"), - cookie("cookie2", "bar")) + requestCookie("cookie1", "foo"), + requestCookie("cookie3", "baz"), + requestCookie("cookie2", "bar")) .build(); - assertThat(request.cookies().firstMatch("cookie1"), isValue(cookie("cookie1", "foo"))); - assertThat(request.cookies().firstMatch("cookie2"), isValue(cookie("cookie2", "bar"))); - assertThat(request.cookies().firstMatch("cookie3"), isValue(cookie("cookie3", "baz"))); + assertThat(request.cookies().firstMatch("cookie1"), isValue(requestCookie("cookie1", "foo"))); + assertThat(request.cookies().firstMatch("cookie2"), isValue(requestCookie("cookie2", "bar"))); + assertThat(request.cookies().firstMatch("cookie3"), isValue(requestCookie("cookie3", "baz"))); } @Test public void cannotExtractNonExistentCookie() { HttpRequest request = get("/") .cookies( - cookie("cookie1", "foo"), - cookie("cookie3", "baz"), - cookie("cookie2", "bar")) + requestCookie("cookie1", "foo"), + requestCookie("cookie3", "baz"), + requestCookie("cookie2", "bar")) .build(); assertThat(request.cookies().firstMatch("cookie4"), isAbsent()); @@ -274,15 +274,15 @@ public void cannotExtractNonExistentCookie() { public void extractsAllCookies() { HttpRequest request = get("/") .cookies( - cookie("cookie1", "foo"), - cookie("cookie3", "baz"), - cookie("cookie2", "bar")) + requestCookie("cookie1", "foo"), + requestCookie("cookie3", "baz"), + requestCookie("cookie2", "bar")) .build(); assertThat(request.cookies(), containsInAnyOrder( - cookie("cookie1", "foo"), - cookie("cookie2", "bar"), - cookie("cookie3", "baz"))); + requestCookie("cookie1", "foo"), + requestCookie("cookie2", "bar"), + requestCookie("cookie3", "baz"))); } @Test @@ -331,12 +331,12 @@ public void rejectsNullCookie() { @Test(expectedExceptions = IllegalArgumentException.class) public void rejectsNullCookieName() { - get("/").cookies(cookie(null, "value")).build(); + get("/").cookies(requestCookie(null, "value")).build(); } @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookieValue() { - get("/").cookies(cookie("name", null)).build(); + get("/").cookies(requestCookie("name", null)).build(); } @Test(expectedExceptions = IllegalArgumentException.class) diff --git a/components/api/src/test/java/com/hotels/styx/api/cookies/RequestCookieTest.java b/components/api/src/test/java/com/hotels/styx/api/cookies/RequestCookieTest.java index 82cf9e9a52..a8780314b7 100644 --- a/components/api/src/test/java/com/hotels/styx/api/cookies/RequestCookieTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/cookies/RequestCookieTest.java @@ -17,22 +17,22 @@ import org.testng.annotations.Test; -import static com.hotels.styx.api.cookies.RequestCookie.cookie; +import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; public class RequestCookieTest { @Test(expectedExceptions = IllegalArgumentException.class) public void acceptsOnlyNonEmptyName() { - cookie("", "value"); + requestCookie("", "value"); } @Test(expectedExceptions = IllegalArgumentException.class) public void acceptsOnlyNonNullName() { - cookie(null, "value"); + requestCookie(null, "value"); } @Test(expectedExceptions = NullPointerException.class) public void acceptsOnlyNonNullValue() { - cookie("name", null); + requestCookie("name", null); } } \ No newline at end of file diff --git a/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala b/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala index f96f6a52f1..10a16be948 100644 --- a/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala +++ b/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala @@ -133,7 +133,7 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers .build val request: HttpRequest = get("/") - .cookies(RequestCookie.cookie("styx_origin_app", "app-02")) + .cookies(RequestCookie.requestCookie("styx_origin_app", "app-02")) .build val response1 = waitForResponse(client.sendRequest(request)) @@ -152,9 +152,9 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers val request: HttpRequest = get("/") .cookies( - RequestCookie.cookie("other_cookie1", "foo"), - RequestCookie.cookie("styx_origin_app", "app-02"), - RequestCookie.cookie("other_cookie2", "bar")) + RequestCookie.requestCookie("other_cookie1", "foo"), + RequestCookie.requestCookie("styx_origin_app", "app-02"), + RequestCookie.requestCookie("other_cookie2", "bar")) .build() @@ -173,7 +173,7 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers .build val request: HttpRequest = get("/") - .cookies(RequestCookie.cookie("styx_origin_app", "h3")) + .cookies(RequestCookie.requestCookie("styx_origin_app", "h3")) .build val response = waitForResponse(client.sendRequest(request)) @@ -198,7 +198,7 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers .build val request: HttpRequest = get("/") - .cookies(RequestCookie.cookie("styx_origin_app", "app-02")) + .cookies(RequestCookie.requestCookie("styx_origin_app", "app-02")) .build val response = waitForResponse(client.sendRequest(request)) diff --git a/components/client/src/test/unit/java/com/hotels/styx/client/StyxHttpClientTest.java b/components/client/src/test/unit/java/com/hotels/styx/client/StyxHttpClientTest.java index f74d92b219..f6e332c345 100644 --- a/components/client/src/test/unit/java/com/hotels/styx/client/StyxHttpClientTest.java +++ b/components/client/src/test/unit/java/com/hotels/styx/client/StyxHttpClientTest.java @@ -26,7 +26,6 @@ import com.hotels.styx.api.client.RemoteHost; import com.hotels.styx.api.client.loadbalancing.spi.LoadBalancer; import com.hotels.styx.api.client.retrypolicy.spi.RetryPolicy; -import com.hotels.styx.api.cookies.RequestCookie; import com.hotels.styx.api.metrics.MetricRegistry; import com.hotels.styx.api.metrics.codahale.CodaHaleMetricRegistry; import com.hotels.styx.api.exceptions.NoAvailableHostsException; @@ -53,7 +52,7 @@ import static com.hotels.styx.api.Id.GENERIC_APP; import static com.hotels.styx.api.client.Origin.newOriginBuilder; import static com.hotels.styx.api.client.RemoteHost.remoteHost; -import static com.hotels.styx.api.cookies.RequestCookie.cookie; +import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_REQUEST; import static com.hotels.styx.api.messages.HttpResponseStatus.INTERNAL_SERVER_ERROR; import static com.hotels.styx.api.messages.HttpResponseStatus.NOT_IMPLEMENTED; @@ -390,7 +389,7 @@ public void prefersStickyOrigins() { HttpResponse response = styxHttpClient.sendRequest( get("/foo") - .cookies(cookie("styx_origin_" + Id.GENERIC_APP, "Origin-Y")) + .cookies(requestCookie("styx_origin_" + Id.GENERIC_APP, "Origin-Y")) .build()) .toBlocking() .first(); @@ -417,7 +416,7 @@ public void prefersRestrictedOrigins() { HttpResponse response = styxHttpClient.sendRequest( get("/foo") - .cookies(cookie("restrictedOrigin", "Origin-Y")) + .cookies(requestCookie("restrictedOrigin", "Origin-Y")) .build()) .toBlocking() .first(); @@ -444,8 +443,8 @@ public void prefersRestrictedOriginsOverStickyOriginsWhenBothAreConfigured() { HttpResponse response = styxHttpClient.sendRequest( get("/foo") .cookies( - cookie("restrictedOrigin", "Origin-Y"), - cookie("styx_origin_" + Id.GENERIC_APP, "Origin-X") + requestCookie("restrictedOrigin", "Origin-Y"), + requestCookie("styx_origin_" + Id.GENERIC_APP, "Origin-X") ) .build()) .toBlocking() diff --git a/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperationTest.java b/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperationTest.java index a8a2f1515d..35394b6661 100644 --- a/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperationTest.java +++ b/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperationTest.java @@ -19,7 +19,7 @@ import io.netty.handler.codec.http.DefaultHttpRequest; import org.testng.annotations.Test; -import static com.hotels.styx.api.cookies.RequestCookie.cookie; +import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; import static com.hotels.styx.api.messages.HttpMethod.GET; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.anyOf; @@ -33,8 +33,8 @@ public void shouldTransformStyxRequestToNettyRequestWithAllRelevantInformation() .method(GET) .header("X-Forwarded-Proto", "https") .cookies( - cookie("HASESSION_V3", "asdasdasd"), - cookie("has", "123456789") + requestCookie("HASESSION_V3", "asdasdasd"), + requestCookie("has", "123456789") ) .uri("https://www.example.com/foo%2Cbar?foo,baf=2") .build(); diff --git a/components/proxy/src/test/java/com/hotels/styx/proxy/StyxBackendServiceClientFactoryTest.java b/components/proxy/src/test/java/com/hotels/styx/proxy/StyxBackendServiceClientFactoryTest.java index a307e91447..76b2d2a0cc 100644 --- a/components/proxy/src/test/java/com/hotels/styx/proxy/StyxBackendServiceClientFactoryTest.java +++ b/components/proxy/src/test/java/com/hotels/styx/proxy/StyxBackendServiceClientFactoryTest.java @@ -38,7 +38,7 @@ import static com.hotels.styx.api.Id.GENERIC_APP; import static com.hotels.styx.api.Id.id; import static com.hotels.styx.api.client.Origin.newOriginBuilder; -import static com.hotels.styx.api.cookies.RequestCookie.cookie; +import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; import static com.hotels.styx.api.messages.HttpResponseStatus.OK; import static com.hotels.styx.api.service.BackendService.newBackendServiceBuilder; import static com.hotels.styx.api.service.StickySessionConfig.newStickySessionConfigBuilder; @@ -122,9 +122,9 @@ public void usesTheOriginSpecifiedInTheStickySessionCookie() { .build(), new OriginStatsFactory(new CodaHaleMetricRegistry())); - HttpRequest requestz = get("/some-req").cookies(cookie(STICKY_COOKIE, id("z").toString())).build(); - HttpRequest requestx = get("/some-req").cookies(cookie(STICKY_COOKIE, id("x").toString())).build(); - HttpRequest requesty = get("/some-req").cookies(cookie(STICKY_COOKIE, id("y").toString())).build(); + HttpRequest requestz = get("/some-req").cookies(requestCookie(STICKY_COOKIE, id("z").toString())).build(); + HttpRequest requestx = get("/some-req").cookies(requestCookie(STICKY_COOKIE, id("x").toString())).build(); + HttpRequest requesty = get("/some-req").cookies(requestCookie(STICKY_COOKIE, id("y").toString())).build(); HttpResponse responsez = styxHttpClient.sendRequest(requestz).toBlocking().first(); HttpResponse responsex = styxHttpClient.sendRequest(requestx).toBlocking().first(); @@ -167,9 +167,9 @@ public void usesTheOriginSpecifiedInTheOriginsRestrictionCookie() { .build(), new OriginStatsFactory(new CodaHaleMetricRegistry())); - HttpRequest requestz = get("/some-req").cookies(cookie(ORIGINS_RESTRICTION_COOKIE, id("z").toString())).build(); - HttpRequest requestx = get("/some-req").cookies(cookie(ORIGINS_RESTRICTION_COOKIE, id("x").toString())).build(); - HttpRequest requesty = get("/some-req").cookies(cookie(ORIGINS_RESTRICTION_COOKIE, id("y").toString())).build(); + HttpRequest requestz = get("/some-req").cookies(requestCookie(ORIGINS_RESTRICTION_COOKIE, id("z").toString())).build(); + HttpRequest requestx = get("/some-req").cookies(requestCookie(ORIGINS_RESTRICTION_COOKIE, id("x").toString())).build(); + HttpRequest requesty = get("/some-req").cookies(requestCookie(ORIGINS_RESTRICTION_COOKIE, id("y").toString())).build(); HttpResponse responsez = styxHttpClient.sendRequest(requestz).toBlocking().first(); HttpResponse responsex = styxHttpClient.sendRequest(requestx).toBlocking().first(); diff --git a/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java b/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java index 413b2d10f1..7182837b7f 100644 --- a/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java +++ b/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java @@ -29,7 +29,7 @@ import static ch.qos.logback.classic.Level.INFO; import static com.hotels.styx.api.HttpRequest.get; import static com.hotels.styx.api.HttpResponse.response; -import static com.hotels.styx.api.cookies.RequestCookie.cookie; +import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; import static com.hotels.styx.api.messages.HttpResponseStatus.OK; import static com.hotels.styx.common.StyxFutures.await; import static com.hotels.styx.support.matchers.LoggingEventMatcher.loggingEvent; @@ -55,7 +55,7 @@ public void after() { public void logsRequestsAndResponses() { HttpRequest request = get("/") .header("ReqHeader", "ReqHeaderValue") - .cookies(cookie("ReqCookie", "ReqCookieValue")) + .cookies(requestCookie("ReqCookie", "ReqCookieValue")) .build(); consume(interceptor.intercept(request, respondWith( @@ -77,7 +77,7 @@ public void logsRequestsAndResponsesShort() { interceptor = new HttpMessageLoggingInterceptor(false); HttpRequest request = get("/") .header("ReqHeader", "ReqHeaderValue") - .cookies(cookie("ReqCookie", "ReqCookieValue")) + .cookies(requestCookie("ReqCookie", "ReqCookieValue")) .build(); consume(interceptor.intercept(request, respondWith( @@ -99,7 +99,7 @@ public void logsSecureRequests() { HttpRequest request = get("/") .secure(true) .header("ReqHeader", "ReqHeaderValue") - .cookies(cookie("ReqCookie", "ReqCookieValue")) + .cookies(requestCookie("ReqCookie", "ReqCookieValue")) .build(); consume(interceptor.intercept(request, respondWith(response(OK)))); diff --git a/components/server/src/test/java/com/hotels/styx/server/netty/codec/NettyToStyxRequestDecoderTest.java b/components/server/src/test/java/com/hotels/styx/server/netty/codec/NettyToStyxRequestDecoderTest.java index 6ce84a7f57..40c27668cf 100644 --- a/components/server/src/test/java/com/hotels/styx/server/netty/codec/NettyToStyxRequestDecoderTest.java +++ b/components/server/src/test/java/com/hotels/styx/server/netty/codec/NettyToStyxRequestDecoderTest.java @@ -50,7 +50,7 @@ import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Sets.newHashSet; import static com.hotels.styx.api.StyxInternalObservables.toRxObservable; -import static com.hotels.styx.api.cookies.RequestCookie.cookie; +import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; import static com.hotels.styx.server.UniqueIdSuppliers.fixedUniqueIdSupplier; import static com.hotels.styx.support.netty.HttpMessageSupport.httpMessageToBytes; import static com.hotels.styx.support.netty.HttpMessageSupport.httpRequest; @@ -264,9 +264,9 @@ public void canHandleNettyCookies() { com.hotels.styx.api.HttpRequest expected = new com.hotels.styx.api.HttpRequest.Builder( com.hotels.styx.api.messages.HttpMethod.GET, "http://foo.com/") .cookies( - cookie("ABC01", "\"1\""), - cookie("ABC02", "1"), - cookie("guid", "xxxxx-xxx-xxx-xxx-xxxxxxx") + requestCookie("ABC01", "\"1\""), + requestCookie("ABC02", "1"), + requestCookie("guid", "xxxxx-xxx-xxx-xxx-xxxxxxx") ) .build(); assertThat(newHashSet(styxRequest.cookies()), is(newHashSet(expected.cookies()))); @@ -289,9 +289,9 @@ public void acceptsMalformedCookiesWithRelaxedValidation() { com.hotels.styx.api.HttpRequest expected = new com.hotels.styx.api.HttpRequest.Builder( com.hotels.styx.api.messages.HttpMethod.GET, "http://foo.com/") .cookies( - cookie("ABC01", "\"1\""), - cookie("ABC02", "1"), - cookie("guid", "a,b") + requestCookie("ABC01", "\"1\""), + requestCookie("ABC02", "1"), + requestCookie("guid", "a,b") ) .build(); assertThat(newHashSet(styxRequest.cookies()), is(newHashSet(expected.cookies()))); diff --git a/components/server/src/test/java/com/hotels/styx/server/routing/antlr/AntlrConditionTest.java b/components/server/src/test/java/com/hotels/styx/server/routing/antlr/AntlrConditionTest.java index 3c6b54011e..6317926c13 100644 --- a/components/server/src/test/java/com/hotels/styx/server/routing/antlr/AntlrConditionTest.java +++ b/components/server/src/test/java/com/hotels/styx/server/routing/antlr/AntlrConditionTest.java @@ -26,7 +26,7 @@ import static com.hotels.styx.api.HttpHeaderNames.USER_AGENT; import static com.hotels.styx.api.HttpRequest.get; import static com.hotels.styx.api.HttpRequest.post; -import static com.hotels.styx.api.cookies.RequestCookie.cookie; +import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; @@ -393,13 +393,13 @@ public void notExpressionHasHigherPrecedenceThanAndExpression() { public void cookieValueIsPresent() { Condition condition = condition("cookie('TheCookie')"); HttpRequest request = newRequest() - .cookies(cookie("TheCookie", "foobar-foobar-baz")) + .cookies(requestCookie("TheCookie", "foobar-foobar-baz")) .header("App-Name", "app3") .build(); assertThat(condition.evaluate(request), is(true)); request = newRequest() - .cookies(cookie("AnotherCookie", "foobar-foobar-baz")) + .cookies(requestCookie("AnotherCookie", "foobar-foobar-baz")) .header("App-Name", "app3") .build(); assertThat(condition.evaluate(request), is(false)); @@ -415,13 +415,13 @@ public void cookieValueIsPresent() { public void cookieValueMatchesWithString() { Condition condition = condition("cookie('TheCookie') == 'foobar-foobar-baz'"); HttpRequest request = newRequest() - .cookies(cookie("TheCookie", "foobar-foobar-baz")) + .cookies(requestCookie("TheCookie", "foobar-foobar-baz")) .header("App-Name", "app3") .build(); assertThat(condition.evaluate(request), is(true)); request = newRequest() - .cookies(cookie("AnotherCookie", "foobar-baz")) + .cookies(requestCookie("AnotherCookie", "foobar-baz")) .header("App-Name", "app3") .build(); assertThat(condition.evaluate(request), is(false)); @@ -437,13 +437,13 @@ public void cookieValueMatchesWithRegexp() { Condition condition = condition("cookie('TheCookie') =~ 'foobar-.*-baz'"); HttpRequest request = newRequest() - .cookies(cookie("TheCookie", "foobar-foobar-baz")) + .cookies(requestCookie("TheCookie", "foobar-foobar-baz")) .header("App-Name", "app3") .build(); assertThat(condition.evaluate(request), is(true)); request = newRequest() - .cookies(cookie("AnotherCookie", "foobar-x-baz")) + .cookies(requestCookie("AnotherCookie", "foobar-x-baz")) .header("App-Name", "app3") .build(); assertThat(condition.evaluate(request), is(false)); diff --git a/components/server/src/test/java/com/hotels/styx/server/routing/antlr/FunctionResolverTest.java b/components/server/src/test/java/com/hotels/styx/server/routing/antlr/FunctionResolverTest.java index 9c3d2689f2..f50f54a99f 100644 --- a/components/server/src/test/java/com/hotels/styx/server/routing/antlr/FunctionResolverTest.java +++ b/components/server/src/test/java/com/hotels/styx/server/routing/antlr/FunctionResolverTest.java @@ -23,7 +23,7 @@ import java.util.Map; import static com.hotels.styx.api.HttpRequest.get; -import static com.hotels.styx.api.cookies.RequestCookie.cookie; +import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; @@ -60,7 +60,7 @@ public void throwsExceptionIfZeroArgumentFunctionDoesNotExist() { public void resolvesOneArgumentFunctions() { HttpRequest request = get("/foo") .header("Host", "www.hotels.com") - .cookies(cookie("lang", "en_US|en-us_hotels_com")) + .cookies(requestCookie("lang", "en_US|en-us_hotels_com")) .build(); assertThat(functionResolver.resolveFunction("header", singletonList("Host")).call(request), is("www.hotels.com")); @@ -72,7 +72,7 @@ public void resolvesOneArgumentFunctions() { public void throwsExceptionIfOneArgumentFunctionDoesNotExist() { HttpRequest request = get("/foo") .header("Host", "www.hotels.com") - .cookies(cookie("lang", "en_US|en-us_hotels_com")) + .cookies(requestCookie("lang", "en_US|en-us_hotels_com")) .build(); functionResolver.resolveFunction("foobar", singletonList("barfoo")).call(request); diff --git a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala index c818d79abf..9142fe2018 100644 --- a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala +++ b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/HeadersSpec.scala @@ -21,7 +21,7 @@ import com.github.tomakehurst.wiremock.client.WireMock._ import com.hotels.styx.api.FullHttpRequest.get import com.hotels.styx.api.HttpHeaderNames.X_FORWARDED_FOR import com.hotels.styx.api.HttpHeaderValues -import com.hotels.styx.api.cookies.RequestCookie.cookie +import com.hotels.styx.api.cookies.RequestCookie.requestCookie import com.hotels.styx.api.cookies.ResponseCookie.responseCookie import com.hotels.styx.api.messages.HttpResponseStatus._ import com.hotels.styx.support.NettyOrigins @@ -388,7 +388,7 @@ class HeadersSpec extends FunSpec val req = get("/quotedCookies") .addHeader(HOST, styxServer.proxyHost) - .cookies(cookie("test-cookie", "\"hu_hotels_com,HCOM_HU,hu_HU,\"")) + .cookies(requestCookie("test-cookie", "\"hu_hotels_com,HCOM_HU,hu_HU,\"")) .build() val resp = decodedRequest(req) diff --git a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/OriginRestrictionSpec.scala b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/OriginRestrictionSpec.scala index 8f7867b3c7..9a6235e239 100644 --- a/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/OriginRestrictionSpec.scala +++ b/system-tests/e2e-suite/src/test/scala/com/hotels/styx/proxy/OriginRestrictionSpec.scala @@ -19,7 +19,7 @@ import com.github.tomakehurst.wiremock.client.WireMock._ import com.hotels.styx.api.HttpHeaderNames._ import com.hotels.styx.api.FullHttpRequest.get import com.hotels.styx.api.cookies.RequestCookie -import com.hotels.styx.api.cookies.RequestCookie.cookie +import com.hotels.styx.api.cookies.RequestCookie.requestCookie import com.hotels.styx.api.messages.HttpResponseStatus._ import com.hotels.styx.client.StyxHeaderConfig.ORIGIN_ID_DEFAULT import com.hotels.styx.support.backends.FakeHttpServer @@ -56,7 +56,7 @@ class OriginRestrictionSpec extends FunSpec it("Routes to origin indicated by cookie.") { val request = get("/app/") .header(HOST, styxServer.proxyHost) - .cookies(cookie("originRestrictionCookie", "h2")) + .cookies(requestCookie("originRestrictionCookie", "h2")) .build() val response = decodedRequest(request) @@ -68,7 +68,7 @@ class OriginRestrictionSpec extends FunSpec it("Routes to range of origins indicated by cookie.") { val request = get("/app/") .header(HOST, styxServer.proxyHost) - .cookies(cookie("originRestrictionCookie", "h(2|3)")) + .cookies(requestCookie("originRestrictionCookie", "h(2|3)")) .build() val response = decodedRequest(request) @@ -79,7 +79,7 @@ class OriginRestrictionSpec extends FunSpec it("If nothing matches treat as no hosts available") { val request = get("/app/") .header(HOST, styxServer.proxyHost) - .cookies(cookie("originRestrictionCookie", "(?!)")) + .cookies(requestCookie("originRestrictionCookie", "(?!)")) .build() val response = decodedRequest(request) @@ -90,7 +90,7 @@ class OriginRestrictionSpec extends FunSpec it("Routes to list of origins indicated by cookie.") { val request = get("/app/") .header(HOST, styxServer.proxyHost) - .cookies(cookie("originRestrictionCookie", "h2,h[3-4]")) + .cookies(requestCookie("originRestrictionCookie", "h2,h[3-4]")) .build() val response = decodedRequest(request) From e2bcd7dab3ec2be5c40e1fe1dc1826615ab994b1 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Wed, 18 Jul 2018 14:49:43 +0100 Subject: [PATCH 09/26] Tidy-up --- .../styx/api/cookies/RequestCookie.java | 12 +-- .../styx/api/cookies/ResponseCookie.java | 87 +++++++++---------- 2 files changed, 48 insertions(+), 51 deletions(-) diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java index fdb83df82a..e5f0748472 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java @@ -26,10 +26,10 @@ import java.util.Set; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.isNullOrEmpty; import static com.hotels.styx.api.HttpHeaderNames.COOKIE; import static com.hotels.styx.api.common.Strings.quote; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toSet; /** @@ -48,7 +48,7 @@ public final class RequestCookie { */ private RequestCookie(String name, String value) { checkArgument(!isNullOrEmpty(name), "name cannot be null or empty"); - checkNotNull(value, "value cannot be null"); + requireNonNull(value, "value cannot be null"); this.name = name; this.value = value; this.hashCode = Objects.hashCode(name, value); @@ -73,10 +73,6 @@ public static PseudoMap decode(HttpHeaders headers) { .collect(toSet())); } - private static PseudoMap wrap(Set cookies) { - return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); - } - public static void encode(HttpHeaders.Builder headers, Collection cookies) { Set nettyCookies = cookies.stream() .map(RequestCookie::convert) @@ -87,6 +83,10 @@ public static void encode(HttpHeaders.Builder headers, Collection } } + private static PseudoMap wrap(Set cookies) { + return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); + } + private static Cookie convert(RequestCookie cookie) { return new DefaultCookie(cookie.name, cookie.value); } diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java index 332a7dcc2a..59696c4737 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java @@ -15,7 +15,6 @@ */ package com.hotels.styx.api.cookies; -import com.google.common.base.Joiner; import com.hotels.styx.api.HttpHeaders; import com.hotels.styx.api.HttpResponse; import io.netty.handler.codec.http.cookie.ClientCookieDecoder; @@ -39,8 +38,6 @@ * Represents an HTTP cookie as sent in the {@code Set-Cookie} header. */ public final class ResponseCookie { - private static final Joiner JOINER_ON_SEMI_COLON_AND_SPACE = Joiner.on("; "); - private final String name; private final String value; @@ -90,10 +87,6 @@ public static PseudoMap decode(HttpHeaders headers) { .collect(toSet())); } - private static PseudoMap wrap(Set cookies) { - return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); - } - public static void encode(HttpHeaders.Builder headers, Collection cookies) { Set nettyCookies = cookies.stream() .map(ResponseCookie::convert) @@ -106,6 +99,48 @@ public static void encode(HttpResponse.Builder builder, ResponseCookie cookie) { builder.addHeader(SET_COOKIE, ServerCookieEncoder.LAX.encode(convert(cookie))); } + /** + * Returns cookie name. + * + * @return cookie name + */ + public String name() { + return name; + } + + /** + * Returns cookie value. + * + * @return cookie value + */ + public String value() { + return value; + } + + public Optional maxAge() { + return Optional.ofNullable(maxAge).filter(value -> value != UNDEFINED_MAX_AGE); + } + + public Optional path() { + return Optional.ofNullable(path); + } + + public boolean httpOnly() { + return httpOnly; + } + + public Optional domain() { + return Optional.ofNullable(domain); + } + + public boolean secure() { + return secure; + } + + private static PseudoMap wrap(Set cookies) { + return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); + } + private static Cookie convert(ResponseCookie cookie) { DefaultCookie nCookie = new DefaultCookie(cookie.name, cookie.value); @@ -132,24 +167,6 @@ private static ResponseCookie convert(Cookie cookie) { .build(); } - /** - * Returns cookie name. - * - * @return cookie name - */ - public String name() { - return name; - } - - /** - * Returns cookie value. - * - * @return cookie value - */ - public String value() { - return value; - } - @Override public boolean equals(Object o) { if (this == o) { @@ -188,26 +205,6 @@ public String toString() { .toString(); } - public Optional maxAge() { - return Optional.ofNullable(maxAge).filter(value -> value != UNDEFINED_MAX_AGE); - } - - public Optional path() { - return Optional.ofNullable(path); - } - - public boolean httpOnly() { - return httpOnly; - } - - public Optional domain() { - return Optional.ofNullable(domain); - } - - public boolean secure() { - return secure; - } - /** * Builds response cookie. */ From 9091fb6bcc5a2658e66e8ace40078fd87b7435a1 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Wed, 18 Jul 2018 14:56:05 +0100 Subject: [PATCH 10/26] Remove redundant method --- .../styx/api/cookies/ResponseCookie.java | 11 --------- .../hotels/styx/api/FullHttpResponseTest.java | 23 +++++++++---------- .../com/hotels/styx/api/HttpResponseTest.java | 23 +++++++++---------- .../HttpMessageLoggingInterceptorTest.java | 6 ++--- .../connectors/HttpResponseWriterTest.java | 4 ++-- 5 files changed, 27 insertions(+), 40 deletions(-) diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java index 59696c4737..8d32f0e050 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java @@ -65,17 +65,6 @@ private ResponseCookie(Builder builder) { this.hashCode = hash(name, value, domain, maxAge, path, secure, httpOnly); } - /** - * Constructs a cookie with a name, value and attributes. - * - * @param name cookie name - * @param value cookie value - * @return a cookie - */ - public static ResponseCookie cookie(String name, String value) { - return ResponseCookie.responseCookie(name, value).build(); - } - public static ResponseCookie.Builder responseCookie(String name, String value) { return new ResponseCookie.Builder(name, value); } diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java index 0a583dd62d..d8c2c9d924 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java @@ -30,7 +30,6 @@ import static com.hotels.styx.api.FullHttpResponse.response; import static com.hotels.styx.api.HttpHeader.header; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; -import static com.hotels.styx.api.cookies.ResponseCookie.cookie; import static com.hotels.styx.api.cookies.ResponseCookie.responseCookie; import static com.hotels.styx.api.matchers.HttpHeadersMatcher.isNotCacheable; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_GATEWAY; @@ -62,7 +61,7 @@ public void convertsToStreamingHttpResponse() throws Exception { FullHttpResponse response = response(CREATED) .version(HTTP_1_1) .header("HeaderName", "HeaderValue") - .cookies(cookie("CookieName", "CookieValue")) + .cookies(responseCookie("CookieName", "CookieValue").build()) .body("message content", UTF_8) .build(); @@ -75,7 +74,7 @@ public void convertsToStreamingHttpResponse() throws Exception { header("HeaderName", "HeaderValue"), header("Set-Cookie", "CookieName=CookieValue") )); - assertThat(streaming.cookies(), contains(ResponseCookie.cookie("CookieName", "CookieValue"))); + assertThat(streaming.cookies(), contains(responseCookie("CookieName", "CookieValue").build())); String body = streaming.toFullResponse(0x100000) .asCompletableFuture() @@ -121,15 +120,15 @@ public void setsASingleOutboundCookie() { public void setsMultipleOutboundCookies() { FullHttpResponse response = FullHttpResponse.response() .cookies( - cookie("a", "b"), - cookie("c", "d")) + responseCookie("a", "b").build(), + responseCookie("c", "d").build()) .build(); Set cookies = response.cookies(); assertThat(cookies, containsInAnyOrder( - cookie("a", "b"), - cookie("c", "d") + responseCookie("a", "b").build(), + responseCookie("c", "d").build() )); } @@ -137,11 +136,11 @@ public void setsMultipleOutboundCookies() { public void getASingleCookieValue() { FullHttpResponse response = FullHttpResponse.response() .cookies( - cookie("a", "b"), - cookie("c", "d")) + responseCookie("a", "b").build(), + responseCookie("c", "d").build()) .build(); - assertThat(response.cookie("c"), isValue(cookie("c", "d"))); + assertThat(response.cookie("c"), isValue(responseCookie("c", "d").build())); } @Test @@ -235,12 +234,12 @@ public void rejectsNullCookie() { @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookieName() { - FullHttpResponse.response().cookies(cookie(null, "value")).build(); + FullHttpResponse.response().cookies(responseCookie(null, "value").build()).build(); } @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookieValue() { - FullHttpResponse.response().cookies(cookie("name", null)).build(); + FullHttpResponse.response().cookies(responseCookie("name", null).build()).build(); } @DataProvider(name = "responses") diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java index c724bd476c..54666aa005 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java @@ -29,7 +29,6 @@ import static com.hotels.styx.api.HttpHeader.header; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; -import static com.hotels.styx.api.cookies.ResponseCookie.cookie; import static com.hotels.styx.api.cookies.ResponseCookie.responseCookie; import static com.hotels.styx.api.matchers.HttpHeadersMatcher.isNotCacheable; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_GATEWAY; @@ -64,7 +63,7 @@ public void encodesToFullHttpResponse() throws Exception { HttpResponse response = response(CREATED) .version(HTTP_1_0) .header("HeaderName", "HeaderValue") - .cookies(cookie("CookieName", "CookieValue")) + .cookies(responseCookie("CookieName", "CookieValue").build()) .body(body("foo", "bar")) .build(); @@ -75,7 +74,7 @@ public void encodesToFullHttpResponse() throws Exception { assertThat(full.status(), is(CREATED)); assertThat(full.version(), is(HTTP_1_0)); assertThat(full.headers(), contains(header("HeaderName", "HeaderValue"), header("Set-Cookie", "CookieName=CookieValue"))); - assertThat(full.cookies(), contains(cookie("CookieName", "CookieValue"))); + assertThat(full.cookies(), contains(responseCookie("CookieName", "CookieValue").build())); assertThat(full.body(), is(bytes("foobar"))); } @@ -134,26 +133,26 @@ public void setsASingleOutboundCookie() { public void setsMultipleOutboundCookies() { HttpResponse response = response() .cookies( - cookie("a", "b"), - cookie("c", "d")) + responseCookie("a", "b").build(), + responseCookie("c", "d").build()) .build(); Set cookies = response.cookies(); assertThat(cookies, containsInAnyOrder( - cookie("a", "b"), - cookie("c", "d"))); + responseCookie("a", "b").build(), + responseCookie("c", "d").build())); } @Test public void getASingleCookieValue() { HttpResponse response = response() .cookies( - cookie("a", "b"), - cookie("c", "d")) + responseCookie("a", "b").build(), + responseCookie("c", "d").build()) .build(); - assertThat(response.cookies().firstMatch("c"), isValue(cookie("c", "d"))); + assertThat(response.cookies().firstMatch("c"), isValue(responseCookie("c", "d").build())); } @Test @@ -247,12 +246,12 @@ public void rejectsNullCookie() { @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookieName() { - response().cookies(cookie(null, "value")).build(); + response().cookies(responseCookie(null, "value").build()).build(); } @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookieValue() { - response().cookies(cookie("name", null)).build(); + response().cookies(responseCookie("name", null).build()).build(); } @DataProvider(name = "responses") diff --git a/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java b/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java index 7182837b7f..ab46843792 100644 --- a/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java +++ b/components/proxy/src/test/java/com/hotels/styx/proxy/interceptors/HttpMessageLoggingInterceptorTest.java @@ -19,7 +19,6 @@ import com.hotels.styx.api.HttpRequest; import com.hotels.styx.api.HttpResponse; import com.hotels.styx.api.StyxObservable; -import com.hotels.styx.api.cookies.ResponseCookie; import com.hotels.styx.support.api.HttpMessageBodies; import com.hotels.styx.support.matchers.LoggingTestSupport; import org.testng.annotations.AfterMethod; @@ -30,6 +29,7 @@ import static com.hotels.styx.api.HttpRequest.get; import static com.hotels.styx.api.HttpResponse.response; import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; +import static com.hotels.styx.api.cookies.ResponseCookie.responseCookie; import static com.hotels.styx.api.messages.HttpResponseStatus.OK; import static com.hotels.styx.common.StyxFutures.await; import static com.hotels.styx.support.matchers.LoggingEventMatcher.loggingEvent; @@ -61,7 +61,7 @@ public void logsRequestsAndResponses() { consume(interceptor.intercept(request, respondWith( response(OK) .header("RespHeader", "RespHeaderValue") - .cookies(ResponseCookie.cookie("RespCookie", "RespCookieValue")) + .cookies(responseCookie("RespCookie", "RespCookieValue").build()) ))); String requestPattern = "request=\\{method=GET, secure=false, uri=/, origin=\"N/A\", headers=\\[ReqHeader=ReqHeaderValue, Cookie=ReqCookie=ReqCookieValue\\]\\}"; @@ -83,7 +83,7 @@ public void logsRequestsAndResponsesShort() { consume(interceptor.intercept(request, respondWith( response(OK) .header("RespHeader", "RespHeaderValue") - .cookies(ResponseCookie.cookie("RespCookie", "RespCookieValue")) + .cookies(responseCookie("RespCookie", "RespCookieValue").build()) ))); String requestPattern = "request=\\{method=GET, secure=false, uri=/, origin=\"N/A\"}"; diff --git a/components/server/src/test/java/com/hotels/styx/server/netty/connectors/HttpResponseWriterTest.java b/components/server/src/test/java/com/hotels/styx/server/netty/connectors/HttpResponseWriterTest.java index aa4cc71d45..5434b87bf5 100644 --- a/components/server/src/test/java/com/hotels/styx/server/netty/connectors/HttpResponseWriterTest.java +++ b/components/server/src/test/java/com/hotels/styx/server/netty/connectors/HttpResponseWriterTest.java @@ -42,7 +42,7 @@ import static com.google.common.base.Charsets.UTF_8; import static com.hotels.styx.api.HttpResponse.response; import static com.hotels.styx.api.StyxInternalObservables.fromRxObservable; -import static com.hotels.styx.api.cookies.ResponseCookie.cookie; +import static com.hotels.styx.api.cookies.ResponseCookie.responseCookie; import static com.hotels.styx.api.messages.HttpResponseStatus.OK; import static io.netty.buffer.Unpooled.copiedBuffer; import static io.netty.handler.codec.http.LastHttpContent.EMPTY_LAST_CONTENT; @@ -328,7 +328,7 @@ protected void channelRead0(ChannelHandlerContext ctx, HttpResponse response) th } ); - HttpResponse.Builder response = response(OK).cookies(cookie(",,,,", ",,,,")); + HttpResponse.Builder response = response(OK).cookies(responseCookie(",,,,", ",,,,").build()); ch.writeInbound(response.body(fromRxObservable(contentObservable.doOnUnsubscribe(() -> unsubscribed.set(true)))).build()); assertThat(channelRead.get(), is(true)); } From 10ed6dcd060d4dfeae9ca241c7eb8698884329d3 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Wed, 18 Jul 2018 16:08:57 +0100 Subject: [PATCH 11/26] Tidy-up encoding/decoding --- .../com/hotels/styx/api/FullHttpRequest.java | 41 ++++++++++++++----- .../com/hotels/styx/api/FullHttpResponse.java | 13 +++++- .../java/com/hotels/styx/api/HttpRequest.java | 25 ++++++++--- .../com/hotels/styx/api/HttpResponse.java | 13 +++++- .../styx/api/cookies/RequestCookie.java | 24 ++++------- .../styx/api/cookies/ResponseCookie.java | 23 ++++------- .../com/hotels/styx/api/HttpResponseTest.java | 2 +- .../hotels/styx/client/StyxHttpClient.java | 7 ++-- 8 files changed, 94 insertions(+), 54 deletions(-) diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java index be8c362c74..69a76f3911 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java @@ -24,16 +24,21 @@ import java.net.InetSocketAddress; import java.nio.charset.Charset; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.hotels.styx.api.HttpHeaderNames.CONNECTION; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; +import static com.hotels.styx.api.HttpHeaderNames.COOKIE; import static com.hotels.styx.api.HttpHeaderNames.HOST; import static com.hotels.styx.api.HttpHeaderValues.KEEP_ALIVE; +import static com.hotels.styx.api.cookies.RequestCookie.encode; import static com.hotels.styx.api.messages.HttpMethod.DELETE; import static com.hotels.styx.api.messages.HttpMethod.GET; import static com.hotels.styx.api.messages.HttpMethod.HEAD; @@ -155,6 +160,7 @@ public List headers(CharSequence name) { * Because FullHttpRequest is an immutable object, the returned byte array * reference cannot be used to modify the message content. *

+ * * @return Message body content */ @Override @@ -164,12 +170,12 @@ public byte[] body() { /** * Returns the message body as a String decoded with provided character set. - * + *

* Decodes the message body into a Java String object with a provided charset. * The caller must ensure the provided charset is compatible with message content * type and encoding. * - * @param charset Charset used to decode message body. + * @param charset Charset used to decode message body. * @return Message body as a String. */ @Override @@ -293,7 +299,7 @@ public Builder newBuilder() { /** * Converts this request into a streaming form (HttpRequest). - * + *

* Converts this request into a HttpRequest object which represents the HTTP request as a * stream of bytes. * @@ -312,7 +318,17 @@ public HttpRequest toStreamingRequest() { } public PseudoMap cookies() { - return RequestCookie.decode(headers); + // Note: there should only be one "Cookie" header, but we check for multiples just in case + // the alternative would be to respond with a 400 Bad Request status if multiple "Cookie" headers were detected + + return wrap(headers.getAll(COOKIE).stream() + .map(RequestCookie::decode) + .flatMap(Collection::stream) + .collect(Collectors.toSet())); + } + + private static PseudoMap wrap(Set cookies) { + return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); } @Override @@ -390,7 +406,7 @@ public Builder uri(String uri) { /** * Sets the request body. - * + *

* This method encodes a String content to a byte array using the specified * charset, and sets the Content-Length header accordingly. * @@ -404,13 +420,13 @@ public Builder body(String content, Charset charset) { /** * Sets the request body. - * + *

* This method encodes the content to a byte array using the specified * charset, and sets the Content-Length header *if* the setContentLength * argument is true. * - * @param content request body - * @param charset Charset used for encoding request body. + * @param content request body + * @param charset Charset used for encoding request body. * @param setContentLength If true, Content-Length header is set, otherwise it is not set. * @return {@code this} */ @@ -422,12 +438,12 @@ public Builder body(String content, Charset charset, boolean setContentLength) { /** * Sets the request body. - * + *

* This method encodes the content to a byte array provided, and * sets the Content-Length header *if* the setContentLength * argument is true. * - * @param content request body + * @param content request body * @param setContentLength If true, Content-Length header is set, otherwise it is not set. * @return {@code this} */ @@ -541,9 +557,12 @@ public Builder cookies(RequestCookie... cookies) { } private Builder cookies(List cookies) { - RequestCookie.encode(headers, cookies); + if (!cookies.isEmpty()) { + header(COOKIE, encode(cookies)); + } return this; } + /** * Sets whether the request is be secure. * diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java index 6d3f2d201d..2a6e111c2c 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java @@ -26,13 +26,17 @@ import java.nio.charset.Charset; import java.util.List; import java.util.Optional; +import java.util.Set; import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_TYPE; +import static com.hotels.styx.api.HttpHeaderNames.SET_COOKIE; import static com.hotels.styx.api.HttpHeaderNames.TRANSFER_ENCODING; import static com.hotels.styx.api.HttpHeaderValues.CHUNKED; +import static com.hotels.styx.api.cookies.ResponseCookie.decode; +import static com.hotels.styx.api.cookies.ResponseCookie.encode; import static com.hotels.styx.api.messages.HttpResponseStatus.OK; import static com.hotels.styx.api.messages.HttpVersion.HTTP_1_1; import static io.netty.buffer.Unpooled.copiedBuffer; @@ -151,7 +155,11 @@ public HttpResponse toStreamingResponse() { } public PseudoMap cookies() { - return ResponseCookie.decode(headers); + return wrap(decode(headers.getAll(SET_COOKIE))); + } + + private static PseudoMap wrap(Set cookies) { + return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); } public Optional cookie(String name) { @@ -323,7 +331,8 @@ public Builder cookies(ResponseCookie... cookies) { } private Builder cookies(List cookies) { - ResponseCookie.encode(headers, cookies); + encode(cookies).forEach(cookie -> + addHeader(SET_COOKIE, cookie)); return this; } diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java index 3e5a9d1089..0ac412811d 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java @@ -24,17 +24,22 @@ import rx.Observable; import java.net.InetSocketAddress; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.hotels.styx.api.FlowControlDisableOperator.disableFlowControl; import static com.hotels.styx.api.HttpHeaderNames.CONNECTION; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; +import static com.hotels.styx.api.HttpHeaderNames.COOKIE; import static com.hotels.styx.api.HttpHeaderNames.HOST; import static com.hotels.styx.api.HttpHeaderValues.KEEP_ALIVE; +import static com.hotels.styx.api.cookies.RequestCookie.encode; import static com.hotels.styx.api.messages.HttpMethod.DELETE; import static com.hotels.styx.api.messages.HttpMethod.GET; import static com.hotels.styx.api.messages.HttpMethod.HEAD; @@ -55,7 +60,6 @@ import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; import static java.util.UUID.randomUUID; -import static java.util.stream.Collectors.toSet; /** * HTTP request with a fully aggregated/decoded body. @@ -343,7 +347,17 @@ private static byte[] decodeAndRelease(CompositeByteBuf aggregate) { } public PseudoMap cookies() { - return RequestCookie.decode(headers); + // Note: there should only be one "Cookie" header, but we check for multiples just in case + // the alternative would be to respond with a 400 Bad Request status if multiple "Cookie" headers were detected + + return wrap(headers.getAll(COOKIE).stream() + .map(RequestCookie::decode) + .flatMap(Collection::stream) + .collect(Collectors.toSet())); + } + + private static PseudoMap wrap(Set cookies) { + return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); } @Override @@ -419,9 +433,6 @@ public Builder(HttpRequest request, StyxObservable body) { this.version = request.version(); this.headers = request.headers().newBuilder(); this.body = StyxCoreObservable.of(copiedBuffer(request.body())); - RequestCookie.encode(headers, request.cookies().stream() - .map(cookie -> RequestCookie.requestCookie(cookie.name(), cookie.value())) - .collect(toSet())); } /** @@ -580,7 +591,9 @@ public Builder cookies(RequestCookie... cookies) { } private Builder cookies(List cookies) { - RequestCookie.encode(headers, cookies); + if (!cookies.isEmpty()) { + header(COOKIE, encode(cookies)); + } return this; } diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java index 18dc8f723a..eafa5b6351 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java @@ -29,14 +29,18 @@ import java.nio.charset.Charset; import java.util.List; import java.util.Optional; +import java.util.Set; import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.hotels.styx.api.FlowControlDisableOperator.disableFlowControl; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_TYPE; +import static com.hotels.styx.api.HttpHeaderNames.SET_COOKIE; import static com.hotels.styx.api.HttpHeaderNames.TRANSFER_ENCODING; import static com.hotels.styx.api.HttpHeaderValues.CHUNKED; +import static com.hotels.styx.api.cookies.ResponseCookie.decode; +import static com.hotels.styx.api.cookies.ResponseCookie.encode; import static com.hotels.styx.api.messages.HttpResponseStatus.OK; import static com.hotels.styx.api.messages.HttpResponseStatus.statusWithCode; import static com.hotels.styx.api.messages.HttpVersion.HTTP_1_1; @@ -169,7 +173,11 @@ private static byte[] decodeAndRelease(CompositeByteBuf aggregate) { } public PseudoMap cookies() { - return ResponseCookie.decode(headers); + return wrap(decode(headers.getAll(SET_COOKIE))); + } + + private static PseudoMap wrap(Set cookies) { + return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); } public Optional cookie(String name) { @@ -310,7 +318,8 @@ public Builder cookies(ResponseCookie... cookies) { } private Builder cookies(List cookies) { - ResponseCookie.encode(headers, cookies); + encode(cookies).forEach(cookie -> + addHeader(SET_COOKIE, cookie)); return this; } diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java index e5f0748472..412ae462b5 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java @@ -16,7 +16,6 @@ package com.hotels.styx.api.cookies; import com.google.common.base.Objects; -import com.hotels.styx.api.HttpHeaders; import io.netty.handler.codec.http.cookie.ClientCookieEncoder; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.codec.http.cookie.DefaultCookie; @@ -27,7 +26,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.isNullOrEmpty; -import static com.hotels.styx.api.HttpHeaderNames.COOKIE; import static com.hotels.styx.api.common.Strings.quote; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toSet; @@ -65,26 +63,22 @@ public static RequestCookie requestCookie(String name, String value) { return new RequestCookie(name, value); } - public static PseudoMap decode(HttpHeaders headers) { - return wrap(headers.getAll(COOKIE).stream() - .map(ServerCookieDecoder.LAX::decode) - .flatMap(Collection::stream) + public static Set decode(String headerValue) { + return ServerCookieDecoder.LAX.decode(headerValue).stream() .map(RequestCookie::convert) - .collect(toSet())); + .collect(toSet()); } - public static void encode(HttpHeaders.Builder headers, Collection cookies) { + public static String encode(Collection cookies) { + if (cookies.isEmpty()) { + throw new IllegalArgumentException("Cannot create cookie header value from zero cookies"); + } + Set nettyCookies = cookies.stream() .map(RequestCookie::convert) .collect(toSet()); - if (!nettyCookies.isEmpty()) { - headers.set(COOKIE, ClientCookieEncoder.LAX.encode(nettyCookies)); - } - } - - private static PseudoMap wrap(Set cookies) { - return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); + return ClientCookieEncoder.LAX.encode(nettyCookies); } private static Cookie convert(RequestCookie cookie) { diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java index 8d32f0e050..b10946fb4c 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java @@ -15,19 +15,18 @@ */ package com.hotels.styx.api.cookies; -import com.hotels.styx.api.HttpHeaders; -import com.hotels.styx.api.HttpResponse; import io.netty.handler.codec.http.cookie.ClientCookieDecoder; import io.netty.handler.codec.http.cookie.Cookie; import io.netty.handler.codec.http.cookie.DefaultCookie; import java.util.Collection; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import static com.google.common.base.Objects.toStringHelper; -import static com.hotels.styx.api.HttpHeaderNames.SET_COOKIE; import static com.hotels.styx.api.common.Strings.quote; import static io.netty.handler.codec.http.cookie.Cookie.UNDEFINED_MAX_AGE; import static java.util.Objects.hash; @@ -69,23 +68,23 @@ public static ResponseCookie.Builder responseCookie(String name, String value) { return new ResponseCookie.Builder(name, value); } - public static PseudoMap decode(HttpHeaders headers) { - return wrap(headers.getAll(SET_COOKIE).stream() + public static Set decode(List headerValues) { + return headerValues.stream() .map(ClientCookieDecoder.LAX::decode) .map(ResponseCookie::convert) - .collect(toSet())); + .collect(Collectors.toSet()); } - public static void encode(HttpHeaders.Builder headers, Collection cookies) { + public static List encode(Collection cookies) { Set nettyCookies = cookies.stream() .map(ResponseCookie::convert) .collect(toSet()); - headers.set(SET_COOKIE, ServerCookieEncoder.LAX.encode(nettyCookies)); + return ServerCookieEncoder.LAX.encode(nettyCookies); } - public static void encode(HttpResponse.Builder builder, ResponseCookie cookie) { - builder.addHeader(SET_COOKIE, ServerCookieEncoder.LAX.encode(convert(cookie))); + public static String encode(ResponseCookie cookie) { + return ServerCookieEncoder.LAX.encode(convert(cookie)); } /** @@ -126,10 +125,6 @@ public boolean secure() { return secure; } - private static PseudoMap wrap(Set cookies) { - return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); - } - private static Cookie convert(ResponseCookie cookie) { DefaultCookie nCookie = new DefaultCookie(cookie.name, cookie.value); diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java index 54666aa005..8c67ed0453 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java @@ -73,7 +73,7 @@ public void encodesToFullHttpResponse() throws Exception { assertThat(full.status(), is(CREATED)); assertThat(full.version(), is(HTTP_1_0)); - assertThat(full.headers(), contains(header("HeaderName", "HeaderValue"), header("Set-Cookie", "CookieName=CookieValue"))); + assertThat(full.headers(), containsInAnyOrder(header("HeaderName", "HeaderValue"), header("Set-Cookie", "CookieName=CookieValue"))); assertThat(full.cookies(), contains(responseCookie("CookieName", "CookieValue").build())); assertThat(full.body(), is(bytes("foobar"))); diff --git a/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java b/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java index 7681d14ba2..9a91ea9ca3 100644 --- a/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java +++ b/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java @@ -49,6 +49,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Lists.newArrayList; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; +import static com.hotels.styx.api.HttpHeaderNames.SET_COOKIE; import static com.hotels.styx.api.HttpHeaderNames.TRANSFER_ENCODING; import static com.hotels.styx.api.StyxInternalObservables.toRxObservable; import static com.hotels.styx.client.stickysession.StickySessionCookie.newStickySessionCookie; @@ -317,9 +318,9 @@ private HttpResponse addStickySessionIdentifier(HttpResponse httpResponse, Origi } private static HttpResponse addCookie(HttpResponse response, ResponseCookie cookie) { - HttpResponse.Builder builder = response.newBuilder(); - ResponseCookie.encode(builder, cookie); - return builder.build(); + return response.newBuilder() + .addHeader(SET_COOKIE, ResponseCookie.encode(cookie)) + .build(); } private HttpRequest rewriteUrl(HttpRequest request) { From f6f46ffbfd60395fe38b8ee0248f0814b1d22795 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Mon, 23 Jul 2018 14:21:09 +0100 Subject: [PATCH 12/26] Add some Javadoc --- .../styx/api/cookies/RequestCookie.java | 6 ++ .../styx/api/cookies/ResponseCookie.java | 92 +++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java index 412ae462b5..3c7aa44f08 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java @@ -63,6 +63,12 @@ public static RequestCookie requestCookie(String name, String value) { return new RequestCookie(name, value); } + /** + * Decodes a "Cookie" header value into a set of {@link RequestCookie} objects. + * + * @param headerValue "Cookie" header value + * @return cookies + */ public static Set decode(String headerValue) { return ServerCookieDecoder.LAX.decode(headerValue).stream() .map(RequestCookie::convert) diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java index b10946fb4c..72f2f65ed9 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java @@ -64,10 +64,23 @@ private ResponseCookie(Builder builder) { this.hashCode = hash(name, value, domain, maxAge, path, secure, httpOnly); } + /** + * Creates a builder for response cookie. + * + * @param name name + * @param value value + * @return builder + */ public static ResponseCookie.Builder responseCookie(String name, String value) { return new ResponseCookie.Builder(name, value); } + /** + * Decodes a list of "Set-Cookie" header values into a set of {@link ResponseCookie} objects. + * + * @param headerValues "Set-Cookie" header values + * @return cookies + */ public static Set decode(List headerValues) { return headerValues.stream() .map(ClientCookieDecoder.LAX::decode) @@ -75,6 +88,12 @@ public static Set decode(List headerValues) { .collect(Collectors.toSet()); } + /** + * Encodes a collection of {@link ResponseCookie} objects into a list of "Set-Cookie" header values. + * + * @param cookies cookies + * @return "Set-Cookie" header values + */ public static List encode(Collection cookies) { Set nettyCookies = cookies.stream() .map(ResponseCookie::convert) @@ -83,6 +102,12 @@ public static List encode(Collection cookies) { return ServerCookieEncoder.LAX.encode(nettyCookies); } + /** + * Encodes a {@link ResponseCookie} object into a "Set-Cookie" header value. + * + * @param cookie cookie + * @return "Set-Cookie" header value + */ public static String encode(ResponseCookie cookie) { return ServerCookieEncoder.LAX.encode(convert(cookie)); } @@ -105,22 +130,47 @@ public String value() { return value; } + /** + * Returns the Max-Age attribute, if present. + * + * @return Max-Age attribute, if present + */ public Optional maxAge() { return Optional.ofNullable(maxAge).filter(value -> value != UNDEFINED_MAX_AGE); } + /** + * Returns the Path attribute, if present. + * + * @return Path attribute, if present + */ public Optional path() { return Optional.ofNullable(path); } + /** + * Returns true if the HttpOnly attribute is present. + * + * @return true if the HttpOnly attribute is present + */ public boolean httpOnly() { return httpOnly; } + /** + * Returns the Domain attribute, if present. + * + * @return Domain attribute, if present + */ public Optional domain() { return Optional.ofNullable(domain); } + /** + * Returns true if the Secure attribute is present. + * + * @return true if the Secure attribute is present + */ public boolean secure() { return secure; } @@ -208,36 +258,78 @@ private Builder(String name, String value) { this.value = requireNonNull(value); } + /** + * Sets the cookie name. + * + * @param name cookie name + * @return this builder + */ public Builder name(String name) { this.name = requireNonNull(name); return this; } + /** + * Sets the cookie value. + * + * @param value cookie value + * @return this builder + */ public Builder value(String value) { this.value = requireNonNull(value); return this; } + /** + * Sets the Domain attribute. + * + * @param domain Domain attribute + * @return this builder + */ public Builder domain(String domain) { this.domain = domain; return this; } + /** + * Sets the Max-Age attribute. + * + * @param maxAge Max-Age attribute + * @return this builder + */ public Builder maxAge(long maxAge) { this.maxAge = maxAge == UNDEFINED_MAX_AGE ? null : maxAge; return this; } + /** + * Sets the Path attribute. + * + * @param path Path attribute + * @return this builder + */ public Builder path(String path) { this.path = path; return this; } + /** + * Sets/unsets the HttpOnly attribute. + * + * @param httpOnly true to set the attribute, false to unset it + * @return this builder + */ public Builder httpOnly(boolean httpOnly) { this.httpOnly = httpOnly; return this; } + /** + * Sets/unsets the Secure attribute. + * + * @param secure true to set the attribute, false to unset it + * @return this builder + */ public Builder secure(boolean secure) { this.secure = secure; return this; From acb8b805c70710ccd420f5a9c51327c9fa491216 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Mon, 23 Jul 2018 14:23:23 +0100 Subject: [PATCH 13/26] Add some Javadoc --- .../java/com/hotels/styx/api/cookies/RequestCookie.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java index 3c7aa44f08..19065dd556 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java @@ -75,6 +75,12 @@ public static Set decode(String headerValue) { .collect(toSet()); } + /** + * Encodes a collection of {@link RequestCookie} objects into a "Cookie" header value. + * + * @param cookies cookies + * @return "Cookie" header value + */ public static String encode(Collection cookies) { if (cookies.isEmpty()) { throw new IllegalArgumentException("Cannot create cookie header value from zero cookies"); From a0172045f19a44afcfd2a61d342e672f053b78d7 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Mon, 23 Jul 2018 14:50:33 +0100 Subject: [PATCH 14/26] Remove PseudoMap --- .../com/hotels/styx/api/FullHttpRequest.java | 13 +- .../com/hotels/styx/api/FullHttpResponse.java | 13 +- .../java/com/hotels/styx/api/HttpRequest.java | 13 +- .../com/hotels/styx/api/HttpResponse.java | 13 +- .../hotels/styx/api/cookies/PseudoMap.java | 165 ------------------ .../hotels/styx/api/FullHttpRequestTest.java | 10 +- .../com/hotels/styx/api/HttpRequestTest.java | 10 +- .../com/hotels/styx/api/HttpResponseTest.java | 2 +- .../hotels/styx/client/StyxHttpClient.java | 6 +- .../NettyToStyxResponsePropagatorTest.java | 4 +- .../styx/server/routing/AntlrMatcher.java | 2 +- .../routing/antlr/AntlrConditionTest.java | 2 +- .../routing/antlr/FunctionResolverTest.java | 2 +- 13 files changed, 43 insertions(+), 212 deletions(-) delete mode 100644 components/api/src/main/java/com/hotels/styx/api/cookies/PseudoMap.java diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java index 69a76f3911..bdc948fc28 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java @@ -15,7 +15,6 @@ */ package com.hotels.styx.api; -import com.hotels.styx.api.cookies.PseudoMap; import com.hotels.styx.api.cookies.RequestCookie; import com.hotels.styx.api.messages.HttpMethod; import com.hotels.styx.api.messages.HttpVersion; @@ -317,18 +316,20 @@ public HttpRequest toStreamingRequest() { } } - public PseudoMap cookies() { + public Set cookies() { // Note: there should only be one "Cookie" header, but we check for multiples just in case // the alternative would be to respond with a 400 Bad Request status if multiple "Cookie" headers were detected - return wrap(headers.getAll(COOKIE).stream() + return headers.getAll(COOKIE).stream() .map(RequestCookie::decode) .flatMap(Collection::stream) - .collect(Collectors.toSet())); + .collect(Collectors.toSet()); } - private static PseudoMap wrap(Set cookies) { - return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); + public Optional cookie(String name) { + return cookies().stream() + .filter(cookie -> cookie.name().equals(name)) + .findFirst(); } @Override diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java index 2a6e111c2c..a1bbdee4a3 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java @@ -17,7 +17,6 @@ import com.google.common.base.Objects; import com.google.common.net.MediaType; -import com.hotels.styx.api.cookies.PseudoMap; import com.hotels.styx.api.cookies.ResponseCookie; import com.hotels.styx.api.messages.HttpResponseStatus; import com.hotels.styx.api.messages.HttpVersion; @@ -154,16 +153,14 @@ public HttpResponse toStreamingResponse() { } } - public PseudoMap cookies() { - return wrap(decode(headers.getAll(SET_COOKIE))); - } - - private static PseudoMap wrap(Set cookies) { - return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); + public Set cookies() { + return decode(headers.getAll(SET_COOKIE)); } public Optional cookie(String name) { - return cookies().firstMatch(name); + return cookies().stream() + .filter(cookie -> cookie.name().equals(name)) + .findFirst(); } @Override diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java index 0ac412811d..0ed976a814 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java @@ -15,7 +15,6 @@ */ package com.hotels.styx.api; -import com.hotels.styx.api.cookies.PseudoMap; import com.hotels.styx.api.cookies.RequestCookie; import com.hotels.styx.api.messages.HttpMethod; import com.hotels.styx.api.messages.HttpVersion; @@ -346,18 +345,20 @@ private static byte[] decodeAndRelease(CompositeByteBuf aggregate) { } } - public PseudoMap cookies() { + public Set cookies() { // Note: there should only be one "Cookie" header, but we check for multiples just in case // the alternative would be to respond with a 400 Bad Request status if multiple "Cookie" headers were detected - return wrap(headers.getAll(COOKIE).stream() + return headers.getAll(COOKIE).stream() .map(RequestCookie::decode) .flatMap(Collection::stream) - .collect(Collectors.toSet())); + .collect(Collectors.toSet()); } - private static PseudoMap wrap(Set cookies) { - return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); + public Optional cookie(String name) { + return cookies().stream() + .filter(cookie -> cookie.name().equals(name)) + .findFirst(); } @Override diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java index eafa5b6351..518f523823 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java @@ -17,7 +17,6 @@ import com.google.common.base.Objects; import com.google.common.net.MediaType; -import com.hotels.styx.api.cookies.PseudoMap; import com.hotels.styx.api.cookies.ResponseCookie; import com.hotels.styx.api.messages.HttpResponseStatus; import com.hotels.styx.api.messages.HttpVersion; @@ -172,16 +171,14 @@ private static byte[] decodeAndRelease(CompositeByteBuf aggregate) { } } - public PseudoMap cookies() { - return wrap(decode(headers.getAll(SET_COOKIE))); - } - - private static PseudoMap wrap(Set cookies) { - return new PseudoMap<>(cookies, (name, cookie) -> cookie.name().equals(name)); + public Set cookies() { + return decode(headers.getAll(SET_COOKIE)); } public Optional cookie(String name) { - return cookies().firstMatch(name); + return cookies().stream() + .filter(cookie -> cookie.name().equals(name)) + .findFirst(); } @Override diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/PseudoMap.java b/components/api/src/main/java/com/hotels/styx/api/cookies/PseudoMap.java deleted file mode 100644 index 39bb15b39a..0000000000 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/PseudoMap.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - Copyright (C) 2013-2018 Expedia Inc. - - 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 com.hotels.styx.api.cookies; - -import com.google.common.collect.ImmutableSet; - -import java.util.Collection; -import java.util.Iterator; -import java.util.Optional; -import java.util.Set; -import java.util.Spliterator; -import java.util.function.BiPredicate; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.toSet; - -/** - * A set that provides methods for accessing elements like a map. Note that as it is not really a map there is no guarantee - * that a "key" will only match one element. - * - * @param "key" type - * @param element type - */ -public class PseudoMap implements Set { - private final Set set; - private final BiPredicate searcher; - - public PseudoMap(Set set, BiPredicate searcher) { - this.set = ImmutableSet.copyOf(set); - this.searcher = requireNonNull(searcher); - } - - public Set matching(K key) { - return set.stream() - .filter(element -> searcher.test(key, element)) - .collect(toSet()); - } - - public Optional firstMatch(K key) { - return set.stream() - .filter(element -> searcher.test(key, element)) - .findFirst(); - } - - @Override - public int size() { - return set.size(); - } - - @Override - public boolean isEmpty() { - return set.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return set.contains(o); - } - - @Override - public Iterator iterator() { - return set.iterator(); - } - - @Override - public Object[] toArray() { - return set.toArray(); - } - - @Override - public T[] toArray(T[] a) { - return set.toArray(a); - } - - @Override - public boolean add(V e) { - return set.add(e); - } - - @Override - public boolean remove(Object o) { - return set.remove(o); - } - - @Override - public boolean containsAll(Collection c) { - return set.containsAll(c); - } - - @Override - public boolean addAll(Collection c) { - return set.addAll(c); - } - - @Override - public boolean retainAll(Collection c) { - return set.retainAll(c); - } - - @Override - public boolean removeAll(Collection c) { - return set.removeAll(c); - } - - @Override - public void clear() { - set.clear(); - } - - @Override - public boolean equals(Object o) { - return set.equals(o); - } - - @Override - public int hashCode() { - return set.hashCode(); - } - - @Override - public Spliterator spliterator() { - return set.spliterator(); - } - - @Override - public boolean removeIf(Predicate filter) { - return set.removeIf(filter); - } - - @Override - public Stream stream() { - return set.stream(); - } - - @Override - public Stream parallelStream() { - return set.parallelStream(); - } - - @Override - public void forEach(Consumer action) { - set.forEach(action); - } - - @Override - public String toString() { - return set.toString(); - } -} diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java index f1995f4458..2d951ebef7 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java @@ -125,7 +125,7 @@ public void createsARequestWithDefaultValues() { assertThat(request.headers("any"), is(emptyIterable())); assertThat(request.body().length, is(0)); - assertThat(request.cookies().firstMatch("any"), isAbsent()); + assertThat(request.cookie("any"), isAbsent()); assertThat(request.header("any"), isAbsent()); assertThat(request.keepAlive(), is(true)); assertThat(request.method(), is(GET)); @@ -345,9 +345,9 @@ public void canExtractCookies() { requestCookie("cookie2", "bar")) .build(); - assertThat(request.cookies().firstMatch("cookie1"), isValue(requestCookie("cookie1", "foo"))); - assertThat(request.cookies().firstMatch("cookie2"), isValue(requestCookie("cookie2", "bar"))); - assertThat(request.cookies().firstMatch("cookie3"), isValue(requestCookie("cookie3", "baz"))); + assertThat(request.cookie("cookie1"), isValue(requestCookie("cookie1", "foo"))); + assertThat(request.cookie("cookie2"), isValue(requestCookie("cookie2", "bar"))); + assertThat(request.cookie("cookie3"), isValue(requestCookie("cookie3", "baz"))); } @Test @@ -359,7 +359,7 @@ public void cannotExtractNonExistentCookie() { requestCookie("cookie2", "bar")) .build(); - assertThat(request.cookies().firstMatch("cookie4"), isAbsent()); + assertThat(request.cookie("cookie4"), isAbsent()); } @Test diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java index 23f328183b..3eae6737ea 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java @@ -129,7 +129,7 @@ public void createsARequestWithDefaultValues() throws Exception { assertThat(request.headers("any"), is(emptyIterable())); assertThat(bytesToString(request.body()), is("")); - assertThat(request.cookies().firstMatch("any"), isAbsent()); + assertThat(request.cookie("any"), isAbsent()); assertThat(request.header("any"), isAbsent()); assertThat(request.keepAlive(), is(true)); assertThat(request.method(), is(GET)); @@ -253,9 +253,9 @@ public void canExtractCookies() { requestCookie("cookie2", "bar")) .build(); - assertThat(request.cookies().firstMatch("cookie1"), isValue(requestCookie("cookie1", "foo"))); - assertThat(request.cookies().firstMatch("cookie2"), isValue(requestCookie("cookie2", "bar"))); - assertThat(request.cookies().firstMatch("cookie3"), isValue(requestCookie("cookie3", "baz"))); + assertThat(request.cookie("cookie1"), isValue(requestCookie("cookie1", "foo"))); + assertThat(request.cookie("cookie2"), isValue(requestCookie("cookie2", "bar"))); + assertThat(request.cookie("cookie3"), isValue(requestCookie("cookie3", "baz"))); } @Test @@ -267,7 +267,7 @@ public void cannotExtractNonExistentCookie() { requestCookie("cookie2", "bar")) .build(); - assertThat(request.cookies().firstMatch("cookie4"), isAbsent()); + assertThat(request.cookie("cookie4"), isAbsent()); } @Test diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java index 8c67ed0453..ae81ac5a4a 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java @@ -152,7 +152,7 @@ public void getASingleCookieValue() { responseCookie("c", "d").build()) .build(); - assertThat(response.cookies().firstMatch("c"), isValue(responseCookie("c", "d").build())); + assertThat(response.cookie("c"), isValue(responseCookie("c", "d").build())); } @Test diff --git a/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java b/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java index 9a91ea9ca3..fab7c167be 100644 --- a/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java +++ b/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java @@ -291,12 +291,12 @@ private Optional selectOrigin(HttpRequest rewrittenRequest) { @Override public Optional preferredOrigins() { if (nonNull(originsRestrictionCookieName)) { - return rewrittenRequest.cookies().firstMatch(originsRestrictionCookieName) + return rewrittenRequest.cookie(originsRestrictionCookieName) .map(RequestCookie::value) .map(Optional::of) - .orElse(rewrittenRequest.cookies().firstMatch("styx_origin_" + id).map(RequestCookie::value)); + .orElse(rewrittenRequest.cookie("styx_origin_" + id).map(RequestCookie::value)); } else { - return rewrittenRequest.cookies().firstMatch("styx_origin_" + id).map(RequestCookie::value); + return rewrittenRequest.cookie("styx_origin_" + id).map(RequestCookie::value); } } diff --git a/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagatorTest.java b/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagatorTest.java index 122d32e7ea..6c591fff59 100644 --- a/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagatorTest.java +++ b/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/NettyToStyxResponsePropagatorTest.java @@ -243,7 +243,7 @@ public void shouldConvertNettyCookieHeaderToStyxCookies() { HttpResponse styxResponse = toStyxResponse(nettyResponse).build(); assertThat(styxResponse.header("Set-Cookie"), isValue("SESSID=sessId; Domain=.foo.com; Path=/; HttpOnly")); - assertThat(styxResponse.cookies().firstMatch("SESSID"), equalTo( + assertThat(styxResponse.cookie("SESSID"), equalTo( Optional.of(responseCookie("SESSID", "sessId") .domain(".foo.com") .path("/") @@ -258,7 +258,7 @@ public void shouldLeaveIntactQuotedCookieValues() { HttpResponse styxResponse = toStyxResponse(nettyResponse).build(); assertThat(styxResponse.header("Set-Cookie"), isValue("SESSID=\"sessId\"; Domain=.foo.com; Path=/; HttpOnly")); - assertThat(styxResponse.cookies().firstMatch("SESSID"), equalTo( + assertThat(styxResponse.cookie("SESSID"), equalTo( Optional.of(responseCookie("SESSID", "\"sessId\"") .domain(".foo.com") .path("/") diff --git a/components/server/src/main/java/com/hotels/styx/server/routing/AntlrMatcher.java b/components/server/src/main/java/com/hotels/styx/server/routing/AntlrMatcher.java index b03a6a479d..e72d195825 100644 --- a/components/server/src/main/java/com/hotels/styx/server/routing/AntlrMatcher.java +++ b/components/server/src/main/java/com/hotels/styx/server/routing/AntlrMatcher.java @@ -31,7 +31,7 @@ public final class AntlrMatcher implements Matcher { .registerFunction("userAgent", request -> request.header(USER_AGENT).orElse("")) .registerFunction("protocol", request -> request.isSecure() ? "https" : "http") .registerFunction("header", (request, input) -> request.header(input).orElse("")) - .registerFunction("cookie", (request, input) -> request.cookies().firstMatch(input).map(RequestCookie::value).orElse("")) + .registerFunction("cookie", (request, input) -> request.cookie(input).map(RequestCookie::value).orElse("")) .build(); private final Condition condition; diff --git a/components/server/src/test/java/com/hotels/styx/server/routing/antlr/AntlrConditionTest.java b/components/server/src/test/java/com/hotels/styx/server/routing/antlr/AntlrConditionTest.java index 6317926c13..1800b545b7 100644 --- a/components/server/src/test/java/com/hotels/styx/server/routing/antlr/AntlrConditionTest.java +++ b/components/server/src/test/java/com/hotels/styx/server/routing/antlr/AntlrConditionTest.java @@ -37,7 +37,7 @@ public class AntlrConditionTest { .registerFunction("userAgent", request -> request.header(USER_AGENT).orElse("")) .registerFunction("header", (request, input) -> request.header(input).orElse("")) .registerFunction("cookie", (request, input) -> - request.cookies().firstMatch(input).map(RequestCookie::value).orElse("")) + request.cookie(input).map(RequestCookie::value).orElse("")) .build(); private static HttpRequest.Builder newRequest(String uri) { diff --git a/components/server/src/test/java/com/hotels/styx/server/routing/antlr/FunctionResolverTest.java b/components/server/src/test/java/com/hotels/styx/server/routing/antlr/FunctionResolverTest.java index f50f54a99f..b607a10379 100644 --- a/components/server/src/test/java/com/hotels/styx/server/routing/antlr/FunctionResolverTest.java +++ b/components/server/src/test/java/com/hotels/styx/server/routing/antlr/FunctionResolverTest.java @@ -36,7 +36,7 @@ public class FunctionResolverTest { Map oneArgumentFunctions = ImmutableMap.of( "header", (request, name) -> request.header(name).orElse(""), - "cookie", (request, name) -> request.cookies().firstMatch(name).map(RequestCookie::value).orElse("")); + "cookie", (request, name) -> request.cookie(name).map(RequestCookie::value).orElse("")); FunctionResolver functionResolver = new FunctionResolver(zeroArgumentFunctions, oneArgumentFunctions); From 1ba7b3f747c69f62d935ecf5ce060767803b0498 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Mon, 23 Jul 2018 15:16:58 +0100 Subject: [PATCH 15/26] Add more javadoc --- .../com/hotels/styx/api/FullHttpRequest.java | 25 ++++++++++++++++++- .../com/hotels/styx/api/FullHttpResponse.java | 25 ++++++++++++++++++- .../java/com/hotels/styx/api/HttpRequest.java | 25 ++++++++++++++++++- .../com/hotels/styx/api/HttpResponse.java | 25 ++++++++++++++++++- 4 files changed, 96 insertions(+), 4 deletions(-) diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java index bdc948fc28..964a3a0634 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java @@ -316,6 +316,11 @@ public HttpRequest toStreamingRequest() { } } + /** + * Decodes the "Cookie" header in this request and returns the cookies. + * + * @return cookies + */ public Set cookies() { // Note: there should only be one "Cookie" header, but we check for multiples just in case // the alternative would be to respond with a 400 Bad Request status if multiple "Cookie" headers were detected @@ -326,6 +331,12 @@ public Set cookies() { .collect(Collectors.toSet()); } + /** + * Decodes the "Cookie" header in this request and returns the specified cookie. + * + * @param name cookie name + * @return cookies + */ public Optional cookie(String name) { return cookies().stream() .filter(cookie -> cookie.name().equals(name)) @@ -553,11 +564,23 @@ public Builder method(HttpMethod method) { return this; } + /** + * Sets the cookies on this request by overwriting the value of the "Cookie" header. + * + * @param cookies cookies + * @return this builder + */ public Builder cookies(RequestCookie... cookies) { return cookies(asList(cookies)); } - private Builder cookies(List cookies) { + /** + * Sets the cookies on this request by overwriting the value of the "Cookie" header. + * + * @param cookies cookies + * @return this builder + */ + public Builder cookies(List cookies) { if (!cookies.isEmpty()) { header(COOKIE, encode(cookies)); } diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java index a1bbdee4a3..12e9cb00b3 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java @@ -153,10 +153,21 @@ public HttpResponse toStreamingResponse() { } } + /** + * Decodes the "Set-Cookie" headers in this response and returns the cookies. + * + * @return cookies + */ public Set cookies() { return decode(headers.getAll(SET_COOKIE)); } + /** + * Decodes the "Set-Cookie" headers in this response and returns the specified cookie. + * + * @param name cookie name + * @return cookie + */ public Optional cookie(String name) { return cookies().stream() .filter(cookie -> cookie.name().equals(name)) @@ -323,11 +334,23 @@ public Builder setChunked() { return this; } + /** + * Sets the cookies on this response by adding "Set-Cookie" headers. + * + * @param cookies cookies + * @return this builder + */ public Builder cookies(ResponseCookie... cookies) { return cookies(asList(cookies)); } - private Builder cookies(List cookies) { + /** + * Sets the cookies on this response by adding "Set-Cookie" headers. + * + * @param cookies cookies + * @return this builder + */ + public Builder cookies(List cookies) { encode(cookies).forEach(cookie -> addHeader(SET_COOKIE, cookie)); return this; diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java index 0ed976a814..4adf392836 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java @@ -345,6 +345,11 @@ private static byte[] decodeAndRelease(CompositeByteBuf aggregate) { } } + /** + * Decodes the "Cookie" header in this request and returns the cookies. + * + * @return cookies + */ public Set cookies() { // Note: there should only be one "Cookie" header, but we check for multiples just in case // the alternative would be to respond with a 400 Bad Request status if multiple "Cookie" headers were detected @@ -355,6 +360,12 @@ public Set cookies() { .collect(Collectors.toSet()); } + /** + * Decodes the "Cookie" header in this request and returns the specified cookie. + * + * @param name cookie name + * @return cookies + */ public Optional cookie(String name) { return cookies().stream() .filter(cookie -> cookie.name().equals(name)) @@ -587,11 +598,23 @@ public Builder enableKeepAlive() { return header(CONNECTION, KEEP_ALIVE); } + /** + * Sets the cookies on this request by overwriting the value of the "Cookie" header. + * + * @param cookies cookies + * @return this builder + */ public Builder cookies(RequestCookie... cookies) { return cookies(asList(cookies)); } - private Builder cookies(List cookies) { + /** + * Sets the cookies on this request by overwriting the value of the "Cookie" header. + * + * @param cookies cookies + * @return this builder + */ + public Builder cookies(List cookies) { if (!cookies.isEmpty()) { header(COOKIE, encode(cookies)); } diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java index 518f523823..7b421a9644 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java @@ -171,10 +171,21 @@ private static byte[] decodeAndRelease(CompositeByteBuf aggregate) { } } + /** + * Decodes the "Set-Cookie" headers in this response and returns the cookies. + * + * @return cookies + */ public Set cookies() { return decode(headers.getAll(SET_COOKIE)); } + /** + * Decodes the "Set-Cookie" headers in this response and returns the specified cookie. + * + * @param name cookie name + * @return cookie + */ public Optional cookie(String name) { return cookies().stream() .filter(cookie -> cookie.name().equals(name)) @@ -310,11 +321,23 @@ public Builder setChunked() { return this; } + /** + * Sets the cookies on this response by adding "Set-Cookie" headers. + * + * @param cookies cookies + * @return this builder + */ public Builder cookies(ResponseCookie... cookies) { return cookies(asList(cookies)); } - private Builder cookies(List cookies) { + /** + * Sets the cookies on this response by adding "Set-Cookie" headers. + * + * @param cookies cookies + * @return this builder + */ + public Builder cookies(List cookies) { encode(cookies).forEach(cookie -> addHeader(SET_COOKIE, cookie)); return this; From 161baacfa814b1891fddf5afaeb6e36a88b7210b Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Mon, 23 Jul 2018 15:45:11 +0100 Subject: [PATCH 16/26] Refactor --- .../main/java/com/hotels/styx/api/cookies/RequestCookie.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java index 19065dd556..f566859999 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java @@ -82,9 +82,7 @@ public static Set decode(String headerValue) { * @return "Cookie" header value */ public static String encode(Collection cookies) { - if (cookies.isEmpty()) { - throw new IllegalArgumentException("Cannot create cookie header value from zero cookies"); - } + checkArgument(!cookies.isEmpty(), "Cannot create cookie header value from zero cookies"); Set nettyCookies = cookies.stream() .map(RequestCookie::convert) From 1c0762f12e56701cc83ba683135423fe8ee168f4 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Mon, 23 Jul 2018 15:52:58 +0100 Subject: [PATCH 17/26] Tidy-up --- .../styx/api/cookies/ServerCookieEncoder.java | 28 +++++++++++-------- .../hotels/styx/api/FullHttpRequestTest.java | 3 +- .../hotels/styx/api/FullHttpResponseTest.java | 2 +- .../com/hotels/styx/api/HttpRequestTest.java | 5 ++-- .../com/hotels/styx/api/HttpResponseTest.java | 2 +- .../HttpRequestOperationTest.java | 15 ++++++---- 6 files changed, 32 insertions(+), 23 deletions(-) diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/ServerCookieEncoder.java b/components/api/src/main/java/com/hotels/styx/api/cookies/ServerCookieEncoder.java index b13079b334..0f88f3752a 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/ServerCookieEncoder.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/ServerCookieEncoder.java @@ -15,8 +15,6 @@ */ package com.hotels.styx.api.cookies; -import static io.netty.util.internal.ObjectUtil.checkNotNull; - import io.netty.handler.codec.http.HttpHeaderDateFormat; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.cookie.Cookie; @@ -33,6 +31,12 @@ import java.util.List; import java.util.Map; +import static com.hotels.styx.api.cookies.CookieUtil.add; +import static com.hotels.styx.api.cookies.CookieUtil.addQuoted; +import static com.hotels.styx.api.cookies.CookieUtil.stringBuilder; +import static com.hotels.styx.api.cookies.CookieUtil.stripTrailingSeparator; +import static io.netty.util.internal.ObjectUtil.checkNotNull; + /** * A RFC6265 compliant cookie encoder to be used server side, * so some fields are sent (Version is typically ignored). @@ -91,37 +95,37 @@ public String encode(Cookie cookie) { validateCookie(name, value); - StringBuilder buf = CookieUtil.stringBuilder(); + StringBuilder buf = stringBuilder(); if (cookie.wrap()) { - CookieUtil.addQuoted(buf, name, value); + addQuoted(buf, name, value); } else { - CookieUtil.add(buf, name, value); + add(buf, name, value); } if (cookie.maxAge() != Long.MIN_VALUE) { if (cookie.maxAge() >= 0) { - CookieUtil.add(buf, CookieHeaderNames.MAX_AGE, cookie.maxAge()); + add(buf, CookieHeaderNames.MAX_AGE, cookie.maxAge()); } Date expires = new Date(cookie.maxAge() * 1000 + System.currentTimeMillis()); - CookieUtil.add(buf, CookieHeaderNames.EXPIRES, HttpHeaderDateFormat.get().format(expires)); + add(buf, CookieHeaderNames.EXPIRES, HttpHeaderDateFormat.get().format(expires)); } if (cookie.path() != null) { - CookieUtil.add(buf, CookieHeaderNames.PATH, cookie.path()); + add(buf, CookieHeaderNames.PATH, cookie.path()); } if (cookie.domain() != null) { - CookieUtil.add(buf, CookieHeaderNames.DOMAIN, cookie.domain()); + add(buf, CookieHeaderNames.DOMAIN, cookie.domain()); } if (cookie.isSecure()) { - CookieUtil.add(buf, CookieHeaderNames.SECURE); + add(buf, CookieHeaderNames.SECURE); } if (cookie.isHttpOnly()) { - CookieUtil.add(buf, CookieHeaderNames.HTTPONLY); + add(buf, CookieHeaderNames.HTTPONLY); } - return CookieUtil.stripTrailingSeparator(buf); + return stripTrailingSeparator(buf); } /** Deduplicate a list of encoded cookies by keeping only the last instance with a given name. diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java index 2d951ebef7..25810d5f38 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java @@ -16,6 +16,7 @@ package com.hotels.styx.api; import com.google.common.collect.ImmutableMap; +import com.hotels.styx.api.cookies.RequestCookie; import com.hotels.styx.api.messages.HttpMethod; import io.netty.buffer.ByteBuf; import org.testng.annotations.DataProvider; @@ -426,7 +427,7 @@ private static byte[] bytes(String content) { @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookie() { - get("/").cookies(null); + get("/").cookies((RequestCookie) null); } @Test(expectedExceptions = IllegalArgumentException.class) diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java index d8c2c9d924..f96f81bf1f 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java @@ -229,7 +229,7 @@ public void shouldCheckIfCurrentResponseIsARedirectToOtherResource(com.hotels.st @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookie() { - FullHttpResponse.response().cookies(null).build(); + FullHttpResponse.response().cookies((ResponseCookie) null).build(); } @Test(expectedExceptions = NullPointerException.class) diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java index 3eae6737ea..fffbecdb88 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java @@ -16,6 +16,7 @@ package com.hotels.styx.api; import com.google.common.collect.ImmutableMap; +import com.hotels.styx.api.cookies.RequestCookie; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.testng.annotations.DataProvider; @@ -76,7 +77,7 @@ public void decodesToFullHttpRequest() throws Exception { assertThat(full.headers(), containsInAnyOrder( header("HeaderName", "HeaderValue"), header("Cookie", "CookieName=CookieValue"))); -// assertThat(full.cookies(), contains(HttpCookie.cookie("CookieName", "CookieValue"))); + assertThat(full.cookies(), contains(requestCookie("CookieName", "CookieValue"))); assertThat(full.body(), is(bytes("foobar"))); } @@ -326,7 +327,7 @@ public void builderSetsRequestContent() throws Exception { @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookie() { - get("/").cookies(null); + get("/").cookies((RequestCookie) null); } @Test(expectedExceptions = IllegalArgumentException.class) diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java index ae81ac5a4a..d6266ecf7b 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java @@ -241,7 +241,7 @@ public void shouldCheckIfCurrentResponseIsARedirectToOtherResource(HttpResponseS @Test(expectedExceptions = NullPointerException.class) public void rejectsNullCookie() { - response().cookies(null).build(); + response().cookies((ResponseCookie) null).build(); } @Test(expectedExceptions = NullPointerException.class) diff --git a/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperationTest.java b/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperationTest.java index 35394b6661..46c4290ccd 100644 --- a/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperationTest.java +++ b/components/client/src/test/unit/java/com/hotels/styx/client/netty/connectionpool/HttpRequestOperationTest.java @@ -17,13 +17,14 @@ import com.hotels.styx.api.HttpRequest; import io.netty.handler.codec.http.DefaultHttpRequest; +import io.netty.handler.codec.http.cookie.DefaultCookie; +import io.netty.handler.codec.http.cookie.ServerCookieDecoder; import org.testng.annotations.Test; import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; import static com.hotels.styx.api.messages.HttpMethod.GET; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.anyOf; -import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.core.Is.is; public class HttpRequestOperationTest { @@ -43,10 +44,12 @@ public void shouldTransformStyxRequestToNettyRequestWithAllRelevantInformation() assertThat(nettyRequest.method(), is(io.netty.handler.codec.http.HttpMethod.GET)); assertThat(nettyRequest.uri(), is("https://www.example.com/foo%2Cbar?foo%2Cbaf=2")); assertThat(nettyRequest.headers().get("X-Forwarded-Proto"), is("https")); - assertThat(nettyRequest.headers().getAll("Cookie"), anyOf( - contains("HASESSION_V3=asdasdasd; has=123456789"), - contains("has=123456789; HASESSION_V3=asdasdasd") - )); + + assertThat(ServerCookieDecoder.LAX.decode(nettyRequest.headers().get("Cookie")), + containsInAnyOrder( + new DefaultCookie("HASESSION_V3", "asdasdasd"), + new DefaultCookie("has", "123456789"))); + } @Test From e13efd063c5c9f1bec20d77e7b7625e280bb2791 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Tue, 24 Jul 2018 17:20:24 +0100 Subject: [PATCH 18/26] Add+remove cookie methods --- .../java/com/hotels/styx/api/HttpRequest.java | 72 ++++++++++++++++++- .../com/hotels/styx/api/HttpResponse.java | 65 ++++++++++++++++- 2 files changed, 133 insertions(+), 4 deletions(-) diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java index 4adf392836..769670fc66 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java @@ -15,6 +15,7 @@ */ package com.hotels.styx.api; +import com.google.common.collect.ImmutableSet; import com.hotels.styx.api.cookies.RequestCookie; import com.hotels.styx.api.messages.HttpMethod; import com.hotels.styx.api.messages.HttpVersion; @@ -23,11 +24,13 @@ import rx.Observable; import java.net.InetSocketAddress; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import static com.google.common.base.Objects.toStringHelper; @@ -38,6 +41,7 @@ import static com.hotels.styx.api.HttpHeaderNames.COOKIE; import static com.hotels.styx.api.HttpHeaderNames.HOST; import static com.hotels.styx.api.HttpHeaderValues.KEEP_ALIVE; +import static com.hotels.styx.api.cookies.RequestCookie.decode; import static com.hotels.styx.api.cookies.RequestCookie.encode; import static com.hotels.styx.api.messages.HttpMethod.DELETE; import static com.hotels.styx.api.messages.HttpMethod.GET; @@ -59,6 +63,7 @@ import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; import static java.util.UUID.randomUUID; +import static java.util.stream.Collectors.toList; /** * HTTP request with a fully aggregated/decoded body. @@ -614,13 +619,78 @@ public Builder cookies(RequestCookie... cookies) { * @param cookies cookies * @return this builder */ - public Builder cookies(List cookies) { + public Builder cookies(Collection cookies) { if (!cookies.isEmpty()) { header(COOKIE, encode(cookies)); } return this; } + /** + * Adds cookies into this request by overwriting the value of the "Cookie" header. + *

+ * Note that this requires decoding the current header value before re-encoding, so it is most efficient to + * add all new cookies in one call to the method rather than spreading them out. + * + * @param cookies new cookies + * @return this builder + */ + public Builder addCookies(RequestCookie... cookies) { + return addCookies(asList(cookies)); + } + + /** + * Adds cookies into this request by overwriting the value of the "Cookie" header. + *

+ * Note that this requires decoding the current header value before re-encoding, so it is most efficient to + * add all new cookies in one call to the method rather than spreading them out. + * + * @param cookies new cookies + * @return this builder + */ + public Builder addCookies(Collection cookies) { + Set currentCookies = decode(headers.get(COOKIE)); + + List combinedCookies = new ArrayList<>(currentCookies.size() + cookies.size()); + combinedCookies.addAll(currentCookies); + combinedCookies.addAll(cookies); + return cookies(combinedCookies); + } + + /** + * Removes all cookies matching one of the supplied names by overwriting the value of the "Cookie" header. + * + * @param names cookie names + * @return this builder + */ + public Builder removeCookies(String... names) { + return removeCookies(asList(names)); + } + + /** + * Removes all cookies matching one of the supplied names by overwriting the value of the "Cookie" header. + * + * @param names cookie names + * @return this builder + */ + public Builder removeCookies(Collection names) { + return removeCookiesIf(toSet(names)::contains); + } + + private Builder removeCookiesIf(Predicate removeIfName) { + Predicate keepIf = cookie -> !removeIfName.test(cookie.name()); + + List newCookies = decode(headers.get(COOKIE)).stream() + .filter(keepIf) + .collect(toList()); + + return cookies(newCookies); + } + + private static Set toSet(Collection collection) { + return collection instanceof Set ? (Set) collection : ImmutableSet.copyOf(collection); + } + /** * Builds a new full request based on the settings configured in this builder. * If {@code validate} is set to true: diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java index 7b421a9644..d632a061dc 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java @@ -16,6 +16,7 @@ package com.hotels.styx.api; import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; import com.google.common.net.MediaType; import com.hotels.styx.api.cookies.ResponseCookie; import com.hotels.styx.api.messages.HttpResponseStatus; @@ -26,9 +27,11 @@ import rx.Observable; import java.nio.charset.Charset; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; @@ -52,6 +55,7 @@ import static java.lang.String.format; import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; /** * HTTP response with a fully aggregated/decoded body. @@ -322,7 +326,7 @@ public Builder setChunked() { } /** - * Sets the cookies on this response by adding "Set-Cookie" headers. + * Sets the cookies on this response by removing existing "Set-Cookie" headers and adding new ones. * * @param cookies cookies * @return this builder @@ -332,17 +336,72 @@ public Builder cookies(ResponseCookie... cookies) { } /** - * Sets the cookies on this response by adding "Set-Cookie" headers. + * Sets the cookies on this response by removing existing "Set-Cookie" headers and adding new ones. * * @param cookies cookies * @return this builder */ - public Builder cookies(List cookies) { + public Builder cookies(Collection cookies) { + headers.remove(SET_COOKIE); + return addCookies(cookies); + } + + /** + * Adds cookies into this response by adding "Set-Cookie" headers. + * + * @param cookies cookies + * @return this builder + */ + public Builder addCookies(ResponseCookie... cookies) { + return addCookies(asList(cookies)); + } + + /** + * Adds cookies into this response by adding "Set-Cookie" headers. + * + * @param cookies cookies + * @return this builder + */ + public Builder addCookies(Collection cookies) { encode(cookies).forEach(cookie -> addHeader(SET_COOKIE, cookie)); return this; } + /** + * Removes all cookies matching one of the supplied names by removing their "Set-Cookie" headers. + * + * @param names cookie names + * @return this builder + */ + public Builder removeCookies(String... names) { + return removeCookies(asList(names)); + } + + /** + * Removes all cookies matching one of the supplied names by removing their "Set-Cookie" headers. + * + * @param names cookie names + * @return this builder + */ + public Builder removeCookies(Collection names) { + return removeCookiesIf(toSet(names)::contains); + } + + private Builder removeCookiesIf(Predicate removeIfName) { + Predicate keepIf = cookie -> !removeIfName.test(cookie.name()); + + List newCookies = decode(headers.getAll(SET_COOKIE)).stream() + .filter(keepIf) + .collect(toList()); + + return cookies(newCookies); + } + + private static Set toSet(Collection collection) { + return collection instanceof Set ? (Set) collection : ImmutableSet.copyOf(collection); + } + /** * Sets the (only) value for the header with the specified name. *

From abcd42533a914c4d2e52dafe60b7001001f9a608 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Tue, 24 Jul 2018 17:27:03 +0100 Subject: [PATCH 19/26] Changes based on review comments --- .../java/com/hotels/styx/client/StyxHttpClient.java | 12 +++--------- .../com/hotels/styx/client/StickySessionSpec.scala | 13 +++++++------ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java b/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java index fab7c167be..b0ff955858 100644 --- a/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java +++ b/components/client/src/main/java/com/hotels/styx/client/StyxHttpClient.java @@ -25,7 +25,6 @@ import com.hotels.styx.api.client.loadbalancing.spi.LoadBalancer; import com.hotels.styx.api.client.retrypolicy.spi.RetryPolicy; import com.hotels.styx.api.cookies.RequestCookie; -import com.hotels.styx.api.cookies.ResponseCookie; import com.hotels.styx.api.exceptions.NoAvailableHostsException; import com.hotels.styx.api.messages.HttpResponseStatus; import com.hotels.styx.api.metrics.MetricRegistry; @@ -49,7 +48,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Lists.newArrayList; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; -import static com.hotels.styx.api.HttpHeaderNames.SET_COOKIE; import static com.hotels.styx.api.HttpHeaderNames.TRANSFER_ENCODING; import static com.hotels.styx.api.StyxInternalObservables.toRxObservable; import static com.hotels.styx.client.stickysession.StickySessionCookie.newStickySessionCookie; @@ -311,18 +309,14 @@ public List avoidOrigins() { private HttpResponse addStickySessionIdentifier(HttpResponse httpResponse, Origin origin) { if (this.loadBalancer instanceof StickySessionLoadBalancingStrategy) { int maxAge = backendService.stickySessionConfig().stickySessionTimeoutSeconds(); - return addCookie(httpResponse, newStickySessionCookie(id, origin.id(), maxAge)); + return httpResponse.newBuilder() + .addCookies(newStickySessionCookie(id, origin.id(), maxAge)) + .build(); } else { return httpResponse; } } - private static HttpResponse addCookie(HttpResponse response, ResponseCookie cookie) { - return response.newBuilder() - .addHeader(SET_COOKIE, ResponseCookie.encode(cookie)) - .build(); - } - private HttpRequest rewriteUrl(HttpRequest request) { return rewriteRuleset.rewrite(request); } diff --git a/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala b/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala index 10a16be948..0c0446e346 100644 --- a/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala +++ b/components/client/src/test/integration/scala/com/hotels/styx/client/StickySessionSpec.scala @@ -25,6 +25,7 @@ import com.hotels.styx.api.Id.id import com.hotels.styx.api.client.loadbalancing.spi.LoadBalancer import com.hotels.styx.api.client.{ActiveOrigins, Origin} import com.hotels.styx.api.cookies.RequestCookie +import com.hotels.styx.api.cookies.RequestCookie.requestCookie import com.hotels.styx.api.messages.HttpResponseStatus.OK import com.hotels.styx.api.service.{BackendService, StickySessionConfig} import com.hotels.styx.client.OriginsInventory.newOriginsInventoryBuilder @@ -133,7 +134,7 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers .build val request: HttpRequest = get("/") - .cookies(RequestCookie.requestCookie("styx_origin_app", "app-02")) + .cookies(requestCookie("styx_origin_app", "app-02")) .build val response1 = waitForResponse(client.sendRequest(request)) @@ -152,9 +153,9 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers val request: HttpRequest = get("/") .cookies( - RequestCookie.requestCookie("other_cookie1", "foo"), - RequestCookie.requestCookie("styx_origin_app", "app-02"), - RequestCookie.requestCookie("other_cookie2", "bar")) + requestCookie("other_cookie1", "foo"), + requestCookie("styx_origin_app", "app-02"), + requestCookie("other_cookie2", "bar")) .build() @@ -173,7 +174,7 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers .build val request: HttpRequest = get("/") - .cookies(RequestCookie.requestCookie("styx_origin_app", "h3")) + .cookies(requestCookie("styx_origin_app", "h3")) .build val response = waitForResponse(client.sendRequest(request)) @@ -198,7 +199,7 @@ class StickySessionSpec extends FunSuite with BeforeAndAfter with ShouldMatchers .build val request: HttpRequest = get("/") - .cookies(RequestCookie.requestCookie("styx_origin_app", "app-02")) + .cookies(requestCookie("styx_origin_app", "app-02")) .build val response = waitForResponse(client.sendRequest(request)) From 1f4616c16a6641690dcba2232e75f0835f259cf2 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Wed, 25 Jul 2018 11:11:18 +0100 Subject: [PATCH 20/26] Add more tests --- .../com/hotels/styx/api/FullHttpRequest.java | 84 +++++++++++++++++-- .../com/hotels/styx/api/FullHttpResponse.java | 75 ++++++++++++++++- .../java/com/hotels/styx/api/HttpRequest.java | 2 +- .../com/hotels/styx/api/HttpResponse.java | 10 +++ .../styx/api/cookies/RequestCookie.java | 5 ++ .../styx/api/cookies/ResponseCookie.java | 4 +- .../hotels/styx/api/FullHttpRequestTest.java | 32 +++++++ .../hotels/styx/api/FullHttpResponseTest.java | 35 +++++++- .../com/hotels/styx/api/HttpRequestTest.java | 32 +++++++ .../com/hotels/styx/api/HttpResponseTest.java | 33 ++++++++ .../styx/api/cookies/RequestCookieTest.java | 16 ++++ 11 files changed, 312 insertions(+), 16 deletions(-) diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java index 964a3a0634..51f580505a 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java @@ -15,6 +15,7 @@ */ package com.hotels.styx.api; +import com.google.common.collect.ImmutableSet; import com.hotels.styx.api.cookies.RequestCookie; import com.hotels.styx.api.messages.HttpMethod; import com.hotels.styx.api.messages.HttpVersion; @@ -23,11 +24,13 @@ import java.net.InetSocketAddress; import java.nio.charset.Charset; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import static com.google.common.base.Objects.toStringHelper; @@ -37,6 +40,7 @@ import static com.hotels.styx.api.HttpHeaderNames.COOKIE; import static com.hotels.styx.api.HttpHeaderNames.HOST; import static com.hotels.styx.api.HttpHeaderValues.KEEP_ALIVE; +import static com.hotels.styx.api.cookies.RequestCookie.decode; import static com.hotels.styx.api.cookies.RequestCookie.encode; import static com.hotels.styx.api.messages.HttpMethod.DELETE; import static com.hotels.styx.api.messages.HttpMethod.GET; @@ -51,6 +55,7 @@ import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; import static java.util.UUID.randomUUID; +import static java.util.stream.Collectors.toList; /** * HTTP request with a fully aggregated/decoded body. @@ -83,7 +88,7 @@ public class FullHttpRequest implements FullHttpMessage { * @param uri URI * @return {@code this} */ - public static Builder get(String uri) { + public static Builder get(String uri) { return new Builder(GET, uri); } @@ -93,7 +98,7 @@ public static Builder get(String uri) { * @param uri URI * @return {@code this} */ - public static Builder head(String uri) { + public static Builder head(String uri) { return new Builder(HEAD, uri); } @@ -103,7 +108,7 @@ public static Builder head(String uri) { * @param uri URI * @return {@code this} */ - public static Builder post(String uri) { + public static Builder post(String uri) { return new Builder(POST, uri); } @@ -113,7 +118,7 @@ public static Builder post(String uri) { * @param uri URI * @return {@code this} */ - public static Builder delete(String uri) { + public static Builder delete(String uri) { return new Builder(DELETE, uri); } @@ -123,7 +128,7 @@ public static Builder delete(String uri) { * @param uri URI * @return {@code this} */ - public static Builder put(String uri) { + public static Builder put(String uri) { return new Builder(PUT, uri); } @@ -133,7 +138,7 @@ public static Builder put(String uri) { * @param uri URI * @return {@code this} */ - public static Builder patch(String uri) { + public static Builder patch(String uri) { return new Builder(PATCH, uri); } @@ -580,13 +585,78 @@ public Builder cookies(RequestCookie... cookies) { * @param cookies cookies * @return this builder */ - public Builder cookies(List cookies) { + public Builder cookies(Collection cookies) { if (!cookies.isEmpty()) { header(COOKIE, encode(cookies)); } return this; } + /** + * Adds cookies into this request by overwriting the value of the "Cookie" header. + *

+ * Note that this requires decoding the current header value before re-encoding, so it is most efficient to + * add all new cookies in one call to the method rather than spreading them out. + * + * @param cookies new cookies + * @return this builder + */ + public Builder addCookies(RequestCookie... cookies) { + return addCookies(asList(cookies)); + } + + /** + * Adds cookies into this request by overwriting the value of the "Cookie" header. + *

+ * Note that this requires decoding the current header value before re-encoding, so it is most efficient to + * add all new cookies in one call to the method rather than spreading them out. + * + * @param cookies new cookies + * @return this builder + */ + public Builder addCookies(Collection cookies) { + Set currentCookies = decode(headers.get(COOKIE)); + + List combinedCookies = new ArrayList<>(currentCookies.size() + cookies.size()); + combinedCookies.addAll(cookies); + combinedCookies.addAll(currentCookies); + return cookies(combinedCookies); + } + + /** + * Removes all cookies matching one of the supplied names by overwriting the value of the "Cookie" header. + * + * @param names cookie names + * @return this builder + */ + public Builder removeCookies(String... names) { + return removeCookies(asList(names)); + } + + /** + * Removes all cookies matching one of the supplied names by overwriting the value of the "Cookie" header. + * + * @param names cookie names + * @return this builder + */ + public Builder removeCookies(Collection names) { + return removeCookiesIf(toSet(names)::contains); + } + + private Builder removeCookiesIf(Predicate removeIfName) { + Predicate keepIf = cookie -> !removeIfName.test(cookie.name()); + + List newCookies = decode(headers.get(COOKIE)).stream() + .filter(keepIf) + .collect(toList()); + + return cookies(newCookies); + } + + private static Set toSet(Collection collection) { + return collection instanceof Set ? (Set) collection : ImmutableSet.copyOf(collection); + } + /** * Sets whether the request is be secure. * diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java index 12e9cb00b3..9838272e62 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java @@ -16,6 +16,7 @@ package com.hotels.styx.api; import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; import com.google.common.net.MediaType; import com.hotels.styx.api.cookies.ResponseCookie; import com.hotels.styx.api.messages.HttpResponseStatus; @@ -23,9 +24,11 @@ import rx.Observable; import java.nio.charset.Charset; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; @@ -42,6 +45,7 @@ import static java.lang.Integer.parseInt; import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; /** * HTTP response with a fully aggregated/decoded body. @@ -335,7 +339,7 @@ public Builder setChunked() { } /** - * Sets the cookies on this response by adding "Set-Cookie" headers. + * Sets the cookies on this response by removing existing "Set-Cookie" headers and adding new ones. * * @param cookies cookies * @return this builder @@ -345,17 +349,82 @@ public Builder cookies(ResponseCookie... cookies) { } /** - * Sets the cookies on this response by adding "Set-Cookie" headers. + * Sets the cookies on this response by removing existing "Set-Cookie" headers and adding new ones. * * @param cookies cookies * @return this builder */ - public Builder cookies(List cookies) { + public Builder cookies(Collection cookies) { + headers.remove(SET_COOKIE); + return addCookies(cookies); + } + + /** + * Adds cookies into this response by adding "Set-Cookie" headers. + * + * @param cookies cookies + * @return this builder + */ + public Builder addCookies(ResponseCookie... cookies) { + return addCookies(asList(cookies)); + } + + /** + * Adds cookies into this response by adding "Set-Cookie" headers. + * + * @param cookies cookies + * @return this builder + */ + public Builder addCookies(Collection cookies) { + if (cookies.isEmpty()) { + return this; + } + + removeCookies(cookies.stream().map(ResponseCookie::name).collect(toList())); + encode(cookies).forEach(cookie -> addHeader(SET_COOKIE, cookie)); return this; } + /** + * Removes all cookies matching one of the supplied names by removing their "Set-Cookie" headers. + * + * @param names cookie names + * @return this builder + */ + public Builder removeCookies(String... names) { + return removeCookies(asList(names)); + } + + /** + * Removes all cookies matching one of the supplied names by removing their "Set-Cookie" headers. + * + * @param names cookie names + * @return this builder + */ + public Builder removeCookies(Collection names) { + if (names.isEmpty()) { + return this; + } + + return removeCookiesIf(toSet(names)::contains); + } + + private Builder removeCookiesIf(Predicate removeIfName) { + Predicate keepIf = cookie -> !removeIfName.test(cookie.name()); + + List newCookies = decode(headers.getAll(SET_COOKIE)).stream() + .filter(keepIf) + .collect(toList()); + + return cookies(newCookies); + } + + private static Set toSet(Collection collection) { + return collection instanceof Set ? (Set) collection : ImmutableSet.copyOf(collection); + } + /** * Sets the (only) value for the header with the specified name. *

diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java index 769670fc66..6017e2e4ef 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java @@ -652,8 +652,8 @@ public Builder addCookies(Collection cookies) { Set currentCookies = decode(headers.get(COOKIE)); List combinedCookies = new ArrayList<>(currentCookies.size() + cookies.size()); - combinedCookies.addAll(currentCookies); combinedCookies.addAll(cookies); + combinedCookies.addAll(currentCookies); return cookies(combinedCookies); } diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java index d632a061dc..9b9aa6727a 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java @@ -363,6 +363,12 @@ public Builder addCookies(ResponseCookie... cookies) { * @return this builder */ public Builder addCookies(Collection cookies) { + if (cookies.isEmpty()) { + return this; + } + + removeCookies(cookies.stream().map(ResponseCookie::name).collect(toList())); + encode(cookies).forEach(cookie -> addHeader(SET_COOKIE, cookie)); return this; @@ -385,6 +391,10 @@ public Builder removeCookies(String... names) { * @return this builder */ public Builder removeCookies(Collection names) { + if (names.isEmpty()) { + return this; + } + return removeCookiesIf(toSet(names)::contains); } diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java index f566859999..d1087aa01c 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/RequestCookie.java @@ -27,6 +27,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.isNullOrEmpty; import static com.hotels.styx.api.common.Strings.quote; +import static java.util.Collections.emptySet; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toSet; @@ -70,6 +71,10 @@ public static RequestCookie requestCookie(String name, String value) { * @return cookies */ public static Set decode(String headerValue) { + if (headerValue == null) { + return emptySet(); + } + return ServerCookieDecoder.LAX.decode(headerValue).stream() .map(RequestCookie::convert) .collect(toSet()); diff --git a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java index 72f2f65ed9..97c11d53c0 100644 --- a/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java +++ b/components/api/src/main/java/com/hotels/styx/api/cookies/ResponseCookie.java @@ -212,7 +212,6 @@ public boolean equals(Object o) { ResponseCookie that = (ResponseCookie) o; return httpOnly == that.httpOnly && secure == that.secure - && hashCode == that.hashCode && Objects.equals(name, that.name) && Objects.equals(value, that.value) && Objects.equals(domain, that.domain) @@ -222,7 +221,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return hash(name, value, domain, maxAge, path, httpOnly, secure, hashCode); + return hash(name, value, domain, maxAge, path, httpOnly, secure); } @Override @@ -235,7 +234,6 @@ public String toString() { .add("path", path) .add("httpOnly", httpOnly) .add("secure", secure) - .add("hashCode", hashCode) .toString(); } diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java index 25810d5f38..996f8ab87f 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java @@ -485,4 +485,36 @@ public void createsANewRequestWithSameVersionAsBefore() { assertThat(newRequest.version(), is(HTTP_1_0)); } + @Test + public void addsCookies() { + FullHttpRequest request = FullHttpRequest.get("/") + .addCookies(requestCookie("x", "x1"), requestCookie("y", "y1")) + .build(); + + assertThat(request.cookies(), containsInAnyOrder(requestCookie("x", "x1"), requestCookie("y", "y1"))); + } + + @Test + public void addsCookiesToExistingCookies() { + FullHttpRequest request = FullHttpRequest.get("/") + .addCookies(requestCookie("z", "z1")) + .addCookies(requestCookie("x", "x1"), requestCookie("y", "y1")) + .build(); + + assertThat(request.cookies(), containsInAnyOrder(requestCookie("x", "x1"), requestCookie("y", "y1"), requestCookie("z", "z1"))); + } + + @Test + public void newCookiesWithDuplicateNamesOverridePreviousOnes() { + FullHttpRequest r1 = FullHttpRequest.get("/") + .cookies(requestCookie("y", "y1")) + .build(); + + FullHttpRequest r2 = r1.newBuilder().addCookies( + requestCookie("y", "y2")) + .build(); + + assertThat(r2.cookies(), containsInAnyOrder(requestCookie("y", "y2"))); + } + } diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java index f96f81bf1f..3792f4cb71 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java @@ -461,11 +461,11 @@ public void toFullResponseReleasesOriginalRefCountedBuffers() throws ExecutionEx @Test public void transformedBodyIsNewCopy() { - FullHttpRequest request = get("/foo") + FullHttpResponse request = response() .body("Original body", UTF_8) .build(); - FullHttpRequest newRequest = request.newBuilder() + FullHttpResponse newRequest = response() .body("New body", UTF_8) .build(); @@ -473,4 +473,35 @@ public void transformedBodyIsNewCopy() { assertThat(newRequest.bodyAs(UTF_8), is("New body")); } + @Test + public void addsCookies() { + FullHttpResponse response = response() + .addCookies(responseCookie("x", "x1").build(), responseCookie("y", "y1").build()) + .build(); + + assertThat(response.cookies(), containsInAnyOrder(responseCookie("x", "x1").build(), responseCookie("y", "y1").build())); + } + + @Test + public void addsCookiesToExistingCookies() { + FullHttpResponse response = response() + .addCookies(responseCookie("z", "z1").build()) + .addCookies(responseCookie("x", "x1").build(), responseCookie("y", "y1").build()) + .build(); + + assertThat(response.cookies(), containsInAnyOrder(responseCookie("x", "x1").build(), responseCookie("y", "y1").build(), responseCookie("z", "z1").build())); + } + + @Test + public void newCookiesWithDuplicateNamesOverridePreviousOnes() { + FullHttpResponse r1 = response() + .cookies(responseCookie("y", "y1").build()) + .build(); + + FullHttpResponse r2 = r1.newBuilder().addCookies( + responseCookie("y", "y2").build()) + .build(); + + assertThat(r2.cookies(), containsInAnyOrder(responseCookie("y", "y2").build())); + } } \ No newline at end of file diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java index fffbecdb88..4d8d8ac050 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java @@ -401,6 +401,38 @@ public void conversionPreservesClientAddress() throws Exception { assertThat(shouldMatchOriginal.clientAddress(), is(address)); } + @Test + public void addsCookies() { + HttpRequest request = HttpRequest.get("/") + .addCookies(requestCookie("x", "x1"), requestCookie("y", "y1")) + .build(); + + assertThat(request.cookies(), containsInAnyOrder(requestCookie("x", "x1"), requestCookie("y", "y1"))); + } + + @Test + public void addsCookiesToExistingCookies() { + HttpRequest request = HttpRequest.get("/") + .addCookies(requestCookie("z", "z1")) + .addCookies(requestCookie("x", "x1"), requestCookie("y", "y1")) + .build(); + + assertThat(request.cookies(), containsInAnyOrder(requestCookie("x", "x1"), requestCookie("y", "y1"), requestCookie("z", "z1"))); + } + + @Test + public void newCookiesWithDuplicateNamesOverridePreviousOnes() { + HttpRequest r1 = HttpRequest.get("/") + .cookies(requestCookie("y", "y1")) + .build(); + + HttpRequest r2 = r1.newBuilder().addCookies( + requestCookie("y", "y2")) + .build(); + + assertThat(r2.cookies(), containsInAnyOrder(requestCookie("y", "y2"))); + } + private static StyxObservable body(String... contents) { return StyxObservable.from(Stream.of(contents) .map(content -> copiedBuffer(content, UTF_8)) diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java index d6266ecf7b..fa9ccc6f26 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java @@ -29,6 +29,7 @@ import static com.hotels.styx.api.HttpHeader.header; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; +import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; import static com.hotels.styx.api.cookies.ResponseCookie.responseCookie; import static com.hotels.styx.api.matchers.HttpHeadersMatcher.isNotCacheable; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_GATEWAY; @@ -317,6 +318,38 @@ public void encodesBodyWithCharset() throws InterruptedException, ExecutionExcep assertThat(responseUtf16.body(), is("Hello, World!".getBytes(UTF_16))); } + @Test + public void addsCookies() { + HttpResponse response = response() + .addCookies(responseCookie("x", "x1").build(), responseCookie("y", "y1").build()) + .build(); + + assertThat(response.cookies(), containsInAnyOrder(responseCookie("x", "x1").build(), responseCookie("y", "y1").build())); + } + + @Test + public void addsCookiesToExistingCookies() { + HttpResponse response = response() + .addCookies(responseCookie("z", "z1").build()) + .addCookies(responseCookie("x", "x1").build(), responseCookie("y", "y1").build()) + .build(); + + assertThat(response.cookies(), containsInAnyOrder(responseCookie("x", "x1").build(), responseCookie("y", "y1").build(), responseCookie("z", "z1").build())); + } + + @Test + public void newCookiesWithDuplicateNamesOverridePreviousOnes() { + HttpResponse r1 = response() + .cookies(responseCookie("y", "y1").build()) + .build(); + + HttpResponse r2 = r1.newBuilder().addCookies( + responseCookie("y", "y2").build()) + .build(); + + assertThat(r2.cookies(), containsInAnyOrder(responseCookie("y", "y2").build())); + } + private static HttpResponse.Builder response() { return HttpResponse.response(); } diff --git a/components/api/src/test/java/com/hotels/styx/api/cookies/RequestCookieTest.java b/components/api/src/test/java/com/hotels/styx/api/cookies/RequestCookieTest.java index a8780314b7..c7f016b0d7 100644 --- a/components/api/src/test/java/com/hotels/styx/api/cookies/RequestCookieTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/cookies/RequestCookieTest.java @@ -15,9 +15,13 @@ */ package com.hotels.styx.api.cookies; +import com.google.common.collect.ImmutableList; import org.testng.annotations.Test; +import static com.hotels.styx.api.cookies.RequestCookie.encode; import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; public class RequestCookieTest { @@ -35,4 +39,16 @@ public void acceptsOnlyNonNullName() { public void acceptsOnlyNonNullValue() { requestCookie("name", null); } + + @Test + public void doesNotEncodeDuplicateCookies() { + String encoded = encode(ImmutableList.of( + requestCookie("foo", "bar"), + requestCookie("bar", "foo"), + requestCookie("foo", "asjdfksdajf") + ) + ); + + assertThat(encoded, is("bar=foo; foo=bar")); + } } \ No newline at end of file From 8830768d51aa924e1fa413a26a30eff0bb6c4153 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Wed, 25 Jul 2018 11:15:25 +0100 Subject: [PATCH 21/26] Add missing changes --- .../com/hotels/styx/api/FullHttpResponse.java | 22 +++++++++---------- .../hotels/styx/api/FullHttpRequestTest.java | 4 ++-- .../hotels/styx/api/FullHttpResponseTest.java | 7 +++--- .../com/hotels/styx/api/HttpResponseTest.java | 1 - 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java index 9838272e62..4b5300f9aa 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java @@ -109,13 +109,13 @@ public byte[] body() { /** * Returns the message body as a String decoded with provided character set. - * + *

* Decodes the message body into a Java String object with a provided charset. * The caller must ensure the provided charset is compatible with message content * type and encoding. * - * @param charset Charset used to decode message body. - * @return Message body as a String. + * @param charset Charset used to decode message body. + * @return Message body as a String. */ @Override public String bodyAs(Charset charset) { @@ -143,11 +143,11 @@ public boolean isRedirect() { /** * Converts this response to a streaming form (HttpResponse). - * + *

* Converts this response to a HttpResponse object which represents the HTTP response as a * stream of bytes. * - * @return A streaming HttpResponse object. + * @return A streaming HttpResponse object. */ public HttpResponse toStreamingResponse() { if (this.body.length == 0) { @@ -253,7 +253,7 @@ public Builder status(HttpResponseStatus status) { /** * Sets the request body. - * + *

* This method encodes a String content to a byte array using the specified * charset, and sets the Content-Length header accordingly. * @@ -267,13 +267,13 @@ public Builder body(String content, Charset charset) { /** * Sets the response body. - * + *

* This method encodes the content to a byte array using the specified * charset, and sets the Content-Length header *if* the setContentLength * argument is true. * - * @param content response body - * @param charset Charset used for encoding response body. + * @param content response body + * @param charset Charset used for encoding response body. * @param setContentLength If true, Content-Length header is set, otherwise it is not set. * @return {@code this} */ @@ -285,12 +285,12 @@ public Builder body(String content, Charset charset, boolean setContentLength) { /** * Sets the response body. - * + *

* This method encodes the content to a byte array provided, and * sets the Content-Length header *if* the setContentLength * argument is true. * - * @param content response body + * @param content response body * @param setContentLength If true, Content-Length header is set, otherwise it is not set. * @return {@code this} */ diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java index 996f8ab87f..680bdbf07d 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java @@ -93,7 +93,7 @@ public void convertsToStreamingHttpRequestWithEmptyBody(FullHttpRequest fullRequ TestSubscriber subscriber = TestSubscriber.create(0); subscriber.requestMore(1); - ((StyxCoreObservable)streaming.body()).delegate().subscribe(subscriber); + ((StyxCoreObservable) streaming.body()).delegate().subscribe(subscriber); assertThat(subscriber.getOnNextEvents().size(), is(0)); subscriber.assertCompleted(); @@ -239,7 +239,7 @@ public void requestBodyCannotBeChangedViaStreamingRequest() { .body("original", UTF_8) .build(); - ByteBuf byteBuf = ((StyxCoreObservable)original.toStreamingRequest().body()) + ByteBuf byteBuf = ((StyxCoreObservable) original.toStreamingRequest().body()) .delegate() .toBlocking() .first(); diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java index 3792f4cb71..df62cb9c41 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java @@ -26,7 +26,6 @@ import java.util.Set; import java.util.concurrent.ExecutionException; -import static com.hotels.styx.api.FullHttpRequest.get; import static com.hotels.styx.api.FullHttpResponse.response; import static com.hotels.styx.api.HttpHeader.header; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; @@ -73,7 +72,7 @@ public void convertsToStreamingHttpResponse() throws Exception { header("Content-Length", "15"), header("HeaderName", "HeaderValue"), header("Set-Cookie", "CookieName=CookieValue") - )); + )); assertThat(streaming.cookies(), contains(responseCookie("CookieName", "CookieValue").build())); String body = streaming.toFullResponse(0x100000) @@ -330,7 +329,7 @@ public void convertsToStreamingHttpResponseWithEmptyBody(FullHttpResponse respon TestSubscriber subscriber = TestSubscriber.create(0); subscriber.requestMore(1); - ((StyxCoreObservable)streaming.body()).delegate().subscribe(subscriber); + ((StyxCoreObservable) streaming.body()).delegate().subscribe(subscriber); assertThat(subscriber.getOnNextEvents().size(), is(0)); subscriber.assertCompleted(); @@ -434,7 +433,7 @@ public void responseBodyCannotBeChangedViaStreamingMessage() { .body("original", UTF_8) .build(); - ByteBuf byteBuf = ((StyxCoreObservable)original.toStreamingResponse().body()) + ByteBuf byteBuf = ((StyxCoreObservable) original.toStreamingResponse().body()) .delegate() .toBlocking() .first(); diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java index fa9ccc6f26..be7e9c8287 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java @@ -29,7 +29,6 @@ import static com.hotels.styx.api.HttpHeader.header; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; -import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; import static com.hotels.styx.api.cookies.ResponseCookie.responseCookie; import static com.hotels.styx.api.matchers.HttpHeadersMatcher.isNotCacheable; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_GATEWAY; From 5fb51a05bca99f5c00f76825f3fb95a60076fbb2 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Wed, 25 Jul 2018 11:37:23 +0100 Subject: [PATCH 22/26] Add more tests --- .../com/hotels/styx/api/FullHttpRequestTest.java | 13 +++++++++++++ .../hotels/styx/api/FullHttpResponseTest.java | 14 ++++++++++++++ .../com/hotels/styx/api/HttpRequestTest.java | 16 +++++++++++++++- .../com/hotels/styx/api/HttpResponseTest.java | 15 +++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java index 680bdbf07d..75b01aabdf 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java @@ -517,4 +517,17 @@ public void newCookiesWithDuplicateNamesOverridePreviousOnes() { assertThat(r2.cookies(), containsInAnyOrder(requestCookie("y", "y2"))); } + @Test + public void removesCookies() { + FullHttpRequest r1 = FullHttpRequest.get("/") + .addCookies(requestCookie("x", "x1"), requestCookie("y", "y1")) + .build(); + + FullHttpRequest r2 = r1.newBuilder() + .removeCookies("x") + .removeCookies("foo") // ensure that trying to remove a non-existent cookie does not cause Exception + .build(); + + assertThat(r2.cookies(), contains(requestCookie("y", "y1"))); + } } diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java index df62cb9c41..3f5b50560a 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java @@ -503,4 +503,18 @@ public void newCookiesWithDuplicateNamesOverridePreviousOnes() { assertThat(r2.cookies(), containsInAnyOrder(responseCookie("y", "y2").build())); } + + @Test + public void removesCookies() { + FullHttpResponse r1 = response() + .addCookies(responseCookie("x", "x1").build(), responseCookie("y", "y1").build()) + .build(); + + FullHttpResponse r2 = r1.newBuilder() + .removeCookies("x") + .removeCookies("foo") // ensure that trying to remove a non-existent cookie does not cause Exception + .build(); + + assertThat(r2.cookies(), contains(responseCookie("y", "y1").build())); + } } \ No newline at end of file diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java index 4d8d8ac050..7034e6e5e0 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java @@ -430,7 +430,21 @@ public void newCookiesWithDuplicateNamesOverridePreviousOnes() { requestCookie("y", "y2")) .build(); - assertThat(r2.cookies(), containsInAnyOrder(requestCookie("y", "y2"))); + assertThat(r2.cookies(), contains(requestCookie("y", "y2"))); + } + + @Test + public void removesCookies() { + HttpRequest r1 = HttpRequest.get("/") + .addCookies(requestCookie("x", "x1"), requestCookie("y", "y1")) + .build(); + + HttpRequest r2 = r1.newBuilder() + .removeCookies("x") + .removeCookies("foo") // ensure that trying to remove a non-existent cookie does not cause Exception + .build(); + + assertThat(r2.cookies(), contains(requestCookie("y", "y1"))); } private static StyxObservable body(String... contents) { diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java index be7e9c8287..2c42389cfa 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java @@ -29,6 +29,7 @@ import static com.hotels.styx.api.HttpHeader.header; import static com.hotels.styx.api.HttpHeaderNames.CONTENT_LENGTH; +import static com.hotels.styx.api.cookies.RequestCookie.requestCookie; import static com.hotels.styx.api.cookies.ResponseCookie.responseCookie; import static com.hotels.styx.api.matchers.HttpHeadersMatcher.isNotCacheable; import static com.hotels.styx.api.messages.HttpResponseStatus.BAD_GATEWAY; @@ -349,6 +350,20 @@ public void newCookiesWithDuplicateNamesOverridePreviousOnes() { assertThat(r2.cookies(), containsInAnyOrder(responseCookie("y", "y2").build())); } + @Test + public void removesCookies() { + HttpResponse r1 = response() + .addCookies(responseCookie("x", "x1").build(), responseCookie("y", "y1").build()) + .build(); + + HttpResponse r2 = r1.newBuilder() + .removeCookies("x") + .removeCookies("foo") // ensure that trying to remove a non-existent cookie does not cause Exception + .build(); + + assertThat(r2.cookies(), contains(responseCookie("y", "y1").build())); + } + private static HttpResponse.Builder response() { return HttpResponse.response(); } From affb3e61d4aca8d18ef15fb63ba7ef81a7c6bbfb Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Wed, 25 Jul 2018 14:07:12 +0100 Subject: [PATCH 23/26] Test a new scenario --- .../java/com/hotels/styx/api/FullHttpRequest.java | 2 ++ .../main/java/com/hotels/styx/api/HttpRequest.java | 2 ++ .../java/com/hotels/styx/api/FullHttpRequestTest.java | 10 ++++++++++ .../com/hotels/styx/api/FullHttpResponseTest.java | 10 ++++++++++ .../java/com/hotels/styx/api/HttpRequestTest.java | 10 ++++++++++ .../java/com/hotels/styx/api/HttpResponseTest.java | 11 +++++++++++ 6 files changed, 45 insertions(+) diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java index 51f580505a..cd1ba95a59 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java @@ -586,6 +586,8 @@ public Builder cookies(RequestCookie... cookies) { * @return this builder */ public Builder cookies(Collection cookies) { + headers.remove(COOKIE); + if (!cookies.isEmpty()) { header(COOKIE, encode(cookies)); } diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java index 6017e2e4ef..f75d7644ef 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java @@ -620,6 +620,8 @@ public Builder cookies(RequestCookie... cookies) { * @return this builder */ public Builder cookies(Collection cookies) { + headers.remove(COOKIE); + if (!cookies.isEmpty()) { header(COOKIE, encode(cookies)); } diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java index 75b01aabdf..7de46834c2 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpRequestTest.java @@ -530,4 +530,14 @@ public void removesCookies() { assertThat(r2.cookies(), contains(requestCookie("y", "y1"))); } + + @Test + public void removesCookiesInSameBuilder() { + FullHttpRequest r1 = FullHttpRequest.get("/") + .addCookies(requestCookie("x", "x1")) + .removeCookies("x") + .build(); + + assertThat(r1.cookie("x"), isAbsent()); + } } diff --git a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java index 3f5b50560a..5743aabcaf 100644 --- a/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/FullHttpResponseTest.java @@ -517,4 +517,14 @@ public void removesCookies() { assertThat(r2.cookies(), contains(responseCookie("y", "y1").build())); } + + @Test + public void removesCookiesInSameBuilder() { + FullHttpResponse r1 = response() + .addCookies(responseCookie("x", "x1").build()) + .removeCookies("x") + .build(); + + assertThat(r1.cookie("x"), isAbsent()); + } } \ No newline at end of file diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java index 7034e6e5e0..fc17a758a8 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpRequestTest.java @@ -447,6 +447,16 @@ public void removesCookies() { assertThat(r2.cookies(), contains(requestCookie("y", "y1"))); } + @Test + public void removesCookiesInSameBuilder() { + HttpRequest r1 = HttpRequest.get("/") + .addCookies(requestCookie("x", "x1")) + .removeCookies("x") + .build(); + + assertThat(r1.cookie("x"), isAbsent()); + } + private static StyxObservable body(String... contents) { return StyxObservable.from(Stream.of(contents) .map(content -> copiedBuffer(content, UTF_8)) diff --git a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java index 2c42389cfa..e2fbde738c 100644 --- a/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java +++ b/components/api/src/test/java/com/hotels/styx/api/HttpResponseTest.java @@ -44,6 +44,7 @@ import static com.hotels.styx.api.messages.HttpResponseStatus.TEMPORARY_REDIRECT; import static com.hotels.styx.api.messages.HttpVersion.HTTP_1_0; import static com.hotels.styx.api.messages.HttpVersion.HTTP_1_1; +import static com.hotels.styx.support.matchers.IsOptional.isAbsent; import static com.hotels.styx.support.matchers.IsOptional.isValue; import static java.nio.charset.StandardCharsets.UTF_16; import static java.nio.charset.StandardCharsets.UTF_8; @@ -364,6 +365,16 @@ public void removesCookies() { assertThat(r2.cookies(), contains(responseCookie("y", "y1").build())); } + @Test + public void removesCookiesInSameBuilder() { + HttpResponse r1 = response() + .addCookies(responseCookie("x", "x1").build()) + .removeCookies("x") + .build(); + + assertThat(r1.cookie("x"), isAbsent()); + } + private static HttpResponse.Builder response() { return HttpResponse.response(); } From 352735dec450843d2b9e26ce7504f4742647cbdd Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Wed, 25 Jul 2018 14:16:24 +0100 Subject: [PATCH 24/26] changes based on pr comments --- .../src/main/java/com/hotels/styx/api/FullHttpRequest.java | 4 ++-- .../api/src/main/java/com/hotels/styx/api/HttpRequest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java index cd1ba95a59..c0306c8e21 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java @@ -595,7 +595,7 @@ public Builder cookies(Collection cookies) { } /** - * Adds cookies into this request by overwriting the value of the "Cookie" header. + * Adds cookies into the "Cookie" header. If the name matches an already existing cookie, the value will be overwritten. *

* Note that this requires decoding the current header value before re-encoding, so it is most efficient to * add all new cookies in one call to the method rather than spreading them out. @@ -608,7 +608,7 @@ public Builder addCookies(RequestCookie... cookies) { } /** - * Adds cookies into this request by overwriting the value of the "Cookie" header. + * Adds cookies into the "Cookie" header. If the name matches an already existing cookie, the value will be overwritten. *

* Note that this requires decoding the current header value before re-encoding, so it is most efficient to * add all new cookies in one call to the method rather than spreading them out. diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java index f75d7644ef..5262aac31d 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java @@ -629,7 +629,7 @@ public Builder cookies(Collection cookies) { } /** - * Adds cookies into this request by overwriting the value of the "Cookie" header. + * Adds cookies into the "Cookie" header. If the name matches an already existing cookie, the value will be overwritten. *

* Note that this requires decoding the current header value before re-encoding, so it is most efficient to * add all new cookies in one call to the method rather than spreading them out. @@ -642,7 +642,7 @@ public Builder addCookies(RequestCookie... cookies) { } /** - * Adds cookies into this request by overwriting the value of the "Cookie" header. + * Adds cookies into the "Cookie" header. If the name matches an already existing cookie, the value will be overwritten. *

* Note that this requires decoding the current header value before re-encoding, so it is most efficient to * add all new cookies in one call to the method rather than spreading them out. From 098dc3cbe4de1d284e45fe577dfeb9e8564bb34d Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Thu, 26 Jul 2018 14:02:44 +0100 Subject: [PATCH 25/26] Null-checks --- .../src/main/java/com/hotels/styx/api/FullHttpRequest.java | 3 +++ .../src/main/java/com/hotels/styx/api/FullHttpResponse.java | 5 +++++ .../api/src/main/java/com/hotels/styx/api/HttpRequest.java | 6 ++++++ .../api/src/main/java/com/hotels/styx/api/HttpResponse.java | 5 +++++ 4 files changed, 19 insertions(+) diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java index c0306c8e21..574103f6fc 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java @@ -586,6 +586,7 @@ public Builder cookies(RequestCookie... cookies) { * @return this builder */ public Builder cookies(Collection cookies) { + requireNonNull(cookies); headers.remove(COOKIE); if (!cookies.isEmpty()) { @@ -617,6 +618,7 @@ public Builder addCookies(RequestCookie... cookies) { * @return this builder */ public Builder addCookies(Collection cookies) { + requireNonNull(cookies); Set currentCookies = decode(headers.get(COOKIE)); List combinedCookies = new ArrayList<>(currentCookies.size() + cookies.size()); @@ -642,6 +644,7 @@ public Builder removeCookies(String... names) { * @return this builder */ public Builder removeCookies(Collection names) { + requireNonNull(names); return removeCookiesIf(toSet(names)::contains); } diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java index 4b5300f9aa..fd171f5c66 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpResponse.java @@ -355,6 +355,7 @@ public Builder cookies(ResponseCookie... cookies) { * @return this builder */ public Builder cookies(Collection cookies) { + requireNonNull(cookies); headers.remove(SET_COOKIE); return addCookies(cookies); } @@ -376,6 +377,8 @@ public Builder addCookies(ResponseCookie... cookies) { * @return this builder */ public Builder addCookies(Collection cookies) { + requireNonNull(cookies); + if (cookies.isEmpty()) { return this; } @@ -404,6 +407,8 @@ public Builder removeCookies(String... names) { * @return this builder */ public Builder removeCookies(Collection names) { + requireNonNull(names); + if (names.isEmpty()) { return this; } diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java index 5262aac31d..53104ca914 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java @@ -620,6 +620,8 @@ public Builder cookies(RequestCookie... cookies) { * @return this builder */ public Builder cookies(Collection cookies) { + requireNonNull(cookies); + headers.remove(COOKIE); if (!cookies.isEmpty()) { @@ -651,6 +653,8 @@ public Builder addCookies(RequestCookie... cookies) { * @return this builder */ public Builder addCookies(Collection cookies) { + requireNonNull(cookies); + Set currentCookies = decode(headers.get(COOKIE)); List combinedCookies = new ArrayList<>(currentCookies.size() + cookies.size()); @@ -676,6 +680,8 @@ public Builder removeCookies(String... names) { * @return this builder */ public Builder removeCookies(Collection names) { + requireNonNull(names); + return removeCookiesIf(toSet(names)::contains); } diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java index 9b9aa6727a..e804111ade 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpResponse.java @@ -342,6 +342,7 @@ public Builder cookies(ResponseCookie... cookies) { * @return this builder */ public Builder cookies(Collection cookies) { + requireNonNull(cookies); headers.remove(SET_COOKIE); return addCookies(cookies); } @@ -363,6 +364,8 @@ public Builder addCookies(ResponseCookie... cookies) { * @return this builder */ public Builder addCookies(Collection cookies) { + requireNonNull(cookies); + if (cookies.isEmpty()) { return this; } @@ -391,6 +394,8 @@ public Builder removeCookies(String... names) { * @return this builder */ public Builder removeCookies(Collection names) { + requireNonNull(names); + if (names.isEmpty()) { return this; } From 0b7f3a74c421ad5b042a3610418152e4428ce950 Mon Sep 17 00:00:00 2001 From: Kyle Vosper Date: Thu, 26 Jul 2018 15:25:30 +0100 Subject: [PATCH 26/26] Changes based on PR comments --- .../com/hotels/styx/api/FullHttpRequest.java | 33 +++++++++---------- .../java/com/hotels/styx/api/HttpRequest.java | 33 +++++++++---------- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java index 574103f6fc..7bc8f66c3f 100644 --- a/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/FullHttpRequest.java @@ -24,14 +24,13 @@ import java.net.InetSocketAddress; import java.nio.charset.Charset; -import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; -import java.util.stream.Collectors; import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; @@ -56,6 +55,7 @@ import static java.util.Objects.requireNonNull; import static java.util.UUID.randomUUID; import static java.util.stream.Collectors.toList; +import static java.util.stream.Stream.concat; /** * HTTP request with a fully aggregated/decoded body. @@ -327,13 +327,9 @@ public HttpRequest toStreamingRequest() { * @return cookies */ public Set cookies() { - // Note: there should only be one "Cookie" header, but we check for multiples just in case - // the alternative would be to respond with a 400 Bad Request status if multiple "Cookie" headers were detected - - return headers.getAll(COOKIE).stream() + return headers.get(COOKIE) .map(RequestCookie::decode) - .flatMap(Collection::stream) - .collect(Collectors.toSet()); + .orElseGet(Collections::emptySet); } /** @@ -620,11 +616,9 @@ public Builder addCookies(RequestCookie... cookies) { public Builder addCookies(Collection cookies) { requireNonNull(cookies); Set currentCookies = decode(headers.get(COOKIE)); + List newCookies = concat(cookies.stream(), currentCookies.stream()).collect(toList()); - List combinedCookies = new ArrayList<>(currentCookies.size() + cookies.size()); - combinedCookies.addAll(cookies); - combinedCookies.addAll(currentCookies); - return cookies(combinedCookies); + return cookies(newCookies); } /** @@ -706,6 +700,7 @@ public Builder disableValidation() { public FullHttpRequest build() { if (validate) { ensureContentLengthIsValid(); + requireNotDuplicatedHeader(COOKIE); ensureMethodIsValid(); setHostHeader(); } @@ -727,13 +722,17 @@ private boolean isMethodValid() { } private void ensureContentLengthIsValid() { - List contentLengths = headers.build().getAll(CONTENT_LENGTH); + requireNotDuplicatedHeader(CONTENT_LENGTH).ifPresent(contentLength -> + checkArgument(isInteger(contentLength), "Invalid Content-Length found. %s", contentLength) + ); + } - checkArgument(contentLengths.size() <= 1, "Duplicate Content-Length found. %s", contentLengths); + private Optional requireNotDuplicatedHeader(CharSequence headerName) { + List headerValues = headers.build().getAll(headerName); - if (contentLengths.size() == 1) { - checkArgument(isInteger(contentLengths.get(0)), "Invalid Content-Length found. %s", contentLengths.get(0)); - } + checkArgument(headerValues.size() <= 1, "Duplicate %s found. %s", headerName, headerValues); + + return headerValues.isEmpty() ? Optional.empty() : Optional.of(headerValues.get(0)); } private static boolean isInteger(String contentLength) { diff --git a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java index 53104ca914..e52168b0c0 100644 --- a/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java +++ b/components/api/src/main/java/com/hotels/styx/api/HttpRequest.java @@ -24,14 +24,13 @@ import rx.Observable; import java.net.InetSocketAddress; -import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; -import java.util.stream.Collectors; import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; @@ -64,6 +63,7 @@ import static java.util.Objects.requireNonNull; import static java.util.UUID.randomUUID; import static java.util.stream.Collectors.toList; +import static java.util.stream.Stream.concat; /** * HTTP request with a fully aggregated/decoded body. @@ -356,13 +356,9 @@ private static byte[] decodeAndRelease(CompositeByteBuf aggregate) { * @return cookies */ public Set cookies() { - // Note: there should only be one "Cookie" header, but we check for multiples just in case - // the alternative would be to respond with a 400 Bad Request status if multiple "Cookie" headers were detected - - return headers.getAll(COOKIE).stream() + return headers.get(COOKIE) .map(RequestCookie::decode) - .flatMap(Collection::stream) - .collect(Collectors.toSet()); + .orElseGet(Collections::emptySet); } /** @@ -656,11 +652,9 @@ public Builder addCookies(Collection cookies) { requireNonNull(cookies); Set currentCookies = decode(headers.get(COOKIE)); + List newCookies = concat(cookies.stream(), currentCookies.stream()).collect(toList()); - List combinedCookies = new ArrayList<>(currentCookies.size() + cookies.size()); - combinedCookies.addAll(cookies); - combinedCookies.addAll(currentCookies); - return cookies(combinedCookies); + return cookies(newCookies); } /** @@ -713,6 +707,7 @@ private static Set toSet(Collection collection) { public HttpRequest build() { if (validate) { ensureContentLengthIsValid(); + requireNotDuplicatedHeader(COOKIE); ensureMethodIsValid(); setHostHeader(); } @@ -734,13 +729,17 @@ private boolean isMethodValid() { } private void ensureContentLengthIsValid() { - List contentLengths = headers.build().getAll(CONTENT_LENGTH); + requireNotDuplicatedHeader(CONTENT_LENGTH).ifPresent(contentLength -> + checkArgument(isInteger(contentLength), "Invalid Content-Length found. %s", contentLength) + ); + } - checkArgument(contentLengths.size() <= 1, "Duplicate Content-Length found. %s", contentLengths); + private Optional requireNotDuplicatedHeader(CharSequence headerName) { + List headerValues = headers.build().getAll(headerName); - if (contentLengths.size() == 1) { - checkArgument(isInteger(contentLengths.get(0)), "Invalid Content-Length found. %s", contentLengths.get(0)); - } + checkArgument(headerValues.size() <= 1, "Duplicate %s found. %s", headerName, headerValues); + + return headerValues.isEmpty() ? Optional.empty() : Optional.of(headerValues.get(0)); } private static boolean isInteger(String contentLength) {