diff --git a/plugin-examples/pom.xml b/plugin-examples/pom.xml index de2e4765cd..4b02cf2aea 100644 --- a/plugin-examples/pom.xml +++ b/plugin-examples/pom.xml @@ -2,41 +2,29 @@ + 4.0.0 - jar - com.hotels.styx plugin-examples Styx proxy plugin examples - 0.0.1-SNAPSHOT + com.hotels.styx + 1.0-SNAPSHOT - UTF-8 - UTF-8 - 1.8 - 1.8 + ${project.parent.basedir} + 1.8 + 1.8 - - 0.9-SNAPSHOT 1.1.6 2.9.9 - - 6.8 - - - 3.0 - 2.4 - 2.6 - 2.16 + 6.14.3 - com.hotels.styx styx-api - ${styx.version} - + 1.0-SNAPSHOT provided @@ -68,138 +56,71 @@ org.testng testng ${testng.version} - test - - - junit - junit - - + + + src/main/resources + true + + **/* + + + + + + src/test/resources + + + org.apache.maven.plugins - maven-resources-plugin - ${maven-resources-plugin.version} + maven-jar-plugin + + + + test-jar + + + - + org.apache.maven.plugins maven-compiler-plugin - ${maven-compiler-plugin.version} + 3.3 - ${maven.compiler.source} - ${maven.compiler.target} + ${java.source} + ${java.target} - - - - org.apache.maven.plugins - maven-jar-plugin - ${maven-jar-plugin.version} - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven-surefire-plugin.version} - - - - - org.codehaus.groovy.maven - gmaven-plugin - 1.0 + - get-local-ip - initialize - - execute - - - - - commons-lang - commons-lang - 2.4 - - - - project.properties.localIP = java.net.InetAddress.getLocalHost().getHostAddress() - - + default-compile + none + + + + default-testCompile + none + + + java-compile + compile + compile + + + java-test-compile + test-compile + testCompile - - - - src/main/resources - true - - - - - - styx - - false - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - - org.apache.maven.plugins - maven-shade-plugin - 2.4.3 - - - package - - shade - - - true - jar-with-dependencies - ${pluginsDir} - - - - *:* - - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - - - - - - diff --git a/plugin-examples/src/main/java/com/hotels/styx/EarlyReturnExamplePlugin.java b/plugin-examples/src/main/java/com/hotels/styx/EarlyReturnExamplePlugin.java new file mode 100644 index 0000000000..261e84f381 --- /dev/null +++ b/plugin-examples/src/main/java/com/hotels/styx/EarlyReturnExamplePlugin.java @@ -0,0 +1,50 @@ +/* + Copyright (C) 2013-2019 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; + +import com.hotels.styx.api.Eventual; +import com.hotels.styx.api.HttpResponse; +import com.hotels.styx.api.LiveHttpRequest; +import com.hotels.styx.api.LiveHttpResponse; +import com.hotels.styx.api.plugins.spi.Plugin; + +import static com.hotels.styx.api.HttpHeaderNames.CONTENT_TYPE; +import static com.hotels.styx.api.HttpResponseStatus.OK; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * This shows an example of a plugin that responds immediately to a received request, instead of proxying it downstream. + * It takes a request and if the HTTP request contains a header named `X-Respond`, then it returns an eventual wrapping + * the HTTP response + * The eventual object is returned immediately, even if the response has not yet arrived. + */ + +public class EarlyReturnExamplePlugin implements Plugin { + + @Override + public Eventual intercept(LiveHttpRequest request, Chain chain) { + if (request.header("X-Respond").isPresent()) { + return Eventual.of(HttpResponse.response(OK) + .header(CONTENT_TYPE, "text/plain; charset=utf-8") + .body("Responding from plugin", UTF_8) + .build() + .stream()); + } else { + return chain.proceed(request); + } + } +} + diff --git a/plugin-examples/src/main/java/com/hotels/styx/ModifyContentByAggregationExamplePlugin.java b/plugin-examples/src/main/java/com/hotels/styx/ModifyContentByAggregationExamplePlugin.java new file mode 100644 index 0000000000..7dfb0c8d6c --- /dev/null +++ b/plugin-examples/src/main/java/com/hotels/styx/ModifyContentByAggregationExamplePlugin.java @@ -0,0 +1,72 @@ +/* + Copyright (C) 2013-2019 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; + +import com.hotels.styx.api.Eventual; +import com.hotels.styx.api.HttpResponse; +import com.hotels.styx.api.LiveHttpRequest; +import com.hotels.styx.api.LiveHttpResponse; +import com.hotels.styx.api.plugins.spi.Plugin; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; + +/** + * You can replace content after aggregating it in memory. + * For example, if the message content contains a JSON object and you need to modify the object somehow. + * You can aggregate the live HTTP message into a full HTTP message. Transform the content into a full message context + * and convert the results back to live HTTP message + * + *This can be used when you need the entire content in order to operate on it. + *Please note that this uses more heap space as the full response is transiently stored in the heap. + */ + +public class ModifyContentByAggregationExamplePlugin implements Plugin { + private final Config config; + + public ModifyContentByAggregationExamplePlugin(Config config) { + this.config = requireNonNull(config); + } + + @Override + public Eventual intercept(LiveHttpRequest request, Chain chain) { + return chain.proceed(request) + .flatMap(response -> response.aggregate(10000)) + .map(response -> { + String body = response.bodyAs(UTF_8); + return response.newBuilder() + .body(body + config.extraText(), UTF_8) + .build(); + }) + .map(HttpResponse::stream); + } + + /** + * Config for example plugin. + */ + public static class Config { + private final String extraText; + + public Config(String extraText) { + this.extraText = requireNonNull(extraText); + } + + public String extraText() { + return extraText; + } + } +} + diff --git a/plugin-examples/src/main/java/com/hotels/styx/ExamplePlugin.java b/plugin-examples/src/main/java/com/hotels/styx/ModifyHeadersExamplePlugin.java similarity index 94% rename from plugin-examples/src/main/java/com/hotels/styx/ExamplePlugin.java rename to plugin-examples/src/main/java/com/hotels/styx/ModifyHeadersExamplePlugin.java index 65165c5abe..9509b42fe1 100644 --- a/plugin-examples/src/main/java/com/hotels/styx/ExamplePlugin.java +++ b/plugin-examples/src/main/java/com/hotels/styx/ModifyHeadersExamplePlugin.java @@ -32,15 +32,15 @@ * You can change it in whatever way you like (provided it still implements the Plugin interface), * and should rename it to something relevant to your project. */ -public class ExamplePlugin implements Plugin { - private final ExamplePluginConfig config; +public class ModifyHeadersExamplePlugin implements Plugin { + private final ModifyHeadersExamplePluginConfig config; /** * The plugin will be constructed by your plugin factory, so the constructor can take whatever form you like. * * @param config */ - public ExamplePlugin(ExamplePluginConfig config) { + public ModifyHeadersExamplePlugin(ModifyHeadersExamplePluginConfig config) { this.config = requireNonNull(config); } @@ -84,4 +84,4 @@ public Eventual intercept(LiveHttpRequest request, Chain chain public Map adminInterfaceHandlers() { return emptyMap(); } -} \ No newline at end of file +} diff --git a/plugin-examples/src/main/java/com/hotels/styx/ExamplePluginConfig.java b/plugin-examples/src/main/java/com/hotels/styx/ModifyHeadersExamplePluginConfig.java similarity index 95% rename from plugin-examples/src/main/java/com/hotels/styx/ExamplePluginConfig.java rename to plugin-examples/src/main/java/com/hotels/styx/ModifyHeadersExamplePluginConfig.java index 9dabf4ac03..0487220551 100644 --- a/plugin-examples/src/main/java/com/hotels/styx/ExamplePluginConfig.java +++ b/plugin-examples/src/main/java/com/hotels/styx/ModifyHeadersExamplePluginConfig.java @@ -23,7 +23,7 @@ * * You should rename it to something relevant to your project. */ -public class ExamplePluginConfig { +public class ModifyHeadersExamplePluginConfig { private final String requestHeaderValue; private final String responseHeaderValue; @@ -33,7 +33,7 @@ public class ExamplePluginConfig { * @param requestHeaderValue value from config * @param responseHeaderValue value from config */ - public ExamplePluginConfig( + public ModifyHeadersExamplePluginConfig( @JsonProperty("requestHeaderValue") String requestHeaderValue, @JsonProperty("responseHeaderValue") String responseHeaderValue) { this.requestHeaderValue = requestHeaderValue; diff --git a/plugin-examples/src/main/java/com/hotels/styx/ExamplePluginFactory.java b/plugin-examples/src/main/java/com/hotels/styx/ModifyHeadersExamplePluginFactory.java similarity index 86% rename from plugin-examples/src/main/java/com/hotels/styx/ExamplePluginFactory.java rename to plugin-examples/src/main/java/com/hotels/styx/ModifyHeadersExamplePluginFactory.java index c75f32e124..d098beae54 100644 --- a/plugin-examples/src/main/java/com/hotels/styx/ExamplePluginFactory.java +++ b/plugin-examples/src/main/java/com/hotels/styx/ModifyHeadersExamplePluginFactory.java @@ -21,7 +21,7 @@ /** * The factory is used to construct your plugin. You can do this however you like. */ -public class ExamplePluginFactory implements PluginFactory { +public class ModifyHeadersExamplePluginFactory implements PluginFactory { /** * The environment object will provide this plugin with the configuration you created in the YAML file. * @@ -37,8 +37,8 @@ public Plugin create(PluginFactory.Environment environment) { * However, using a separate config object has the advantage that your PluginFactory is * free to select a Plugin implementation at runtime. */ - ExamplePluginConfig config = environment.pluginConfig(ExamplePluginConfig.class); + ModifyHeadersExamplePluginConfig config = environment.pluginConfig(ModifyHeadersExamplePluginConfig.class); - return new ExamplePlugin(config); + return new ModifyHeadersExamplePlugin(config); } } diff --git a/plugin-examples/src/main/java/com/hotels/styx/ReplaceLiveContentExampleConfig.java b/plugin-examples/src/main/java/com/hotels/styx/ReplaceLiveContentExampleConfig.java new file mode 100644 index 0000000000..7f1f0cb647 --- /dev/null +++ b/plugin-examples/src/main/java/com/hotels/styx/ReplaceLiveContentExampleConfig.java @@ -0,0 +1,33 @@ +/* + Copyright (C) 2013-2019 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; + +import static java.util.Objects.requireNonNull; + +/** + * Config for example plugin. + */ +public class ReplaceLiveContentExampleConfig { + private String replacement; + + public ReplaceLiveContentExampleConfig(String replacement) { + this.replacement = requireNonNull(replacement); + } + + public String replacement() { + return replacement; + } +} diff --git a/plugin-examples/src/main/java/com/hotels/styx/ReplaceLiveContentExamplePlugin.java b/plugin-examples/src/main/java/com/hotels/styx/ReplaceLiveContentExamplePlugin.java new file mode 100644 index 0000000000..159373e959 --- /dev/null +++ b/plugin-examples/src/main/java/com/hotels/styx/ReplaceLiveContentExamplePlugin.java @@ -0,0 +1,53 @@ +/* + Copyright (C) 2013-2019 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; + +import com.hotels.styx.api.ByteStream; +import com.hotels.styx.api.Eventual; +import com.hotels.styx.api.LiveHttpRequest; +import com.hotels.styx.api.LiveHttpResponse; +import com.hotels.styx.api.plugins.spi.Plugin; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; + +/** + * You can replace without aggregating it first, if the replacement does not depend on the original contents. + * For example, if you need to replace a message body based on information in the message headers , regardless of + * the original message body. (E.g you need to add a HTTP message body based on a HTTP error code) + *

+ * You can transform a live HTTP message body using the `replaceWith` Bytestream operator such as shown in the example below. + *

+ * This can be used to replace a message body without having to look into it, which will also save heap space as the + * live upstream response body is never stored in the heap in full. + */ + +public class ReplaceLiveContentExamplePlugin implements Plugin { + private final ReplaceLiveContentExampleConfig config; + + public ReplaceLiveContentExamplePlugin(ReplaceLiveContentExampleConfig config) { + this.config = requireNonNull(config); + } + + @Override + public Eventual intercept(LiveHttpRequest request, Chain chain) { + return chain.proceed(request) + .map(response -> response.newBuilder() + .body(body -> body.replaceWith(ByteStream.from(config.replacement(), UTF_8))) + .build()); + } +} + diff --git a/plugin-examples/src/test/java/com/hotels/styx/EarlyReturnExamplePluginTest.java b/plugin-examples/src/test/java/com/hotels/styx/EarlyReturnExamplePluginTest.java new file mode 100644 index 0000000000..374112096e --- /dev/null +++ b/plugin-examples/src/test/java/com/hotels/styx/EarlyReturnExamplePluginTest.java @@ -0,0 +1,52 @@ +/* + Copyright (C) 2013-2019 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; + +import com.hotels.styx.api.*; +import org.testng.annotations.Test; +import reactor.core.publisher.Mono; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + + +/** + * This tests the behaviours added in the EarlyReturnExamplePlugin. + */ + +public class EarlyReturnExamplePluginTest { + + @Test + public void returnsEarlyWhenHeaderIsPresent() { + + EarlyReturnExamplePlugin plugin = new EarlyReturnExamplePlugin(); + + LiveHttpRequest request = LiveHttpRequest.get("/") + .header("X-Respond", "foo") + .build(); + + HttpInterceptor.Chain chain = request1 -> Eventual.of(LiveHttpResponse.response().build()); + + Eventual eventualLive = plugin.intercept(request, chain); + Eventual eventual = eventualLive.flatMap(response -> response.aggregate(100)); + + HttpResponse response = Mono.from(eventual).block(); + + assertThat(response.bodyAs(UTF_8), is("Responding from plugin")); + } + +} diff --git a/plugin-examples/src/test/java/com/hotels/styx/ModifyContentByAggregationExamplePluginTest.java b/plugin-examples/src/test/java/com/hotels/styx/ModifyContentByAggregationExamplePluginTest.java new file mode 100644 index 0000000000..472060fe03 --- /dev/null +++ b/plugin-examples/src/test/java/com/hotels/styx/ModifyContentByAggregationExamplePluginTest.java @@ -0,0 +1,54 @@ +/* + Copyright (C) 2013-2019 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; + +import com.hotels.styx.ModifyContentByAggregationExamplePlugin.Config; +import com.hotels.styx.api.*; +import org.testng.annotations.Test; +import reactor.core.publisher.Mono; + +import static com.hotels.styx.api.HttpResponse.response; +import static com.hotels.styx.api.LiveHttpRequest.get; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + + +public class ModifyContentByAggregationExamplePluginTest { + + @Test + public void modifiesContent() { + // Set up + Config config = new Config("MyExtraText"); + ModifyContentByAggregationExamplePlugin plugin = new ModifyContentByAggregationExamplePlugin(config); + + LiveHttpRequest request = get("/").build(); + + HttpInterceptor.Chain chain = anyRequest -> Eventual.of(response() + .body("OriginalBody", UTF_8) + .build() + .stream()); + + // Execution + + HttpResponse response = Mono.from(plugin.intercept(request, chain) + .flatMap(liveResponse -> liveResponse.aggregate(100))) + .block(); + + // Assertion + assertThat(response.bodyAs(UTF_8), is("OriginalBodyMyExtraText")); + } +} diff --git a/plugin-examples/src/test/java/com/hotels/styx/ExamplePluginTest.java b/plugin-examples/src/test/java/com/hotels/styx/ModifyHeadersExamplePluginTest.java similarity index 75% rename from plugin-examples/src/test/java/com/hotels/styx/ExamplePluginTest.java rename to plugin-examples/src/test/java/com/hotels/styx/ModifyHeadersExamplePluginTest.java index e8c072be0c..77a816d9b8 100644 --- a/plugin-examples/src/test/java/com/hotels/styx/ExamplePluginTest.java +++ b/plugin-examples/src/test/java/com/hotels/styx/ModifyHeadersExamplePluginTest.java @@ -20,6 +20,7 @@ import com.hotels.styx.api.LiveHttpRequest; import com.hotels.styx.api.LiveHttpResponse; import org.testng.annotations.Test; +import reactor.core.publisher.Mono; import static com.hotels.styx.api.HttpResponseStatus.OK; import static com.hotels.styx.api.LiveHttpRequest.get; @@ -30,15 +31,15 @@ /** * This is a unit test for your plugin. Please change it to test the behaviour you expect your plugin to exhibit. */ -public class ExamplePluginTest { - private final ExamplePluginConfig config = new ExamplePluginConfig("foo", "bar"); - private final ExamplePlugin plugin = new ExamplePlugin(config); +public class ModifyHeadersExamplePluginTest { + private final ModifyHeadersExamplePluginConfig config = new ModifyHeadersExamplePluginConfig("foo", "bar"); + private final ModifyHeadersExamplePlugin plugin = new ModifyHeadersExamplePlugin(config); /** - * This tests the behaviours added in the example plugin. + * This tests the behaviours added in the ModifyHeadersExamplePlugin. */ @Test - public void addsExtraHeaders() throws Exception { + public void addsExtraHeaders() { // a simple way to mock the downstream system HttpInterceptor.Chain chain = request -> { assertThat(request.header("myRequestHeader").orElse(null), is("foo")); @@ -51,9 +52,9 @@ public void addsExtraHeaders() throws Exception { .build(); - // since this is a test, we want to wait for the response, so we call CompletableFuture.get - LiveHttpResponse response = plugin.intercept(request, chain).asCompletableFuture().get(); + // since this is a test, we want to wait for the response + LiveHttpResponse response = Mono.from(plugin.intercept(request, chain)).block(); - assertThat(response.header("myResponseheader").orElse(null), is("bar")); + assertThat(response.header("myResponseHeader").orElse(null), is("bar")); } } diff --git a/plugin-examples/src/test/java/com/hotels/styx/ReplaceLiveContentExamplePluginTest.java b/plugin-examples/src/test/java/com/hotels/styx/ReplaceLiveContentExamplePluginTest.java new file mode 100644 index 0000000000..baf92935ef --- /dev/null +++ b/plugin-examples/src/test/java/com/hotels/styx/ReplaceLiveContentExamplePluginTest.java @@ -0,0 +1,48 @@ +/* + Copyright (C) 2013-2019 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; + +import com.hotels.styx.api.*; +import org.testng.annotations.Test; +import reactor.core.publisher.Mono; + +import static com.hotels.styx.api.LiveHttpRequest.get; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + + +public class ReplaceLiveContentExamplePluginTest { + + @Test + public void replacesLiveContent() { + // Set up + ReplaceLiveContentExampleConfig config = new ReplaceLiveContentExampleConfig("myNewContent"); + + ReplaceLiveContentExamplePlugin plugin = new ReplaceLiveContentExamplePlugin(config); + + LiveHttpRequest request = get("/").build(); + HttpInterceptor.Chain chain = request1 -> Eventual.of(LiveHttpResponse.response().build()); + + // Execution + HttpResponse response = Mono.from(plugin.intercept(request, chain) + .flatMap(liveResponse -> liveResponse.aggregate(100))) + .block(); + + // Assertion + assertThat(response.bodyAs(UTF_8), is("myNewContent")); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8af165b935..b0156356a3 100755 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,7 @@ components support system-tests + plugin-examples