diff --git a/docs/root/configuration/http_filters/lua_filter.rst b/docs/root/configuration/http_filters/lua_filter.rst index 0f5fb6022d358..627e6bfc80f56 100644 --- a/docs/root/configuration/http_filters/lua_filter.rst +++ b/docs/root/configuration/http_filters/lua_filter.rst @@ -290,6 +290,17 @@ under the filter name i.e. *envoy.lua*. Below is an example of a *metadata* in a Returns a :ref:`metadata object `. +requestInfo() +^^^^^^^^^^^^^ + +.. code-block:: lua + + requestInfo = handle:requestInfo() + +Returns :repo:`information ` related to the current request. + +Returns a :ref:`request info object `. + .. _config_http_filters_lua_header_wrapper: Header object API @@ -403,3 +414,18 @@ __pairs() Iterates through every *metadata* entry. *key* is a string that supplies a *metadata* key. *value* is *metadata* entry value. + +.. _config_http_filters_lua_request_info_wrapper: + +Request info object API +----------------------- + +protocol() +^^^^^^^^^^ + +.. code-block:: lua + + requestInfo:protocol() + +Returns the string representation of :repo:`HTTP protocol ` +used by the current request. The possible values are: *HTTP/1.0*, *HTTP/1.1*, and *HTTP/2*. \ No newline at end of file diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 114e40173c18c..1be25ff7c32c9 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -4,6 +4,7 @@ Version history 1.8.0 (Pending) =============== * http: response filters not applied to early error paths such as http_parser generated 400s. +* lua: added :ref:`requestInfo() ` wrapper and *protocol()* API. * ratelimit: added support for :repo:`api/envoy/service/ratelimit/v2/rls.proto`. Lyft's reference implementation of the `ratelimit `_ service also supports the data-plane-api proto as of v1.1.0. Envoy can use either proto to send client requests to a ratelimit server with the use of the diff --git a/source/extensions/filters/http/lua/BUILD b/source/extensions/filters/http/lua/BUILD index bc306c23731d5..ea2a04f409c8e 100644 --- a/source/extensions/filters/http/lua/BUILD +++ b/source/extensions/filters/http/lua/BUILD @@ -34,6 +34,8 @@ envoy_cc_library( hdrs = ["wrappers.h"], deps = [ "//include/envoy/http:header_map_interface", + "//include/envoy/request_info:request_info_interface", + "//source/common/http:utility_lib", "//source/extensions/filters/common/lua:lua_lib", ], ) diff --git a/source/extensions/filters/http/lua/lua_filter.cc b/source/extensions/filters/http/lua/lua_filter.cc index 73c7a5ff2f010..d0c1c705973b6 100644 --- a/source/extensions/filters/http/lua/lua_filter.cc +++ b/source/extensions/filters/http/lua/lua_filter.cc @@ -367,6 +367,16 @@ int StreamHandleWrapper::luaMetadata(lua_State* state) { return 1; } +int StreamHandleWrapper::luaRequestInfo(lua_State* state) { + ASSERT(state_ == State::Running); + if (request_info_wrapper_.get() != nullptr) { + request_info_wrapper_.pushStack(); + } else { + request_info_wrapper_.reset(RequestInfoWrapper::create(state, callbacks_.requestInfo()), true); + } + return 1; +} + int StreamHandleWrapper::luaLogTrace(lua_State* state) { const char* message = luaL_checkstring(state, 2); filter_.scriptLog(spdlog::level::trace, message); @@ -411,6 +421,7 @@ FilterConfig::FilterConfig(const std::string& lua_code, ThreadLocal::SlotAllocat lua_state_.registerType(); lua_state_.registerType(); lua_state_.registerType(); + lua_state_.registerType(); lua_state_.registerType(); request_function_slot_ = lua_state_.registerGlobal("envoy_on_request"); diff --git a/source/extensions/filters/http/lua/lua_filter.h b/source/extensions/filters/http/lua/lua_filter.h index 868229686737d..d020145811054 100644 --- a/source/extensions/filters/http/lua/lua_filter.h +++ b/source/extensions/filters/http/lua/lua_filter.h @@ -68,6 +68,12 @@ class FilterCallbacks { * route entry. */ virtual const ProtobufWkt::Struct& metadata() const PURE; + + /** + * @return RequestInfo::RequestInfo& the current request info handle. This handle is mutable to + * accomodate write API e.g. setDynamicMetadata(). + */ + virtual RequestInfo::RequestInfo& requestInfo() PURE; }; class Filter; @@ -122,7 +128,7 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject headers_wrapper_; Filters::Common::Lua::LuaDeathRef body_wrapper_; Filters::Common::Lua::LuaDeathRef trailers_wrapper_; + Filters::Common::Lua::LuaDeathRef request_info_wrapper_; Filters::Common::Lua::LuaDeathRef metadata_wrapper_; State state_{State::Running}; std::function yield_callback_; @@ -310,6 +323,7 @@ class Filter : public Http::StreamFilter, Logger::Loggable { void respond(Http::HeaderMapPtr&& headers, Buffer::Instance* body, lua_State* state) override; const ProtobufWkt::Struct& metadata() const override { return getMetadata(callbacks_); } + RequestInfo::RequestInfo& requestInfo() override { return callbacks_->requestInfo(); } Filter& parent_; Http::StreamDecoderFilterCallbacks* callbacks_{}; @@ -328,6 +342,7 @@ class Filter : public Http::StreamFilter, Logger::Loggable { void respond(Http::HeaderMapPtr&& headers, Buffer::Instance* body, lua_State* state) override; const ProtobufWkt::Struct& metadata() const override { return getMetadata(callbacks_); } + RequestInfo::RequestInfo& requestInfo() override { return callbacks_->requestInfo(); } Filter& parent_; Http::StreamEncoderFilterCallbacks* callbacks_{}; diff --git a/source/extensions/filters/http/lua/wrappers.cc b/source/extensions/filters/http/lua/wrappers.cc index 642a444a612d2..8bdd09ea7c6e5 100644 --- a/source/extensions/filters/http/lua/wrappers.cc +++ b/source/extensions/filters/http/lua/wrappers.cc @@ -1,5 +1,7 @@ #include "extensions/filters/http/lua/wrappers.h" +#include "common/http/utility.h" + namespace Envoy { namespace Extensions { namespace HttpFilters { @@ -100,6 +102,11 @@ void HeaderMapWrapper::checkModifiable(lua_State* state) { } } +int RequestInfoWrapper::luaProtocol(lua_State* state) { + lua_pushstring(state, Http::Utility::getProtocolString(request_info_.protocol().value()).c_str()); + return 1; +} + } // namespace Lua } // namespace HttpFilters } // namespace Extensions diff --git a/source/extensions/filters/http/lua/wrappers.h b/source/extensions/filters/http/lua/wrappers.h index 57a5b6ce7cbc1..cbdd92d057aed 100644 --- a/source/extensions/filters/http/lua/wrappers.h +++ b/source/extensions/filters/http/lua/wrappers.h @@ -1,6 +1,7 @@ #pragma once #include "envoy/http/header_map.h" +#include "envoy/request_info/request_info.h" #include "extensions/filters/common/lua/lua.h" @@ -98,6 +99,23 @@ class HeaderMapWrapper : public Filters::Common::Lua::BaseLuaObject { +public: + RequestInfoWrapper(RequestInfo::RequestInfo& request_info) : request_info_{request_info} {} + static ExportedFunctions exportedFunctions() { return {{"protocol", static_luaProtocol}}; } + +private: + /** + * Get current protocol being used. + * @return string representation of Http::Protocol. + */ + DECLARE_LUA_FUNCTION(RequestInfoWrapper, luaProtocol); + RequestInfo::RequestInfo& request_info_; +}; + } // namespace Lua } // namespace HttpFilters } // namespace Extensions diff --git a/test/extensions/filters/http/lua/BUILD b/test/extensions/filters/http/lua/BUILD index 39eceafebf201..8dcfc1654e4fd 100644 --- a/test/extensions/filters/http/lua/BUILD +++ b/test/extensions/filters/http/lua/BUILD @@ -31,6 +31,7 @@ envoy_extension_cc_test( deps = [ "//source/extensions/filters/http/lua:wrappers_lib", "//test/extensions/filters/common/lua:lua_wrappers_lib", + "//test/mocks/request_info:request_info_mocks", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/filters/http/lua/lua_filter_test.cc b/test/extensions/filters/http/lua/lua_filter_test.cc index 2562c2fa9fe50..844fcf5da33e2 100644 --- a/test/extensions/filters/http/lua/lua_filter_test.cc +++ b/test/extensions/filters/http/lua/lua_filter_test.cc @@ -15,6 +15,8 @@ using testing::AtLeast; using testing::InSequence; using testing::Invoke; using testing::Return; +using testing::ReturnPointee; +using testing::ReturnRef; using testing::StrEq; using testing::_; @@ -79,6 +81,7 @@ class LuaHttpFilterTest : public testing::Test { Http::MockStreamDecoderFilterCallbacks decoder_callbacks_; Http::MockStreamEncoderFilterCallbacks encoder_callbacks_; envoy::api::v2::core::Metadata metadata_; + NiceMock request_info_; const std::string HEADER_ONLY_SCRIPT{R"EOF( function envoy_on_request(request_handle) @@ -1460,6 +1463,25 @@ TEST_F(LuaHttpFilterTest, GetMetadataFromHandleNoLuaMetadata) { EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); } +// No available Lua metadata on route. +TEST_F(LuaHttpFilterTest, GetCurrentProtocol) { + const std::string SCRIPT{R"EOF( + function envoy_on_request(request_handle) + request_handle:logTrace(request_handle:requestInfo():protocol()) + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + EXPECT_CALL(decoder_callbacks_, requestInfo()).WillOnce(ReturnRef(request_info_)); + EXPECT_CALL(request_info_, protocol()).WillOnce(Return(Http::Protocol::Http11)); + + Http::TestHeaderMapImpl request_headers{{":path", "/"}}; + EXPECT_CALL(*filter_, scriptLog(spdlog::level::trace, StrEq("HTTP/1.1"))); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); +} + } // namespace Lua } // namespace HttpFilters } // namespace Extensions diff --git a/test/extensions/filters/http/lua/lua_integration_test.cc b/test/extensions/filters/http/lua/lua_integration_test.cc index 7820f546e400f..2ac5b6be06457 100644 --- a/test/extensions/filters/http/lua/lua_integration_test.cc +++ b/test/extensions/filters/http/lua/lua_integration_test.cc @@ -109,6 +109,7 @@ name: envoy.lua request_handle:headers():add("request_body_size", body_length) request_handle:headers():add("request_metadata_foo", metadata["foo"]) request_handle:headers():add("request_metadata_baz", metadata["baz"]) + request_handle:headers():add("request_protocol", request_handle:requestInfo():protocol()) end function envoy_on_response(response_handle) @@ -117,6 +118,7 @@ name: envoy.lua response_handle:headers():add("response_metadata_foo", metadata["foo"]) response_handle:headers():add("response_metadata_baz", metadata["baz"]) response_handle:headers():add("response_body_size", body_length) + response_handle:headers():add("request_protocol", response_handle:requestInfo():protocol()) response_handle:headers():remove("foo") end )EOF"; @@ -153,6 +155,10 @@ name: envoy.lua ->value() .c_str()); + EXPECT_STREQ( + "HTTP/1.1", + upstream_request_->headers().get(Http::LowerCaseString("request_protocol"))->value().c_str()); + Http::TestHeaderMapImpl response_headers{{":status", "200"}, {"foo", "bar"}}; upstream_request_->encodeHeaders(response_headers, false); Buffer::OwnedImpl response_data1("good"); @@ -170,6 +176,8 @@ name: envoy.lua EXPECT_STREQ( "bat", response->headers().get(Http::LowerCaseString("response_metadata_baz"))->value().c_str()); + EXPECT_STREQ("HTTP/1.1", + response->headers().get(Http::LowerCaseString("request_protocol"))->value().c_str()); EXPECT_EQ(nullptr, response->headers().get(Http::LowerCaseString("foo"))); cleanup(); diff --git a/test/extensions/filters/http/lua/wrappers_test.cc b/test/extensions/filters/http/lua/wrappers_test.cc index ce35a5a38bfb8..e642bda4537aa 100644 --- a/test/extensions/filters/http/lua/wrappers_test.cc +++ b/test/extensions/filters/http/lua/wrappers_test.cc @@ -1,9 +1,13 @@ +#include "common/http/utility.h" + #include "extensions/filters/http/lua/wrappers.h" #include "test/extensions/filters/common/lua/lua_wrappers.h" +#include "test/mocks/request_info/mocks.h" #include "test/test_common/utility.h" using testing::InSequence; +using testing::ReturnPointee; namespace Envoy { namespace Extensions { @@ -210,6 +214,42 @@ TEST_F(LuaHeaderMapWrapperTest, IteratorAcrossYield) { "[string \"...\"]:5: object used outside of proper scope"); } +class LuaRequestInfoWrapperTest + : public Filters::Common::Lua::LuaWrappersTestBase { +public: + virtual void setup(const std::string& script) { + Filters::Common::Lua::LuaWrappersTestBase::setup(script); + } + +protected: + void expectToPrintCurrentProtocol(const absl::optional& protocol) { + const std::string SCRIPT{R"EOF( + function callMe(object) + testPrint(string.format("'%s'", object:protocol())) + end + )EOF"}; + + InSequence s; + setup(SCRIPT); + + NiceMock request_info; + ON_CALL(request_info, protocol()).WillByDefault(ReturnPointee(&protocol)); + Filters::Common::Lua::LuaDeathRef wrapper( + RequestInfoWrapper::create(coroutine_->luaState(), request_info), true); + EXPECT_CALL(*this, + testPrint(fmt::format("'{}'", Http::Utility::getProtocolString(protocol.value())))); + start("callMe"); + wrapper.reset(); + } +}; + +// Return the current request protocol. +TEST_F(LuaRequestInfoWrapperTest, ReturnCurrentProtocol) { + expectToPrintCurrentProtocol(Http::Protocol::Http10); + expectToPrintCurrentProtocol(Http::Protocol::Http11); + expectToPrintCurrentProtocol(Http::Protocol::Http2); +} + } // namespace Lua } // namespace HttpFilters } // namespace Extensions