Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/root/api/starting_envoy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,37 @@ This can help negate the effect of head-of-line (HOL) blocking for slow connecti
// Swift
builder.h2ExtendKeepaliveTimeout(true)


~~~~~~~~~~~~~~~~~~~~
``addKeyValueStore``
~~~~~~~~~~~~~~~~~~~~

Implementations of a public KeyValueStore interface may be added in their respective languages and
made available to the library. General usage is supported, but typical future usage will be in
support of HTTP and endpoint property caching.

**Example**::

// Kotlin
builder.addKeyValueStore("io.envoyproxy.envoymobile.MyKeyValueStore", MyKeyValueStoreImpl())

// Swift
// Coming soon.


The library also contains a simple Android-specific KeyValueStore implementation based on Android's
SharedPreferences.

**Example**::

// Android
val preferences = context.getSharedPreferences("io.envoyproxy.envoymobile.MyPreferences", Context.MODE_PRIVATE)
builder.addKeyValueStore("io.envoyproxy.envoymobile.MyKeyValueStore", SharedPreferencesStore(preferences))

// iOS
// Coming soon.


----------------------
Advanced configuration
----------------------
Expand Down
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Features:
- android: enable the filtering of unroutable families by default. (:issues: `#2267 <2267>`)
- instrumentation: add timers and warnings to platform-provided callbacks (:issue: `#2300 <2300>`)
- iOS: add support for integrating Envoy Mobile via the Swift Package Manager
- android: create simple persistent SharedPreferencesStore (:issue: `#2319 <2319>`)
- iOS: A documentation archive is now included in the GitHub release artifact (:issue: `#2335 <2335>`)

0.4.6 (April 26, 2022)
Expand Down
1 change: 1 addition & 0 deletions library/kotlin/io/envoyproxy/envoymobile/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ kt_android_library(
name = "envoy_lib",
srcs = [
"AndroidEngineBuilder.kt",
"android/SharedPreferencesStore.kt",
],
custom_package = "io.envoyproxy.envoymobile",
manifest = "EnvoyManifest.xml",
Expand Down
2 changes: 1 addition & 1 deletion library/kotlin/io/envoyproxy/envoymobile/EngineBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ open class EngineBuilder(
* @return this builder.
*/
fun addKeyValueStore(name: String, keyValueStore: KeyValueStore): EngineBuilder {
this.keyValueStores.put(name, EnvoyKeyValueStoreAdapter(keyValueStore))
this.keyValueStores.put(name, keyValueStore)
return this
}

Expand Down
29 changes: 3 additions & 26 deletions library/kotlin/io/envoyproxy/envoymobile/KeyValueStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,7 @@ package io.envoyproxy.envoymobile
import io.envoyproxy.envoymobile.engine.types.EnvoyKeyValueStore

/**
* `KeyValueStore` is bridged through to `EnvoyKeyValueStore` to communicate with the engine.
* `KeyValueStore` is an interface that may be implemented to provide access to an arbitrary
* key-value store implementation that may be made accessible to native Envoy Mobile code.
*/
class KeyValueStore constructor (
val read: ((key: String) -> String?),
val remove: ((key: String) -> Unit),
val save: ((key: String, value: String) -> Unit)
)

/**
* Class responsible for bridging between the platform-level `KeyValueStore` and the
* engine's `EnvoyKeyValueStore`.
*/
internal class EnvoyKeyValueStoreAdapter(
private val callbacks: KeyValueStore
) : EnvoyKeyValueStore {
override fun read(key: String): String? {
return callbacks.read(key)
}

override fun remove(key: String) {
callbacks.remove(key)
}

override fun save(key: String, value: String) {
callbacks.save(key, value)
}
}
interface KeyValueStore : EnvoyKeyValueStore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.envoyproxy.envoymobile.android

import android.content.SharedPreferences

import io.envoyproxy.envoymobile.KeyValueStore

/**
* Simple implementation of a `KeyValueStore` leveraging `SharedPreferences` for persistence.
*/
class SharedPreferencesStore(sharedPreferences: SharedPreferences) : KeyValueStore {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we have some docs for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added docstring, and the interface is documented - would be great to generate docs from these.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Looks like @jpsim has made some great progress here!)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add some unit tests specific to SharedPreferencesStore class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't today have a unit test setup that loads the Android platform APIs, because we don't today have a lot of Android-only or Android-specific code (by design). If/as we add more, we probably do want such a test suite, but this is mostly meant to be a trivial example implementation that guides others in using the API. That said, once we have a place for such tests to live, I agree we should cover this at the unit level for completeness' sake.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thank you for the context.

I would argue that we should have a test even for this trivial example since otherwise we cannot really tell whether it works or not the way we expect it to work - as in, I think that we are past the threshold when we can justify having an Android tests suite (if we have a need for Android code we have a need for Android tests).

Could we create a GH issue for this if you do not want to do this as part of this PR? I would love us to start investing in testing more, otherwise it's going to become increasingly hard to continue to move forward without leaving broken stuff behind us.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private val preferences = sharedPreferences
private val editor = sharedPreferences.edit()

override fun read(key: String): String? {
return preferences.getString(key, null)
}

override fun remove(key: String) {
editor.remove(key)
editor.apply()
}

override fun save(key: String, value: String) {
editor.putString(key, value)
editor.apply()
}
}
6 changes: 6 additions & 0 deletions test/kotlin/apps/experimental/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package io.envoyproxy.envoymobile.helloenvoykotlin

import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.util.Log
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.envoyproxy.envoymobile.android.SharedPreferencesStore
import io.envoyproxy.envoymobile.AndroidEngineBuilder
import io.envoyproxy.envoymobile.Element
import io.envoyproxy.envoymobile.Engine
Expand All @@ -26,6 +28,7 @@ private const val REQUEST_HANDLER_THREAD_NAME = "hello_envoy_kt"
private const val REQUEST_AUTHORITY = "api.lyft.com"
private const val REQUEST_PATH = "/ping"
private const val REQUEST_SCHEME = "https"
private const val PERSISTENCE_KEY = "EnvoyMobilePersistenceKey"
private val FILTERED_HEADERS = setOf(
"server",
"filter-demo",
Expand All @@ -45,6 +48,8 @@ class MainActivity : Activity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val preferences = getSharedPreferences(PERSISTENCE_KEY, Context.MODE_PRIVATE)

engine = AndroidEngineBuilder(application)
.addLogLevel(LogLevel.DEBUG)
.addPlatformFilter(::DemoFilter)
Expand All @@ -54,6 +59,7 @@ class MainActivity : Activity() {
.enableInterfaceBinding(true)
.addNativeFilter("envoy.filters.http.buffer", "{\"@type\":\"type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer\",\"max_request_bytes\":5242880}")
.addStringAccessor("demo-accessor", { "PlatformString" })
.addKeyValueStore("demo-kv-store", SharedPreferencesStore(preferences))
.setOnEngineRunning { Log.d("MainActivity", "Envoy async internal setup completed") }
.setEventTracker({
for (entry in it.entries) {
Expand Down
10 changes: 5 additions & 5 deletions test/kotlin/integration/KeyValueStoreTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ class KeyValueStoreTest {

val readExpectation = CountDownLatch(3)
val saveExpectation = CountDownLatch(1)
val testKeyValueStore = KeyValueStore(
read = { _ -> readExpectation.countDown(); null },
remove = { _ -> {}},
save = { _, _ -> saveExpectation.countDown() }
)
val testKeyValueStore = object : KeyValueStore {
override fun read(key: String): String? { readExpectation.countDown(); return null }
override fun remove(key: String) {}
override fun save(key: String, value: String) { saveExpectation.countDown() }
}

val engine = EngineBuilder(Custom(config))
.addKeyValueStore("envoy.key_value.platform_test", testKeyValueStore)
Expand Down