diff --git a/library/java/io/envoyproxy/envoymobile/engine/AndroidEngineImpl.java b/library/java/io/envoyproxy/envoymobile/engine/AndroidEngineImpl.java index c14d50bd6d..15e0c857d2 100644 --- a/library/java/io/envoyproxy/envoymobile/engine/AndroidEngineImpl.java +++ b/library/java/io/envoyproxy/envoymobile/engine/AndroidEngineImpl.java @@ -27,12 +27,12 @@ public EnvoyHTTPStream startStream(EnvoyHTTPCallbacks callbacks) { return envoyEngine.startStream(callbacks); } - @Override - public int runWithConfig(String configurationYAML, String logLevel) { + public int runWithTemplate(String configurationYAML, EnvoyConfiguration envoyConfiguration, + String logLevel) { // re-enable lifecycle-based stat flushing when https://github.com/lyft/envoy-mobile/issues/748 // gets fixed. AndroidAppLifecycleMonitor monitor = new AndroidAppLifecycleMonitor(); // application.registerActivityLifecycleCallbacks(monitor); - return envoyEngine.runWithConfig(configurationYAML, logLevel); + return envoyEngine.runWithTemplate(configurationYAML, envoyConfiguration, logLevel); } @Override diff --git a/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java b/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java index 6bc74892fe..389dab1d37 100644 --- a/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java +++ b/library/java/io/envoyproxy/envoymobile/engine/EnvoyConfiguration.java @@ -39,8 +39,8 @@ public class EnvoyConfiguration { * @param appId the App ID of the App using this Envoy Client. * @param virtualClusters the JSON list of virtual cluster configs. * @param nativeFilterChain the configuration for native filters. - * @param httpPlatformFilterFactories the configuration for platform filters. - * @param stringAccesssors platform string accessors to register. + * @param httpPlatformFilterFactories the configuration for platform filters. + * @param stringAccessors platform string accessors to register. */ public EnvoyConfiguration(String statsDomain, int connectTimeoutSeconds, int dnsRefreshSeconds, int dnsFailureRefreshSecondsBase, int dnsFailureRefreshSecondsMax, diff --git a/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngine.java b/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngine.java index 35c532d595..bfcc0dcdda 100644 --- a/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngine.java +++ b/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngine.java @@ -1,8 +1,6 @@ package io.envoyproxy.envoymobile.engine; import io.envoyproxy.envoymobile.engine.types.EnvoyHTTPCallbacks; -import io.envoyproxy.envoymobile.engine.types.EnvoyLogger; -import io.envoyproxy.envoymobile.engine.types.EnvoyOnEngineRunning; import io.envoyproxy.envoymobile.engine.types.EnvoyStringAccessor; import java.util.Map; @@ -25,11 +23,15 @@ public interface EnvoyEngine { /** * Run the Envoy engine with the provided yaml string and log level. * + * The envoyConfiguration is used to resolve the configurationYAML. + * * @param configurationYAML The configuration yaml with which to start Envoy. + * @param envoyConfiguration The EnvoyConfiguration used to start Envoy. * @param logLevel The log level to use when starting Envoy. * @return A status indicating if the action was successful. */ - int runWithConfig(String configurationYAML, String logLevel); + int runWithTemplate(String configurationYAML, EnvoyConfiguration envoyConfiguration, + String logLevel); /** * Run the Envoy engine with the provided EnvoyConfiguration and log level. diff --git a/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java b/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java index 5719f92d61..75fa5fe479 100644 --- a/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java +++ b/library/java/io/envoyproxy/envoymobile/engine/EnvoyEngineImpl.java @@ -50,18 +50,32 @@ public void terminate() { /** * Run the Envoy engine with the provided yaml string and log level. * + * The envoyConfiguration is used to resolve the configurationYAML. + * * @param configurationYAML The configuration yaml with which to start Envoy. - * @param logLevel The log level to use when starting Envoy. + * @param envoyConfiguration The EnvoyConfiguration used to start Envoy. + * @param logLevel The log level to use when starting Envoy. * @return A status indicating if the action was successful. */ @Override - public int runWithConfig(String configurationYAML, String logLevel) { - try { - return JniLibrary.runEngine(this.engineHandle, configurationYAML, logLevel); - } catch (Throwable throwable) { - // TODO: Need to have a way to log the exception somewhere. - return ENVOY_FAILURE; + public int runWithTemplate(String configurationYAML, EnvoyConfiguration envoyConfiguration, + String logLevel) { + for (EnvoyHTTPFilterFactory filterFactory : envoyConfiguration.httpPlatformFilterFactories) { + JniLibrary.registerFilterFactory(filterFactory.getFilterName(), + new JvmFilterFactoryContext(filterFactory)); + } + + for (Map.Entry entry : + envoyConfiguration.stringAccessors.entrySet()) { + JniLibrary.registerStringAccessor(entry.getKey(), + new JvmStringAccessorContext(entry.getValue())); } + + return runWithResolvedYAML( + envoyConfiguration.resolveTemplate(configurationYAML, JniLibrary.statsSinkTemplateString(), + JniLibrary.platformFilterTemplateString(), + JniLibrary.nativeFilterTemplateString()), + logLevel); } /** @@ -84,11 +98,20 @@ public int runWithConfig(EnvoyConfiguration envoyConfiguration, String logLevel) new JvmStringAccessorContext(entry.getValue())); } - return runWithConfig(envoyConfiguration.resolveTemplate( - JniLibrary.templateString(), JniLibrary.statsSinkTemplateString(), - JniLibrary.platformFilterTemplateString(), - JniLibrary.nativeFilterTemplateString()), - logLevel); + return runWithResolvedYAML( + envoyConfiguration.resolveTemplate( + JniLibrary.templateString(), JniLibrary.statsSinkTemplateString(), + JniLibrary.platformFilterTemplateString(), JniLibrary.nativeFilterTemplateString()), + logLevel); + } + + private int runWithResolvedYAML(String configurationYAML, String logLevel) { + try { + return JniLibrary.runEngine(this.engineHandle, configurationYAML, logLevel); + } catch (Throwable throwable) { + // TODO: Need to have a way to log the exception somewhere. + return ENVOY_FAILURE; + } } /** diff --git a/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt b/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt index 54ed12e45f..52939c49a8 100644 --- a/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt +++ b/library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt @@ -223,7 +223,17 @@ open class EngineBuilder( fun build(): Engine { return when (configuration) { is Custom -> { - EngineImpl(engineType(), configuration.yaml, logLevel) + EngineImpl( + engineType(), + EnvoyConfiguration( + statsDomain, connectTimeoutSeconds, + dnsRefreshSeconds, dnsFailureRefreshSecondsBase, dnsFailureRefreshSecondsMax, + statsFlushSeconds, appVersion, appId, virtualClusters, nativeFilterChain, + platformFilterChain, stringAccessors + ), + configuration.yaml, + logLevel + ) } is Standard -> { EngineImpl( diff --git a/library/kotlin/io/envoyproxy/envoymobile/EngineImpl.kt b/library/kotlin/io/envoyproxy/envoymobile/EngineImpl.kt index 482aaef318..402976c43e 100644 --- a/library/kotlin/io/envoyproxy/envoymobile/EngineImpl.kt +++ b/library/kotlin/io/envoyproxy/envoymobile/EngineImpl.kt @@ -6,9 +6,9 @@ import io.envoyproxy.envoymobile.engine.EnvoyEngine /** * An implementation of {@link Engine}. */ -class EngineImpl internal constructor( +class EngineImpl constructor( internal val envoyEngine: EnvoyEngine, - internal val envoyConfiguration: EnvoyConfiguration?, + internal val envoyConfiguration: EnvoyConfiguration, internal val configurationYAML: String?, internal val logLevel: LogLevel ) : Engine { @@ -22,17 +22,11 @@ class EngineImpl internal constructor( logLevel: LogLevel = LogLevel.INFO ) : this(envoyEngine, envoyConfiguration, null, logLevel) - constructor( - envoyEngine: EnvoyEngine, - configurationYAML: String, - logLevel: LogLevel = LogLevel.INFO - ) : this(envoyEngine, null, configurationYAML, logLevel) - init { streamClient = StreamClientImpl(envoyEngine) pulseClient = PulseClientImpl(envoyEngine) - if (envoyConfiguration == null) { - envoyEngine.runWithConfig(configurationYAML, logLevel.level) + if (configurationYAML != null) { + envoyEngine.runWithTemplate(configurationYAML, envoyConfiguration, logLevel.level) } else { envoyEngine.runWithConfig(envoyConfiguration, logLevel.level) } diff --git a/library/kotlin/io/envoyproxy/envoymobile/mocks/MockEnvoyEngine.kt b/library/kotlin/io/envoyproxy/envoymobile/mocks/MockEnvoyEngine.kt index 4656bcc2b5..16ffeaf73f 100644 --- a/library/kotlin/io/envoyproxy/envoymobile/mocks/MockEnvoyEngine.kt +++ b/library/kotlin/io/envoyproxy/envoymobile/mocks/MockEnvoyEngine.kt @@ -12,7 +12,11 @@ import io.envoyproxy.envoymobile.engine.types.EnvoyStringAccessor internal class MockEnvoyEngine : EnvoyEngine { override fun runWithConfig(envoyConfiguration: EnvoyConfiguration?, logLevel: String?): Int = 0 - override fun runWithConfig(configurationYAML: String?, logLevel: String?): Int = 0 + override fun runWithTemplate( + configurationYAML: String, + envoyConfiguration: EnvoyConfiguration, + logLevel: String + ): Int = 0 override fun startStream(callbacks: EnvoyHTTPCallbacks?): EnvoyHTTPStream = MockEnvoyHTTPStream(callbacks!!) diff --git a/test/kotlin/integration/BUILD b/test/kotlin/integration/BUILD index 77bdfb2071..a291a2b2c5 100644 --- a/test/kotlin/integration/BUILD +++ b/test/kotlin/integration/BUILD @@ -27,3 +27,17 @@ envoy_mobile_jni_kt_test( "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", ], ) + +envoy_mobile_jni_kt_test( + name = "cancel_stream_test", + srcs = [ + "CancelStreamTest.kt", + ], + native_deps = [ + "//library/common/jni:libjava_jni_lib.so", + "//library/common/jni:java_jni_lib.jnilib", + ], + deps = [ + "//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib", + ], +) diff --git a/test/kotlin/integration/CancelStreamTest.kt b/test/kotlin/integration/CancelStreamTest.kt new file mode 100644 index 0000000000..6a3eb2a592 --- /dev/null +++ b/test/kotlin/integration/CancelStreamTest.kt @@ -0,0 +1,157 @@ +package test.kotlin.integration + +import io.envoyproxy.envoymobile.Custom +import io.envoyproxy.envoymobile.EngineBuilder +import io.envoyproxy.envoymobile.EnvoyError +import io.envoyproxy.envoymobile.FilterDataStatus +import io.envoyproxy.envoymobile.FilterHeadersStatus +import io.envoyproxy.envoymobile.FilterTrailersStatus +import io.envoyproxy.envoymobile.RequestHeadersBuilder +import io.envoyproxy.envoymobile.RequestMethod +import io.envoyproxy.envoymobile.ResponseFilter +import io.envoyproxy.envoymobile.ResponseHeaders +import io.envoyproxy.envoymobile.ResponseTrailers +import io.envoyproxy.envoymobile.UpstreamHttpProtocol +import io.envoyproxy.envoymobile.engine.JniLibrary +import java.nio.ByteBuffer +import java.util.concurrent.CountDownLatch +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test + +private const val hcmType = + "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" +private const val pbfType = "type.googleapis.com/envoymobile.extensions.filters.http.platform_bridge.PlatformBridge" +private const val filterName = "cancel_validation_filter" +private const val config = + """ + static_resources: + listeners: + - name: fake_remote_listener + address: + socket_address: { protocol: TCP, address: 127.0.0.1, port_value: 10101 } + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": $hcmType + stat_prefix: remote_hcm + route_config: + name: remote_route + virtual_hosts: + - name: remote_service + domains: ["*"] + routes: + - match: { prefix: "/" } + direct_response: { status: 200 } + http_filters: + - name: envoy.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + - name: base_api_listener + address: + socket_address: { protocol: TCP, address: 0.0.0.0, port_value: 10000 } + api_listener: + api_listener: + "@type": $hcmType + stat_prefix: api_hcm + route_config: + name: api_router + virtual_hosts: + - name: api + domains: ["*"] + routes: + - match: { prefix: "/" } + route: { cluster: fake_remote } + http_filters: + - name: envoy.filters.http.platform_bridge + typed_config: + "@type": $pbfType + platform_filter_name: $filterName + - name: envoy.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: fake_remote + connect_timeout: 0.25s + type: STATIC + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: fake_remote + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: { address: 127.0.0.1, port_value: 10101 } + """ + +class CancelStreamTest { + + init { + JniLibrary.loadTestLibrary() + } + + private val filterExpectation = CountDownLatch(1) + private val runExpectation = CountDownLatch(1) + + class CancelValidationFilter( + private val latch: CountDownLatch + ) : ResponseFilter { + override fun onResponseHeaders(headers: ResponseHeaders, endStream: Boolean): FilterHeadersStatus { + return FilterHeadersStatus.Continue(headers) + } + + override fun onResponseData(body: ByteBuffer, endStream: Boolean): FilterDataStatus { + return FilterDataStatus.Continue(body) + } + + override fun onResponseTrailers(trailers: ResponseTrailers): FilterTrailersStatus { + return FilterTrailersStatus.Continue(trailers) + } + + override fun onError(error: EnvoyError) {} + + override fun onCancel() { + latch.countDown() + } + } + + @Test + fun `cancel stream calls onCancel callback`() { + val engine = EngineBuilder(Custom(config)) + .addPlatformFilter( + name = "cancel_validation_filter", + factory = { CancelValidationFilter(filterExpectation) } + ) + .setOnEngineRunning {} + .build() + + val client = engine.streamClient() + + val requestHeaders = RequestHeadersBuilder( + method = RequestMethod.GET, + scheme = "https", + authority = "example.com", + path = "/test" + ) + .addUpstreamHttpProtocol(UpstreamHttpProtocol.HTTP2) + .build() + + client.newStreamPrototype() + .setOnCancel { + runExpectation.countDown() + } + .start(Executors.newSingleThreadExecutor()) + .sendHeaders(requestHeaders, false) + .cancel() + + filterExpectation.await(10, TimeUnit.SECONDS) + runExpectation.await(10, TimeUnit.SECONDS) + + engine.terminate() + + assertThat(filterExpectation.count).isEqualTo(0) + assertThat(runExpectation.count).isEqualTo(0) + } +} diff --git a/test/swift/integration/CancelStreamTest.swift b/test/swift/integration/CancelStreamTest.swift index 955cdca795..2b0b5d3de7 100644 --- a/test/swift/integration/CancelStreamTest.swift +++ b/test/swift/integration/CancelStreamTest.swift @@ -9,6 +9,7 @@ final class CancelStreamTests: XCTestCase { let hcmType = "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager" // swiftlint:disable:next line_length let pbfType = "type.googleapis.com/envoymobile.extensions.filters.http.platform_bridge.PlatformBridge" + let filterName = "cancel_validation_filter" let config = """ static_resources: @@ -53,7 +54,7 @@ final class CancelStreamTests: XCTestCase { - name: envoy.filters.http.platform_bridge typed_config: "@type": \(pbfType) - platform_filter_name: cancel_validation_filter + platform_filter_name: \(filterName) - name: envoy.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router @@ -102,7 +103,7 @@ final class CancelStreamTests: XCTestCase { let client = EngineBuilder(yaml: config) .addLogLevel(.trace) .addPlatformFilter( - name: "cancel_validation_filter", + name: filterName, factory: { CancelValidationFilter(expectation: filterExpectation) } ) .build()