diff --git a/source/common/http/filter/lua/lua_filter.cc b/source/common/http/filter/lua/lua_filter.cc index 0e3af6dc71116..5b210e1ccc079 100644 --- a/source/common/http/filter/lua/lua_filter.cc +++ b/source/common/http/filter/lua/lua_filter.cc @@ -261,8 +261,22 @@ int StreamHandleWrapper::luaHeaders(lua_State* state) { if (headers_wrapper_.get() != nullptr) { headers_wrapper_.pushStack(); } else { - headers_wrapper_.reset( - HeaderMapWrapper::create(state, headers_, [this]() { return !headers_continued_; }), true); + headers_wrapper_.reset(HeaderMapWrapper::create(state, headers_, + [this] { + // If we are about to do a modifiable header + // operation, blow away the route cache. We + // could be a little more intelligent about + // when we do this so the performance would be + // higher, but this is simple and will get the + // job done for now. This is a NOP on the + // encoder path. + if (!headers_continued_) { + callbacks_.onHeadersModified(); + } + + return !headers_continued_; + }), + true); } return 1; } diff --git a/source/common/http/filter/lua/lua_filter.h b/source/common/http/filter/lua/lua_filter.h index ee44eb2e1b65c..9ae7351aeaa81 100644 --- a/source/common/http/filter/lua/lua_filter.h +++ b/source/common/http/filter/lua/lua_filter.h @@ -34,6 +34,12 @@ class FilterCallbacks { */ virtual void continueIteration() PURE; + /** + * Called when headers have been modified by a script. This can only happen prior to headers + * being continued. + */ + virtual void onHeadersModified() PURE; + /** * Perform an immediate response. * @param headers supplies the response headers. @@ -268,6 +274,7 @@ class Filter : public StreamFilter, Logger::Loggable { } const Buffer::Instance* bufferedBody() override { return callbacks_->decodingBuffer(); } void continueIteration() override { return callbacks_->continueDecoding(); } + void onHeadersModified() override { callbacks_->clearRouteCache(); } void respond(HeaderMapPtr&& headers, Buffer::Instance* body, lua_State* state) override; Filter& parent_; @@ -283,6 +290,7 @@ class Filter : public StreamFilter, Logger::Loggable { } const Buffer::Instance* bufferedBody() override { return callbacks_->encodingBuffer(); } void continueIteration() override { return callbacks_->continueEncoding(); } + void onHeadersModified() override {} void respond(HeaderMapPtr&& headers, Buffer::Instance* body, lua_State* state) override; Filter& parent_; diff --git a/test/common/http/filter/lua/lua_filter_test.cc b/test/common/http/filter/lua/lua_filter_test.cc index b54736e7de5c7..12b0383b32169 100644 --- a/test/common/http/filter/lua/lua_filter_test.cc +++ b/test/common/http/filter/lua/lua_filter_test.cc @@ -609,6 +609,7 @@ TEST_F(LuaHttpFilterTest, RequestAndResponse) { const std::string SCRIPT{R"EOF( function envoy_on_request(request_handle) request_handle:logTrace(request_handle:headers():get(":path")) + request_handle:headers():add("foo", "bar") for chunk in request_handle:bodyChunks() do request_handle:logTrace(chunk:length()) @@ -619,6 +620,7 @@ TEST_F(LuaHttpFilterTest, RequestAndResponse) { function envoy_on_response(response_handle) response_handle:logTrace(response_handle:headers():get(":status")) + response_handle:headers():add("foo", "bar") for chunk in response_handle:bodyChunks() do response_handle:logTrace(chunk:length()) @@ -633,6 +635,7 @@ TEST_F(LuaHttpFilterTest, RequestAndResponse) { TestHeaderMapImpl request_headers{{":path", "/"}}; EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("/"))); + EXPECT_CALL(decoder_callbacks_, clearRouteCache()); EXPECT_EQ(FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); Buffer::OwnedImpl data("hello"); diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index bf1e170985f59..5ccf42aaa6ee6 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -240,10 +240,11 @@ void HttpIntegrationTest::cleanupUpstreamAndDownstream() { } } -void HttpIntegrationTest::waitForNextUpstreamRequest() { +void HttpIntegrationTest::waitForNextUpstreamRequest(uint64_t upstream_index) { // If there is no upstream connection, wait for it to be established. if (!fake_upstream_connection_) { - fake_upstream_connection_ = fake_upstreams_[0]->waitForHttpConnection(*dispatcher_); + fake_upstream_connection_ = + fake_upstreams_[upstream_index]->waitForHttpConnection(*dispatcher_); } // Wait for the next stream on the upstream connection. upstream_request_ = fake_upstream_connection_->waitForNewStream(*dispatcher_); diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index 77ecda8a25990..172f08ee30c5d 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -105,7 +105,7 @@ class HttpIntegrationTest : public BaseIntegrationTest { // Wait for the end of stream on the next upstream stream on fake_upstreams_ // Sets fake_upstream_connection_ to the connection and upstream_request_ to stream. - void waitForNextUpstreamRequest(); + void waitForNextUpstreamRequest(uint64_t upstream_index = 0); // Close |codec_client_| and |fake_upstream_connection_| cleanly. void cleanupUpstreamAndDownstream(); diff --git a/test/integration/lua_integration_test.cc b/test/integration/lua_integration_test.cc index 3a65ec5e0d363..70aafb0b1cbb0 100644 --- a/test/integration/lua_integration_test.cc +++ b/test/integration/lua_integration_test.cc @@ -13,6 +13,8 @@ class LuaIntegrationTest : public HttpIntegrationTest, HttpIntegrationTest::createUpstreams(); fake_upstreams_.emplace_back(new FakeUpstream(0, FakeHttpConnection::Type::HTTP1, version_)); ports_.push_back(fake_upstreams_.back()->localAddress()->ip()->port()); + fake_upstreams_.emplace_back(new FakeUpstream(0, FakeHttpConnection::Type::HTTP1, version_)); + ports_.push_back(fake_upstreams_.back()->localAddress()->ip()->port()); } void initializeFilter(const std::string& filter_config) { @@ -22,6 +24,22 @@ class LuaIntegrationTest : public HttpIntegrationTest, auto* lua_cluster = bootstrap.mutable_static_resources()->add_clusters(); lua_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); lua_cluster->set_name("lua_cluster"); + + auto* alt_cluster = bootstrap.mutable_static_resources()->add_clusters(); + alt_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); + alt_cluster->set_name("alt_cluster"); + }); + + config_helper_.addConfigModifier([](envoy::api::v2::filter::http::HttpConnectionManager& hcm) { + hcm.mutable_route_config() + ->mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_match() + ->set_prefix("/test/long/url"); + + auto* new_route = hcm.mutable_route_config()->mutable_virtual_hosts(0)->add_routes(); + new_route->mutable_match()->set_prefix("/alt/route"); + new_route->mutable_route()->set_cluster("alt_cluster"); }); initialize(); @@ -213,4 +231,36 @@ name: envoy.lua EXPECT_EQ("nope", response_->body()); } +// Filter alters headers and changes route. +TEST_P(LuaIntegrationTest, ChangeRoute) { + const std::string FILTER_AND_CODE = + R"EOF( +name: envoy.lua +config: + inline_code: | + function envoy_on_request(request_handle) + request_handle:headers():remove(":path") + request_handle:headers():add(":path", "/alt/route") + end +)EOF"; + + initializeFilter(FILTER_AND_CODE); + + codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); + Http::TestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "10.0.0.1"}}; + codec_client_->makeHeaderOnlyRequest(request_headers, *response_); + + waitForNextUpstreamRequest(2); + upstream_request_->encodeHeaders(default_response_headers_, true); + response_->waitForEndStream(); + cleanup(); + + EXPECT_TRUE(response_->complete()); + EXPECT_STREQ("200", response_->headers().Status()->value().c_str()); +} + } // namespace Envoy