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
77 changes: 50 additions & 27 deletions include/aws/lambda-runtime/runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,28 +77,60 @@ struct invocation_request {
inline std::chrono::milliseconds get_time_remaining() const;
};

class invocation_response {
private:
class runtime_response {
protected:
/**
* The output of the function which is sent to the lambda caller.
* The response payload from the runtime.
*/
std::string m_payload;

/**
* The MIME type of the payload.
* This is always set to 'application/json' in unsuccessful invocations.
*/
std::string m_content_type;

/**
* Flag to distinguish if the contents are for successful or unsuccessful invocations.
* The serialized XRay response header.
*/
bool m_success;
std::string m_xray_response;

/**
* The serialized XRay response header.
* Instantiate an empty response.
*/
std::string m_xray_response;
runtime_response() = default;

public:
/* Create a runtime response with the given payload, content type and xray response. This can be used for
* constructing an initialization error response. For invocation success and failure response, see
* invocation_response.
*/
runtime_response(std::string const& payload, std::string const& content_type, std::string const& xray_response)
: m_payload(payload), m_content_type(content_type), m_xray_response(xray_response)
{
}

/**
* Get the payload string. The string is assumed to be UTF-8 encoded.
*/
std::string const& get_payload() const { return m_payload; }

/**
* Get the MIME type of the payload.
*/
std::string const& get_content_type() const { return m_content_type; }

/**
* Get the XRay response string. The string is assumed to be UTF-8 encoded.
*/
std::string const& get_xray_response() const { return m_xray_response; }
};

class invocation_response : public runtime_response {
private:
/**
* Flag to distinguish if the contents are for successful or unsuccessful invocations.
*/
bool m_success;

/**
* Instantiate an empty response. Used by the static functions 'success' and 'failure' to create a populated
Expand All @@ -113,7 +145,7 @@ class invocation_response {
// constructor should be used instead.
// Note: adding an overload to invocation_response::failure is not feasible since the parameter types are the same.
invocation_response(std::string const& payload, std::string const& content_type, bool success)
: m_payload(payload), m_content_type(content_type), m_success(success)
: runtime_response(payload, content_type, ""), m_success(success)
{
}

Expand All @@ -122,7 +154,7 @@ class invocation_response {
std::string const& content_type,
bool success,
std::string const& xray_response)
: m_payload(payload), m_content_type(content_type), m_success(success), m_xray_response(xray_response)
: runtime_response(payload, content_type, xray_response), m_success(success)
{
}

Expand All @@ -142,25 +174,10 @@ class invocation_response {
std::string const& error_type,
std::string const& xray_response);

/**
* Get the MIME type of the payload.
*/
std::string const& get_content_type() const { return m_content_type; }

/**
* Get the payload string. The string is assumed to be UTF-8 encoded.
*/
std::string const& get_payload() const { return m_payload; }

/**
* Returns true if the payload and content-type are set. Returns false if the error message and error types are set.
*/
bool is_success() const { return m_success; }

/**
* Get the XRay response string. The string isassumed to be UTF-8 encoded.
*/
std::string const& get_xray_response() const { return m_xray_response; }
};

struct no_result {};
Expand Down Expand Up @@ -189,13 +206,19 @@ class runtime {
*/
post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response);

/**
* Tells lambda that the runtime has failed during initialization.
*/
post_outcome post_init_error(runtime_response const& init_error_response);

private:
void set_curl_next_options();
static void set_curl_post_result_options();
post_outcome do_post(
std::string const& url,
std::string const& request_id,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

was this intended to remove the request_id?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

request_id is part of the url

invocation_response const& handler_response);
std::string const& content_type,
std::string const& payload,
std::string const& xray_response);
std::string const m_user_agent_header;
std::array<std::string const, 3> const m_endpoints;
};
Expand Down
41 changes: 27 additions & 14 deletions src/runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,40 +338,50 @@ runtime::next_outcome runtime::get_next()
runtime::post_outcome runtime::post_success(std::string const& request_id, invocation_response const& handler_response)
{
std::string const url = m_endpoints[Endpoints::RESULT] + request_id + "/response";
return do_post(url, request_id, handler_response);
return do_post(
url, handler_response.get_content_type(), handler_response.get_payload(), handler_response.get_xray_response());
}

runtime::post_outcome runtime::post_failure(std::string const& request_id, invocation_response const& handler_response)
{
std::string const url = m_endpoints[Endpoints::RESULT] + request_id + "/error";
return do_post(url, request_id, handler_response);
return do_post(
url, handler_response.get_content_type(), handler_response.get_payload(), handler_response.get_xray_response());
}

runtime::post_outcome runtime::post_init_error(runtime_response const& init_error_response)
{
std::string const url = m_endpoints[Endpoints::INIT];
return do_post(
url,
init_error_response.get_content_type(),
init_error_response.get_payload(),
init_error_response.get_xray_response());
}

runtime::post_outcome runtime::do_post(
std::string const& url,
std::string const& request_id,
invocation_response const& handler_response)
std::string const& content_type,
std::string const& payload,
std::string const& xray_response)
{
set_curl_post_result_options();
curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, url.c_str());
logging::log_info(LOG_TAG, "Making request to %s", url.c_str());

curl_slist* headers = nullptr;
if (handler_response.get_content_type().empty()) {
if (content_type.empty()) {
headers = curl_slist_append(headers, "content-type: text/html");
}
else {
headers = curl_slist_append(headers, ("content-type: " + handler_response.get_content_type()).c_str());
headers = curl_slist_append(headers, ("content-type: " + content_type).c_str());
}

if (!handler_response.get_xray_response().empty()) {
headers = curl_slist_append(
headers, ("lambda-runtime-function-xray-error-cause: " + handler_response.get_xray_response()).c_str());
}
headers = curl_slist_append(headers, ("lambda-runtime-function-xray-error-cause: " + xray_response).c_str());
headers = curl_slist_append(headers, "Expect:");
headers = curl_slist_append(headers, "transfer-encoding:");
headers = curl_slist_append(headers, m_user_agent_header.c_str());
auto const& payload = handler_response.get_payload();

logging::log_debug(
LOG_TAG, "calculating content length... %s", ("content-length: " + std::to_string(payload.length())).c_str());
headers = curl_slist_append(headers, ("content-length: " + std::to_string(payload.length())).c_str());
Expand All @@ -388,10 +398,10 @@ runtime::post_outcome runtime::do_post(
if (curl_code != CURLE_OK) {
logging::log_debug(
LOG_TAG,
"CURL returned error code %d - %s, for invocation %s",
"CURL returned error code %d - %s, when calling %s",
curl_code,
curl_easy_strerror(curl_code),
request_id.c_str());
url.c_str());
return aws::http::response_code::REQUEST_NOT_MADE;
}

Expand All @@ -400,7 +410,10 @@ runtime::post_outcome runtime::do_post(

if (!is_success(aws::http::response_code(http_response_code))) {
logging::log_error(
LOG_TAG, "Failed to post handler success response. Http response code: %ld.", http_response_code);
LOG_TAG,
"Failed to post handler success response. Http response code: %ld. %s",
http_response_code,
resp.get_body().c_str());
return aws::http::response_code(http_response_code);
}

Expand Down
99 changes: 99 additions & 0 deletions tests/unit/unit_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,105 @@ TEST(InvocationResponseTest, constructor_based_failure)
EXPECT_EQ(R"({"custom":"error"})", resp.get_payload());
}

// --- runtime_response tests ---

TEST(RuntimeResponseTest, constructor_sets_all_fields)
{
runtime_response resp("payload data", "application/json", "xray-trace-123");
EXPECT_EQ("payload data", resp.get_payload());
EXPECT_EQ("application/json", resp.get_content_type());
EXPECT_EQ("xray-trace-123", resp.get_xray_response());
}

TEST(RuntimeResponseTest, constructor_with_empty_fields)
{
runtime_response resp("", "", "");
EXPECT_EQ("", resp.get_payload());
EXPECT_EQ("", resp.get_content_type());
EXPECT_EQ("", resp.get_xray_response());
}

TEST(RuntimeResponseTest, constructor_with_empty_xray)
{
runtime_response resp("error body", "application/json", "");
EXPECT_EQ("error body", resp.get_payload());
EXPECT_EQ("application/json", resp.get_content_type());
EXPECT_EQ("", resp.get_xray_response());
}

TEST(RuntimeResponseTest, large_payload)
{
std::string large(10000, 'x');
runtime_response resp(large, "text/plain", "");
EXPECT_EQ(10000u, resp.get_payload().size());
EXPECT_EQ(large, resp.get_payload());
}

TEST(RuntimeResponseTest, can_be_used_for_init_error)
{
runtime_response init_err(
R"({"errorMessage":"module not found","errorType":"ImportError"})", "application/json", "xray-cause-data");
EXPECT_EQ("application/json", init_err.get_content_type());
EXPECT_NE(std::string::npos, init_err.get_payload().find("module not found"));
EXPECT_EQ("xray-cause-data", init_err.get_xray_response());
}

// --- invocation_response inheritance tests ---

TEST(InvocationResponseInheritanceTest, is_a_runtime_response)
{
invocation_response resp("payload", "text/plain", true, "xray");
runtime_response const& base = resp;
EXPECT_EQ("payload", base.get_payload());
EXPECT_EQ("text/plain", base.get_content_type());
EXPECT_EQ("xray", base.get_xray_response());
}

TEST(InvocationResponseInheritanceTest, three_arg_constructor_has_empty_xray)
{
invocation_response resp("data", "text/html", true);
EXPECT_EQ("data", resp.get_payload());
EXPECT_EQ("text/html", resp.get_content_type());
EXPECT_EQ("", resp.get_xray_response());
EXPECT_TRUE(resp.is_success());
}

TEST(InvocationResponseInheritanceTest, four_arg_constructor_preserves_xray)
{
invocation_response resp("err", "application/json", false, "xray-data");
EXPECT_EQ("err", resp.get_payload());
EXPECT_EQ("application/json", resp.get_content_type());
EXPECT_EQ("xray-data", resp.get_xray_response());
EXPECT_FALSE(resp.is_success());
}

TEST(InvocationResponseInheritanceTest, failure_with_xray_response)
{
auto resp = invocation_response::failure("err msg", "ErrType", "xray-cause");
EXPECT_FALSE(resp.is_success());
EXPECT_EQ("application/json", resp.get_content_type());
EXPECT_EQ("xray-cause", resp.get_xray_response());
EXPECT_NE(std::string::npos, resp.get_payload().find("err msg"));
}

TEST(InvocationResponseInheritanceTest, success_has_empty_xray)
{
auto resp = invocation_response::success("ok", "text/plain");
EXPECT_TRUE(resp.is_success());
EXPECT_EQ("", resp.get_xray_response());
}

TEST(InvocationResponseInheritanceTest, can_pass_as_runtime_response_const_ref)
{
invocation_response resp("body", "application/json", false, "xray-123");
auto check = [](runtime_response const& r) {
EXPECT_EQ("body", r.get_payload());
EXPECT_EQ("application/json", r.get_content_type());
EXPECT_EQ("xray-123", r.get_xray_response());
};
check(resp);
}

// --- http::response tests ---

TEST(HttpResponseTest, add_and_retrieve_header)
Expand Down
Loading