Skip to content

Commit

Permalink
mobile: Add support for sending trailers in the TestServer (envoyprox…
Browse files Browse the repository at this point in the history
…y#33272)

This PR adds support for sending trailers in the TestServer::setResponse. The HttpTestServer has also been updated to support sending trailers.

This PR also updates ReceiveTestTrailersTest.kt to use HttpTestServer.

Risk Level: low (tests only)
Testing: unit test
Docs Changes: n/a
Release Notes: n/a
Platform Specific Features: n/a

Signed-off-by: Fredy Wijaya <fredyw@google.com>
  • Loading branch information
fredyw committed Apr 2, 2024
1 parent 7bf3483 commit 3e9aca4
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 103 deletions.
8 changes: 7 additions & 1 deletion mobile/test/common/integration/test_server.cc
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ void TestServer::setHeadersAndData(absl::string_view header_key, absl::string_vi
}

void TestServer::setResponse(const absl::flat_hash_map<std::string, std::string>& headers,
absl::string_view body) {
absl::string_view body,
const absl::flat_hash_map<std::string, std::string>& trailers) {
ASSERT(upstream_);
Http::TestResponseHeaderMapImpl new_headers;
for (const auto& [key, value] : headers) {
Expand All @@ -202,6 +203,11 @@ void TestServer::setResponse(const absl::flat_hash_map<std::string, std::string>
new_headers.addCopy(":status", "200");
upstream_->setResponseHeaders(std::make_unique<Http::TestResponseHeaderMapImpl>(new_headers));
upstream_->setResponseBody(std::string(body));
Http::TestResponseTrailerMapImpl new_trailers;
for (const auto& [key, value] : trailers) {
new_trailers.addCopy(key, value);
}
upstream_->setResponseTrailers(std::make_unique<Http::TestResponseTrailerMapImpl>(new_trailers));
}

const std::string TestServer::http_proxy_config = R"EOF(
Expand Down
7 changes: 4 additions & 3 deletions mobile/test/common/integration/test_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,12 @@ class TestServer : public ListenerHooks {
absl::string_view response_body);

/**
* Sets the response headers and body for the test server to return on all future request.
* Can only be called once the server has been started.
* Sets the response headers, body, and trailers for the test server to return
* on all future request. Can only be called once the server has been started.
*/
void setResponse(const absl::flat_hash_map<std::string, std::string>& headers,
absl::string_view body);
absl::string_view body,
const absl::flat_hash_map<std::string, std::string>& trailers);

// ListenerHooks
void onWorkerListenerAdded() override {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,20 @@ private HttpTestServer(long handle, int port) {
* @param type the value in {@link HttpTestServerFactory.Type}
* @param headers the response headers
* @param body the response body
* @param trailers the response headers
* @return the `HttpTestServer` instance
*/
public static native HttpTestServer start(int type, Map<String, String> headers, String body);
public static native HttpTestServer start(int type, Map<String, String> headers, String body,
Map<String, String> trailers);

/**
* A convenience method to start the server with an empty response headers and body. This server
* will always return 200 HTTP status code.
* A convenience method to start the server with an empty response headers, body, and trailers.
* This server will always return 200 HTTP status code.
*
* @param type the value in {@link HttpTestServerFactory.Type}
* @return the `HttpTestServer` instance
*/
public static HttpTestServer start(int type) { return start(type, Collections.emptyMap(), ""); }
public static HttpTestServer start(int type) {
return start(type, Collections.emptyMap(), "", Collections.emptyMap());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.envoyproxy.envoymobile.engine.AndroidJniLibrary;
import io.envoyproxy.envoymobile.engine.JniLibrary;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
Expand Down Expand Up @@ -53,7 +54,8 @@ public void setUpEngine() throws Exception {
headers.put("Content-Type", "text/plain");
headers.put("X-Original-Url", "https://test.example.com:6121/simple.txt");
httpTestServer = HttpTestServerFactory.start(HttpTestServerFactory.Type.HTTP3, headers,
"This is a simple text file served by QUIC.\n");
"This is a simple text file served by QUIC.\n",
Collections.emptyMap());
CountDownLatch latch = new CountDownLatch(1);
engine = new AndroidEngineBuilder(appContext,
new Custom(String.format(CONFIG, httpTestServer.getPort())))
Expand Down
9 changes: 4 additions & 5 deletions mobile/test/jni/jni_http_test_server_factory.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@
// NOLINT(namespace-envoy)

extern "C" JNIEXPORT jobject JNICALL
Java_io_envoyproxy_envoymobile_engine_testing_HttpTestServerFactory_start(JNIEnv* env, jclass,
jint type,
jobject headers,
jstring body) {
Java_io_envoyproxy_envoymobile_engine_testing_HttpTestServerFactory_start(
JNIEnv* env, jclass, jint type, jobject headers, jstring body, jobject trailers) {
Envoy::JNI::JniHelper jni_helper(env);

Envoy::ExtensionRegistry::registerFactories();
Expand All @@ -21,7 +19,8 @@ Java_io_envoyproxy_envoymobile_engine_testing_HttpTestServerFactory_start(JNIEnv

auto cpp_headers = Envoy::JNI::javaMapToCppMap(jni_helper, headers);
auto cpp_body = Envoy::JNI::javaStringToCppString(jni_helper, body);
test_server->setResponse(cpp_headers, cpp_body);
auto cpp_trailers = Envoy::JNI::javaMapToCppMap(jni_helper, trailers);
test_server->setResponse(cpp_headers, cpp_body, cpp_trailers);

auto java_http_server_factory_class = jni_helper.findClass(
"io/envoyproxy/envoymobile/engine/testing/HttpTestServerFactory$HttpTestServer");
Expand Down
1 change: 1 addition & 0 deletions mobile/test/kotlin/integration/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ envoy_mobile_jni_kt_test(
native_lib_name = "envoy_jni_with_test_extensions",
deps = [
"//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib",
"//test/java/io/envoyproxy/envoymobile/engine/testing:http_test_server_factory_lib",
],
)

Expand Down
7 changes: 6 additions & 1 deletion mobile/test/kotlin/integration/ReceiveDataTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ class ReceiveDataTest {
@Before
fun setUp() {
httpTestServer =
HttpTestServerFactory.start(HttpTestServerFactory.Type.HTTP2_WITH_TLS, mapOf(), "data")
HttpTestServerFactory.start(
HttpTestServerFactory.Type.HTTP2_WITH_TLS,
mapOf(),
"data",
mapOf()
)
}

@After
Expand Down
125 changes: 38 additions & 87 deletions mobile/test/kotlin/integration/ReceiveTrailersTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,57 @@ package test.kotlin.integration

import com.google.common.truth.Truth.assertThat
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.FinalStreamIntel
import io.envoyproxy.envoymobile.LogLevel
import io.envoyproxy.envoymobile.RequestHeadersBuilder
import io.envoyproxy.envoymobile.RequestMethod
import io.envoyproxy.envoymobile.RequestTrailersBuilder
import io.envoyproxy.envoymobile.ResponseFilter
import io.envoyproxy.envoymobile.ResponseHeaders
import io.envoyproxy.envoymobile.ResponseTrailers
import io.envoyproxy.envoymobile.Standard
import io.envoyproxy.envoymobile.StreamIntel
import io.envoyproxy.envoymobile.engine.EnvoyConfiguration
import io.envoyproxy.envoymobile.engine.JniLibrary
import io.envoyproxy.envoymobile.engine.testing.HttpTestServerFactory
import java.nio.ByteBuffer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import org.junit.After
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test

private const val TEST_RESPONSE_FILTER_TYPE =
"type.googleapis.com/envoymobile.extensions.filters.http.test_remote_response.TestRemoteResponse"
private const val MATCHER_TRAILER_NAME = "test-trailer"
private const val MATCHER_TRAILER_VALUE = "test.code"
private const val TRAILER_NAME = "test-trailer"
private const val TRAILER_VALUE = "test.code"

class ReceiveTrailersTest {

init {
JniLibrary.loadTestLibrary()
}

class ErrorValidationFilter(private val onTrailers: CountDownLatch) : ResponseFilter {
override fun onResponseHeaders(
headers: ResponseHeaders,
endStream: Boolean,
streamIntel: StreamIntel
): FilterHeadersStatus<ResponseHeaders> {
return FilterHeadersStatus.Continue(headers)
}

override fun onResponseData(
body: ByteBuffer,
endStream: Boolean,
streamIntel: StreamIntel
): FilterDataStatus<ResponseHeaders> {
return FilterDataStatus.Continue(body)
}

override fun onResponseTrailers(
trailers: ResponseTrailers,
streamIntel: StreamIntel
): FilterTrailersStatus<ResponseHeaders, ResponseTrailers> {
onTrailers.countDown()
return FilterTrailersStatus.Continue(trailers)
}

override fun onError(error: EnvoyError, finalStreamIntel: FinalStreamIntel) {}
private lateinit var httpTestServer: HttpTestServerFactory.HttpTestServer

override fun onComplete(finalStreamIntel: FinalStreamIntel) {}
@Before
fun setUp() {
httpTestServer =
HttpTestServerFactory.start(
HttpTestServerFactory.Type.HTTP2_WITH_TLS,
mapOf(),
"data",
mapOf(TRAILER_NAME to TRAILER_VALUE)
)
}

override fun onCancel(finalStreamIntel: FinalStreamIntel) {}
@After
fun tearDown() {
httpTestServer.shutdown()
}

@Test
fun `successful sending of trailers`() {
val trailersReceived = CountDownLatch(2)
val expectation = CountDownLatch(2)
val trailersReceived = CountDownLatch(1)
val expectation = CountDownLatch(1)
val engine =
EngineBuilder(Standard())
.addPlatformFilter(
name = "test_platform_filter",
factory = { ErrorValidationFilter(trailersReceived) }
)
.addNativeFilter("test_remote_response", "[$TEST_RESPONSE_FILTER_TYPE]{}")
.addLogLevel(LogLevel.DEBUG)
.setLogger { _, msg -> print(msg) }
.setTrustChainVerification(EnvoyConfiguration.TrustChainVerification.ACCEPT_UNTRUSTED)
.build()

val client = engine.streamClient()
Expand All @@ -84,59 +61,32 @@ class ReceiveTrailersTest {
RequestHeadersBuilder(
method = RequestMethod.GET,
scheme = "https",
authority = "example.com",
path = "/test"
authority = "localhost:${httpTestServer.port}",
path = "/simple.txt"
)
builder.add("send-trailers", "true")
val requestHeadersDefault = builder.build()

val body = ByteBuffer.wrap("match_me".toByteArray(Charsets.UTF_8))
val requestTrailers =
RequestTrailersBuilder().add(MATCHER_TRAILER_NAME, MATCHER_TRAILER_VALUE).build()
val requestTrailers = RequestTrailersBuilder().add(TRAILER_NAME, TRAILER_VALUE).build()

var responseStatus: Int? = null
val trailerValues = mutableListOf<String>()
client
.newStreamPrototype()
.setOnResponseHeaders { headers, _, _ ->
responseStatus = headers.httpStatus
expectation.countDown()
}
.setOnError { _, _ -> fail("Unexpected error") }
.start()
.sendHeaders(requestHeadersDefault, false)
.sendData(body)
.close(requestTrailers)

expectation.await(10, TimeUnit.SECONDS)

builder.remove("send-trailers")
builder.add("send-trailers", "empty")
val requestHeadersEmpty = builder.build()
client
.newStreamPrototype()
.setOnResponseHeaders { headers, _, _ ->
responseStatus = headers.httpStatus
expectation.countDown()
}
.setOnError { _, _ -> fail("Unexpected error") }
.start()
.sendHeaders(requestHeadersEmpty, false)
.sendData(body)
.close(requestTrailers)
expectation.await(10, TimeUnit.SECONDS)

builder.remove("send-trailers")
builder.add("send-trailers", "empty-value")
val requestHeadersEmptyValue = builder.build()
client
.newStreamPrototype()
.setOnResponseHeaders { headers, _, _ ->
responseStatus = headers.httpStatus
expectation.countDown()
.setOnResponseTrailers { trailers, _ ->
val values = trailers.value(TRAILER_NAME)
if (values != null) {
trailerValues += values
}
trailersReceived.countDown()
}
.setOnError { _, _ -> fail("Unexpected error") }
.start()
.sendHeaders(requestHeadersEmptyValue, false)
.sendHeaders(requestHeadersDefault, false)
.sendData(body)
.close(requestTrailers)
expectation.await(10, TimeUnit.SECONDS)
Expand All @@ -147,5 +97,6 @@ class ReceiveTrailersTest {
trailersReceived.await(10, TimeUnit.SECONDS)
assertThat(expectation.count).isEqualTo(0)
assertThat(responseStatus).isEqualTo(200)
assertThat(trailerValues).contains(TRAILER_VALUE)
}
}
7 changes: 6 additions & 1 deletion mobile/test/kotlin/integration/SendDataTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ class SendDataTest {
@Before
fun setUp() {
httpTestServer =
HttpTestServerFactory.start(HttpTestServerFactory.Type.HTTP2_WITH_TLS, mapOf(), "data")
HttpTestServerFactory.start(
HttpTestServerFactory.Type.HTTP2_WITH_TLS,
mapOf(),
"data",
mapOf()
)
}

@After
Expand Down

0 comments on commit 3e9aca4

Please sign in to comment.