Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
871ee1b
extract http call functions into a new class, LuaFilterLibrary
mindyor Feb 23, 2020
df9df09
create fire and forget listener; lua function httpCallAsync
mindyor Feb 23, 2020
541bc4b
group class functions together
mindyor Feb 23, 2020
8b5b84d
add documentation for httpCallAsync
mindyor Feb 24, 2020
323d728
add release note to version history
mindyor Feb 24, 2020
e892eaa
formatting
mindyor Feb 25, 2020
29464ba
naming. s/LuaFilterLibrary/LuaFilterUtil/
mindyor Feb 25, 2020
b566eab
formatting: lengthen underline
mindyor Feb 25, 2020
4cb0ecc
move instantiation of fire and forget writer into stream handle wrapp…
mindyor Feb 25, 2020
27a0f3c
makeHttpCall static function
mindyor Feb 25, 2020
11c1301
whitespace
mindyor Feb 25, 2020
21aa51e
alphabetic order
mindyor Feb 25, 2020
bf9998d
delete fire and forget writer in destructor
mindyor Feb 26, 2020
3e0f6c4
correct docs: httpCallAsync return
mindyor Feb 26, 2020
38a8a99
rename functions Nonblocking as implementation does not use callbacks
mindyor Feb 27, 2020
67aee6e
rename FireAndForgetWriter to FireAndForgetHttpWriter
mindyor Feb 27, 2020
e5bee45
comment nits
mindyor Feb 27, 2020
ac562dd
comment clarity for fire and forget http wrier
mindyor Feb 27, 2020
aa04eb8
remove superfluous else
mindyor Feb 28, 2020
d838492
formatting
mindyor Feb 28, 2020
7b234f3
put helper methods in anonymous namespace
mindyor Feb 28, 2020
f4cd746
rename callbacksListener to callbacks
mindyor Feb 28, 2020
d6e452b
use simple noop callbacks class rather than fire and forget writer
mindyor Feb 28, 2020
d022924
httpCall take flag to indicate asynchronous. Remove httpCallNonblocking
mindyor Mar 3, 2020
5ad50d1
test covering async = false case
mindyor Mar 3, 2020
55d1682
remove lua declaration of functions; update naming and comments to us…
mindyor Mar 3, 2020
1ebaac1
Merge branch 'master' into async-lua-http-call
mindyor Mar 3, 2020
f279ad2
use static noopcallback - thanks snowp
mindyor Mar 3, 2020
1594207
remove unused lines in test
mindyor Mar 3, 2020
9945ac5
integration test assert on header values - thanks snowp
mindyor Mar 3, 2020
1d4ba85
comment tweak
mindyor Mar 3, 2020
6dcdcf5
pr comments
mindyor Mar 4, 2020
8459416
type check for async flag
mindyor Mar 4, 2020
d190e36
formatting
mindyor Mar 4, 2020
9a1dbd7
review feedback nits
mindyor Mar 5, 2020
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
9 changes: 6 additions & 3 deletions docs/root/configuration/http/http_filters/lua_filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -233,14 +233,17 @@ httpCall()

.. code-block:: lua

headers, body = handle:httpCall(cluster, headers, body, timeout)
headers, body = handle:httpCall(cluster, headers, body, timeout, asynchronous)

Makes an HTTP call to an upstream host. Envoy will yield the script until the call completes or
has an error. *cluster* is a string which maps to a configured cluster manager cluster. *headers*
Makes an HTTP call to an upstream host. *cluster* is a string which maps to a configured cluster manager cluster. *headers*
is a table of key/value pairs to send (the value can be a string or table of strings). Note that
the *:method*, *:path*, and *:authority* headers must be set. *body* is an optional string of body
data to send. *timeout* is an integer that specifies the call timeout in milliseconds.

*asynchronous* is a boolean flag. If asynchronous is set to true, Envoy will make the HTTP request and continue,
regardless of response success or failure. If this is set to false, or not set, Envoy will yield the script
until the call completes or has an error.

Returns *headers* which is a table of response headers. Returns *body* which is the string response
body. May be nil if there is no body.

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 @@ -20,6 +20,7 @@ Version history
* listener filters: listener filter extensions use the "envoy.filters.listener" name space. A
mapping of extension names is available in the :ref:`deprecated <deprecated>` documentation.
* listeners: fixed issue where :ref:`TLS inspector listener filter <config_listener_filters_tls_inspector>` could have been bypassed by a client using only TLS 1.3.
* lua: added a parameter to `httpCall` that makes it possible to have the call be asynchronous.
* mongo: the stat emitted for queries without a max time set in the :ref:`MongoDB filter<config_network_filters_mongo_proxy>` was modified to emit correctly for Mongo v3.2+.
* network filters: network filter extensions use the "envoy.filters.network" name space. A mapping
of extension names is available in the :ref:`deprecated <deprecated>` documentation.
Expand Down
146 changes: 88 additions & 58 deletions source/extensions/filters/http/lua/lua_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,79 @@ namespace Extensions {
namespace HttpFilters {
namespace Lua {

namespace {
// Okay to return non-const reference because this doesn't ever get changed.
NoopCallbacks& noopCallbacks() {
static NoopCallbacks* callbacks = new NoopCallbacks();
return *callbacks;
}

void buildHeadersFromTable(Http::HeaderMap& headers, lua_State* state, int table_index) {
// Build a header map to make the request. We iterate through the provided table to do this and
// check that we are getting strings.
lua_pushnil(state);
while (lua_next(state, table_index) != 0) {
// Uses 'key' (at index -2) and 'value' (at index -1).
const char* key = luaL_checkstring(state, -2);
// Check if the current value is a table, we iterate through the table and add each element of
// it as a header entry value for the current key.
if (lua_istable(state, -1)) {
lua_pushnil(state);
while (lua_next(state, -2) != 0) {
const char* value = luaL_checkstring(state, -1);
headers.addCopy(Http::LowerCaseString(key), value);
lua_pop(state, 1);
}
} else {
const char* value = luaL_checkstring(state, -1);
headers.addCopy(Http::LowerCaseString(key), value);
}
// Removes 'value'; keeps 'key' for next iteration. This is the input for lua_next() so that
// it can push the next key/value pair onto the stack.
lua_pop(state, 1);
}
}

Http::AsyncClient::Request* makeHttpCall(lua_State* state, Filter& filter,
Http::AsyncClient::Callbacks& callbacks) {
const std::string cluster = luaL_checkstring(state, 2);
luaL_checktype(state, 3, LUA_TTABLE);
size_t body_size;
const char* body = luaL_optlstring(state, 4, nullptr, &body_size);
int timeout_ms = luaL_checkint(state, 5);
if (timeout_ms < 0) {
luaL_error(state, "http call timeout must be >= 0");
}

if (filter.clusterManager().get(cluster) == nullptr) {
luaL_error(state, "http call cluster invalid. Must be configured");
}

auto headers = std::make_unique<Http::RequestHeaderMapImpl>();
buildHeadersFromTable(*headers, state, 3);
Http::RequestMessagePtr message(new Http::RequestMessageImpl(std::move(headers)));

// Check that we were provided certain headers.
if (message->headers().Path() == nullptr || message->headers().Method() == nullptr ||
message->headers().Host() == nullptr) {
luaL_error(state, "http call headers must include ':path', ':method', and ':authority'");
}

if (body != nullptr) {
message->body() = std::make_unique<Buffer::OwnedImpl>(body, body_size);
message->headers().setContentLength(body_size);
}

absl::optional<std::chrono::milliseconds> timeout;
if (timeout_ms > 0) {
timeout = std::chrono::milliseconds(timeout_ms);
}

return filter.clusterManager().httpAsyncClientForCluster(cluster).send(
std::move(message), callbacks, Http::AsyncClient::RequestOptions().setTimeout(timeout));
}
} // namespace

StreamHandleWrapper::StreamHandleWrapper(Filters::Common::Lua::Coroutine& coroutine,
Http::HeaderMap& headers, bool end_stream, Filter& filter,
FilterCallbacks& callbacks)
Expand Down Expand Up @@ -138,71 +211,23 @@ int StreamHandleWrapper::luaRespond(lua_State* state) {
return lua_yield(state, 0);
}

void StreamHandleWrapper::buildHeadersFromTable(Http::HeaderMap& headers, lua_State* state,
int table_index) {
// Build a header map to make the request. We iterate through the provided table to do this and
// check that we are getting strings.
lua_pushnil(state);
while (lua_next(state, table_index) != 0) {
// Uses 'key' (at index -2) and 'value' (at index -1).
const char* key = luaL_checkstring(state, -2);
// Check if the current value is a table, we iterate through the table and add each element of
// it as a header entry value for the current key.
if (lua_istable(state, -1)) {
lua_pushnil(state);
while (lua_next(state, -2) != 0) {
const char* value = luaL_checkstring(state, -1);
headers.addCopy(Http::LowerCaseString(key), value);
lua_pop(state, 1);
}
} else {
const char* value = luaL_checkstring(state, -1);
headers.addCopy(Http::LowerCaseString(key), value);
}
// Removes 'value'; keeps 'key' for next iteration. This is the input for lua_next() so that
// it can push the next key/value pair onto the stack.
lua_pop(state, 1);
}
}

int StreamHandleWrapper::luaHttpCall(lua_State* state) {
ASSERT(state_ == State::Running);

const std::string cluster = luaL_checkstring(state, 2);
luaL_checktype(state, 3, LUA_TTABLE);
size_t body_size;
const char* body = luaL_optlstring(state, 4, nullptr, &body_size);
int timeout_ms = luaL_checkint(state, 5);
if (timeout_ms < 0) {
return luaL_error(state, "http call timeout must be >= 0");
}

if (filter_.clusterManager().get(cluster) == nullptr) {
return luaL_error(state, "http call cluster invalid. Must be configured");
}

auto headers = std::make_unique<Http::RequestHeaderMapImpl>();
buildHeadersFromTable(*headers, state, 3);
Http::RequestMessagePtr message(new Http::RequestMessageImpl(std::move(headers)));

// Check that we were provided certain headers.
if (message->headers().Path() == nullptr || message->headers().Method() == nullptr ||
message->headers().Host() == nullptr) {
return luaL_error(state, "http call headers must include ':path', ':method', and ':authority'");
}

if (body != nullptr) {
message->body() = std::make_unique<Buffer::OwnedImpl>(body, body_size);
message->headers().setContentLength(body_size);
const int async_flag_index = 6;
if (!lua_isnone(state, async_flag_index) && !lua_isboolean(state, async_flag_index)) {
luaL_error(state, "http call asynchronous flag must be 'true', 'false', or empty");
}

absl::optional<std::chrono::milliseconds> timeout;
if (timeout_ms > 0) {
timeout = std::chrono::milliseconds(timeout_ms);
if (lua_toboolean(state, async_flag_index)) {
return luaHttpCallAsynchronous(state);
} else {
return luaHttpCallSynchronous(state);
}
}

http_request_ = filter_.clusterManager().httpAsyncClientForCluster(cluster).send(
std::move(message), *this, Http::AsyncClient::RequestOptions().setTimeout(timeout));
int StreamHandleWrapper::luaHttpCallSynchronous(lua_State* state) {
http_request_ = makeHttpCall(state, filter_, *this);
if (http_request_) {
state_ = State::HttpCall;
return lua_yield(state, 0);
Expand All @@ -213,6 +238,11 @@ int StreamHandleWrapper::luaHttpCall(lua_State* state) {
}
}

int StreamHandleWrapper::luaHttpCallAsynchronous(lua_State* state) {
makeHttpCall(state, filter_, noopCallbacks());
return 0;
}

void StreamHandleWrapper::onSuccess(Http::ResponseMessagePtr&& response) {
ASSERT(state_ == State::HttpCall || state_ == State::Running);
ENVOY_LOG(debug, "async HTTP response complete");
Expand Down
15 changes: 14 additions & 1 deletion source/extensions/filters/http/lua/lua_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject<StreamHan
* @param 2 (table): A table of HTTP headers. :method, :path, and :authority must be defined.
* @param 3 (string): Body. Can be nil.
* @param 4 (int): Timeout in milliseconds for the call.
* @param 5 (bool): Optional flag. If true, filter continues without waiting for HTTP response
* from upstream service. False/synchronous by default.
* @return headers (table), body (string/nil)
*/
DECLARE_LUA_FUNCTION(StreamHandleWrapper, luaHttpCall);
Expand Down Expand Up @@ -247,7 +249,8 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject<StreamHan
*/
DECLARE_LUA_CLOSURE(StreamHandleWrapper, luaBodyIterator);

static void buildHeadersFromTable(Http::HeaderMap& headers, lua_State* state, int table_index);
int luaHttpCallSynchronous(lua_State* state);
int luaHttpCallAsynchronous(lua_State* state);

// Filters::Common::Lua::BaseLuaObject
void onMarkDead() override {
Expand Down Expand Up @@ -287,6 +290,16 @@ class StreamHandleWrapper : public Filters::Common::Lua::BaseLuaObject<StreamHan
Http::AsyncClient::Request* http_request_{};
};

/**
* An empty Callbacks client. It will ignore everything, including successes and failures.
*/
class NoopCallbacks : public Http::AsyncClient::Callbacks {
public:
// Http::AsyncClient::Callbacks
void onSuccess(Http::ResponseMessagePtr&&) override {}
void onFailure(Http::AsyncClient::FailureReason) override {}
};

/**
* Global configuration for the filter.
*/
Expand Down
Loading