diff --git a/MANIFEST.in b/MANIFEST.in index f104ed5..4228092 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,8 +2,5 @@ include README.md include LICENSE include THIRD-PARTY-LICENSES include requirements/base.txt -include awslambdaric/runtime_client.cpp -recursive-include scripts * -recursive-include deps * prune tests diff --git a/awslambdaric/lambda_runtime_client.py b/awslambdaric/lambda_runtime_client.py index 70ff205..53a8d3a 100644 --- a/awslambdaric/lambda_runtime_client.py +++ b/awslambdaric/lambda_runtime_client.py @@ -13,7 +13,7 @@ import importlib_metadata as metadata -def _user_agent(): +def user_agent(): py_version = ( f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" ) @@ -24,13 +24,6 @@ def _user_agent(): return f"aws-lambda-python/{py_version}-{pkg_version}" -try: - import runtime_client - - runtime_client.initialize_client(_user_agent()) -except ImportError: - runtime_client = None - from .lambda_runtime_marshaller import LambdaMarshaller @@ -65,7 +58,12 @@ def post_init_error(self, error_response_data): runtime_connection = http.client.HTTPConnection(self.lambda_runtime_address) runtime_connection.connect() endpoint = "/2018-06-01/runtime/init/error" - runtime_connection.request("POST", endpoint, error_response_data) + runtime_connection.request( + "POST", + endpoint, + body=error_response_data, + headers={"User-Agent": user_agent()}, + ) response = runtime_connection.getresponse() response_body = response.read() @@ -73,30 +71,67 @@ def post_init_error(self, error_response_data): raise LambdaRuntimeClientError(endpoint, response.code, response_body) def wait_next_invocation(self): - response_body, headers = runtime_client.next() + runtime_connection = http.client.HTTPConnection(self.lambda_runtime_address) + runtime_connection.connect() + endpoint = "/2018-06-01/runtime/invocation/next" + runtime_connection.request( + "GET", endpoint, headers={"User-Agent": user_agent()} + ) + response = runtime_connection.getresponse() + response_body = response.read() + + if response.code != http.HTTPStatus.OK: + raise LambdaRuntimeClientError(endpoint, response.code, response_body) + return InvocationRequest( - invoke_id=headers.get("Lambda-Runtime-Aws-Request-Id"), - x_amzn_trace_id=headers.get("Lambda-Runtime-Trace-Id"), - invoked_function_arn=headers.get("Lambda-Runtime-Invoked-Function-Arn"), - deadline_time_in_ms=headers.get("Lambda-Runtime-Deadline-Ms"), - client_context=headers.get("Lambda-Runtime-Client-Context"), - cognito_identity=headers.get("Lambda-Runtime-Cognito-Identity"), - content_type=headers.get("Content-Type"), + invoke_id=response.getheader("Lambda-Runtime-Aws-Request-Id"), + x_amzn_trace_id=response.getheader("Lambda-Runtime-Trace-Id"), + invoked_function_arn=response.getheader( + "Lambda-Runtime-Invoked-Function-Arn" + ), + deadline_time_in_ms=response.getheader("Lambda-Runtime-Deadline-Ms"), + client_context=response.getheader("Lambda-Runtime-Client-Context"), + cognito_identity=response.getheader("Lambda-Runtime-Cognito-Identity"), + content_type=response.getheader("Content-Type"), event_body=response_body, ) def post_invocation_result( self, invoke_id, result_data, content_type="application/json" ): - runtime_client.post_invocation_result( - invoke_id, + runtime_connection = http.client.HTTPConnection(self.lambda_runtime_address) + runtime_connection.connect() + endpoint = f"/2018-06-01/runtime/invocation/{invoke_id}/response" + headers = {"Content-Type": content_type, "User-Agent": user_agent()} + runtime_connection.request( + "POST", + endpoint, result_data if isinstance(result_data, bytes) else result_data.encode("utf-8"), - content_type, + headers, ) + response = runtime_connection.getresponse() + response_body = response.read() + + if response.code != http.HTTPStatus.OK: + raise LambdaRuntimeClientError(endpoint, response.code, response_body) def post_invocation_error(self, invoke_id, error_response_data, xray_fault): max_header_size = 1024 * 1024 # 1MiB xray_fault = xray_fault if len(xray_fault.encode()) < max_header_size else "" - runtime_client.post_error(invoke_id, error_response_data, xray_fault) + + runtime_connection = http.client.HTTPConnection(self.lambda_runtime_address) + runtime_connection.connect() + endpoint = f"/2018-06-01/runtime/invocation/{invoke_id}/error" + headers = { + "User-Agent": user_agent(), + "Content-Type": "application/json", + "Lambda-Runtime-Function-XRay-Error-Cause": xray_fault, + } + runtime_connection.request("POST", endpoint, error_response_data, headers) + response = runtime_connection.getresponse() + response_body = response.read() + + if response.code != http.HTTPStatus.OK: + raise LambdaRuntimeClientError(endpoint, response.code, response_body) diff --git a/awslambdaric/runtime_client.cpp b/awslambdaric/runtime_client.cpp deleted file mode 100644 index 4650b9a..0000000 --- a/awslambdaric/runtime_client.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. */ - -#include -#include -#include -#include - -#define NULL_IF_EMPTY(v) (((v) == NULL || (v)[0] == 0) ? NULL : (v)) - -static const std::string ENDPOINT(getenv("AWS_LAMBDA_RUNTIME_API") ? getenv("AWS_LAMBDA_RUNTIME_API") : "127.0.0.1:9001"); -static aws::lambda_runtime::runtime *CLIENT; - -static PyObject *method_initialize_client(PyObject *self, PyObject *args) { - char *user_agent_arg; - if (!PyArg_ParseTuple(args, "s", &user_agent_arg)) { - PyErr_SetString(PyExc_RuntimeError, "Wrong arguments"); - return NULL; - } - - const std::string user_agent = std::string(user_agent_arg); - - CLIENT = new aws::lambda_runtime::runtime(ENDPOINT, user_agent); - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject *method_next(PyObject *self) { - if (CLIENT == nullptr) { - PyErr_SetString(PyExc_RuntimeError, "Client not yet initalized"); - return NULL; - } - - auto outcome = CLIENT->get_next(); - if (!outcome.is_success()) { - PyErr_SetString(PyExc_RuntimeError, "Failed to get next"); - return NULL; - } - - auto response = outcome.get_result(); - auto payload = response.payload; - auto request_id = response.request_id.c_str(); - auto trace_id = response.xray_trace_id.c_str(); - auto function_arn = response.function_arn.c_str(); - auto deadline = std::chrono::duration_cast(response.deadline.time_since_epoch()).count(); - auto client_context = response.client_context.c_str(); - auto content_type = response.content_type.c_str(); - auto cognito_id = response.cognito_identity.c_str(); - - PyObject *payload_bytes = PyBytes_FromStringAndSize(payload.c_str(), payload.length()); - PyObject *result = Py_BuildValue("(O,{s:s,s:s,s:s,s:l,s:s,s:s,s:s})", - payload_bytes, //Py_BuildValue() increments reference counter - "Lambda-Runtime-Aws-Request-Id", request_id, - "Lambda-Runtime-Trace-Id", NULL_IF_EMPTY(trace_id), - "Lambda-Runtime-Invoked-Function-Arn", function_arn, - "Lambda-Runtime-Deadline-Ms", deadline, - "Lambda-Runtime-Client-Context", NULL_IF_EMPTY(client_context), - "Content-Type", NULL_IF_EMPTY(content_type), - "Lambda-Runtime-Cognito-Identity", NULL_IF_EMPTY(cognito_id) - ); - - Py_XDECREF(payload_bytes); - return result; -} - -static PyObject *method_post_invocation_result(PyObject *self, PyObject *args) { - if (CLIENT == nullptr) { - PyErr_SetString(PyExc_RuntimeError, "Client not yet initalized"); - return NULL; - } - - PyObject *invocation_response; - Py_ssize_t length; - char *request_id, *content_type, *response_as_c_string; - - if (!PyArg_ParseTuple(args, "sSs", &request_id, &invocation_response, &content_type)) { - PyErr_SetString(PyExc_RuntimeError, "Wrong arguments"); - return NULL; - } - - length = PyBytes_Size(invocation_response); - response_as_c_string = PyBytes_AsString(invocation_response); - std::string response_string(response_as_c_string, response_as_c_string + length); - - auto response = aws::lambda_runtime::invocation_response::success(response_string, content_type); - auto outcome = CLIENT->post_success(request_id, response); - if (!outcome.is_success()) { - PyErr_SetString(PyExc_RuntimeError, "Failed to post invocation response"); - return NULL; - } - - Py_INCREF(Py_None); - return Py_None; -} - -static PyObject *method_post_error(PyObject *self, PyObject *args) { - if (CLIENT == nullptr) { - PyErr_SetString(PyExc_RuntimeError, "Client not yet initalized"); - return NULL; - } - - char *request_id, *response_string, *xray_fault; - - if (!PyArg_ParseTuple(args, "sss", &request_id, &response_string, &xray_fault)) { - PyErr_SetString(PyExc_RuntimeError, "Wrong arguments"); - return NULL; - } - - auto response = aws::lambda_runtime::invocation_response(response_string, "application/json", false, xray_fault); - auto outcome = CLIENT->post_failure(request_id, response); - if (!outcome.is_success()) { - PyErr_SetString(PyExc_RuntimeError, "Failed to post invocation error"); - return NULL; - } - - Py_INCREF(Py_None); - return Py_None; -} - -static PyMethodDef Runtime_Methods[] = { - {"initialize_client", method_initialize_client, METH_VARARGS, NULL}, - {"next", (PyCFunction) method_next, METH_NOARGS, NULL}, - {"post_invocation_result", method_post_invocation_result, METH_VARARGS, NULL}, - {"post_error", method_post_error, METH_VARARGS, NULL}, - {NULL, NULL, 0, NULL} -}; - -static struct PyModuleDef runtime_client = { - PyModuleDef_HEAD_INIT, - "runtime", - NULL, - -1, - Runtime_Methods, - NULL, - NULL, - NULL, - NULL -}; - -PyMODINIT_FUNC PyInit_runtime_client(void) { - return PyModule_Create(&runtime_client); -} diff --git a/deps/aws-lambda-cpp-0.2.6.tar.gz b/deps/aws-lambda-cpp-0.2.6.tar.gz deleted file mode 100644 index 26fa498..0000000 Binary files a/deps/aws-lambda-cpp-0.2.6.tar.gz and /dev/null differ diff --git a/deps/curl-7_65_3.tar.gz b/deps/curl-7_65_3.tar.gz deleted file mode 100644 index c9374b2..0000000 Binary files a/deps/curl-7_65_3.tar.gz and /dev/null differ diff --git a/deps/patches/aws-lambda-cpp-add-content-type.patch b/deps/patches/aws-lambda-cpp-add-content-type.patch deleted file mode 100644 index 2e045ff..0000000 --- a/deps/patches/aws-lambda-cpp-add-content-type.patch +++ /dev/null @@ -1,53 +0,0 @@ -diff --git a/include/aws/lambda-runtime/runtime.h b/include/aws/lambda-runtime/runtime.h -index e14b804..cc1a789 100644 ---- a/include/aws/lambda-runtime/runtime.h -+++ b/include/aws/lambda-runtime/runtime.h -@@ -56,6 +56,11 @@ struct invocation_request { - */ - std::string function_arn; - -+ /** -+ * The Content-type of the current invocation. -+ */ -+ std::string content_type; -+ - /** - * Function execution deadline counted in milliseconds since the Unix epoch. - */ -diff --git a/include/aws/http/response.h b/include/aws/http/response.h -index 9b8cbda..be184c1 100644 ---- a/include/aws/http/response.h -+++ b/include/aws/http/response.h -@@ -36,6 +36,7 @@ public: - inline void set_response_code(aws::http::response_code c); - inline void set_content_type(char const* ct); - inline std::string const& get_body() const; -+ inline std::string const& get_content_type() const; - - private: - response_code m_response_code; -@@ -137,6 +138,12 @@ inline std::string const& response::get_body() const - { - return m_body; - } -+ -+inline std::string const& response::get_content_type() const -+{ -+ return m_content_type; -+} -+ - inline void response::add_header(std::string name, std::string const& value) - { - std::transform(name.begin(), name.end(), name.begin(), ::tolower); -diff --git a/src/runtime.cpp b/src/runtime.cpp -index 08d7014..1cbd6bb 100644 ---- a/src/runtime.cpp -+++ b/src/runtime.cpp -@@ -275,6 +275,7 @@ runtime::next_outcome runtime::get_next() - invocation_request req; - req.payload = resp.get_body(); - req.request_id = resp.get_header(REQUEST_ID_HEADER); -+ req.content_type = resp.get_content_type(); - - if (resp.has_header(TRACE_ID_HEADER)) { - req.xray_trace_id = resp.get_header(TRACE_ID_HEADER); diff --git a/deps/patches/aws-lambda-cpp-add-xray-response.patch b/deps/patches/aws-lambda-cpp-add-xray-response.patch deleted file mode 100644 index 253e468..0000000 --- a/deps/patches/aws-lambda-cpp-add-xray-response.patch +++ /dev/null @@ -1,82 +0,0 @@ -diff --git a/include/aws/lambda-runtime/runtime.h b/include/aws/lambda-runtime/runtime.h -index 0dc292c..be77d93 100644 ---- a/include/aws/lambda-runtime/runtime.h -+++ b/include/aws/lambda-runtime/runtime.h -@@ -85,6 +85,11 @@ private: - */ - bool m_success; - -+ /** -+ * The serialized XRay response header. -+ */ -+ std::string m_xray_response; -+ - /** - * Instantiate an empty response. Used by the static functions 'success' and 'failure' to create a populated - * invocation_response -@@ -97,10 +102,12 @@ public: - // To support clients that need to control the entire error response body (e.g. adding a stack trace), this - // 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) -- { -- } -+ invocation_response(std::string const& payload, 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) -+ {} - - /** - * Create a successful invocation response with the given payload and content-type. -@@ -111,7 +118,7 @@ public: - * Create a failure response with the given error message and error type. - * The content-type is always set to application/json in this case. - */ -- static invocation_response failure(std::string const& error_message, std::string const& error_type); -+ static invocation_response failure(std::string const& error_message, std::string const& error_type, std::string const& xray_response); - - /** - * Get the MIME type of the payload. -@@ -127,6 +134,11 @@ public: - * 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 { -diff --git a/src/runtime.cpp b/src/runtime.cpp -index e2ee7cd..d895c4b 100644 ---- a/src/runtime.cpp -+++ b/src/runtime.cpp -@@ -337,6 +337,7 @@ runtime::post_outcome runtime::do_post( - headers = curl_slist_append(headers, ("content-type: " + handler_response.get_content_type()).c_str()); - } - -+ headers = curl_slist_append(headers, ("lambda-runtime-function-xray-error-cause: " + handler_response.get_xray_response()).c_str()); - headers = curl_slist_append(headers, "Expect:"); - headers = curl_slist_append(headers, "transfer-encoding:"); - headers = curl_slist_append(headers, get_user_agent_header().c_str()); -@@ -511,13 +512,15 @@ invocation_response invocation_response::success(std::string const& payload, std - } - - AWS_LAMBDA_RUNTIME_API --invocation_response invocation_response::failure(std::string const& error_message, std::string const& error_type) -+invocation_response invocation_response::failure(std::string const& error_message, std::string const& error_type, std::string const& xray_response) - { - invocation_response r; - r.m_success = false; - r.m_content_type = "application/json"; - r.m_payload = R"({"errorMessage":")" + json_escape(error_message) + R"(","errorType":")" + json_escape(error_type) + - R"(", "stackTrace":[]})"; -+ r.m_xray_response = xray_response; -+ - return r; - } diff --git a/deps/patches/aws-lambda-cpp-make-lto-optional.patch b/deps/patches/aws-lambda-cpp-make-lto-optional.patch deleted file mode 100644 index 54f78d2..0000000 --- a/deps/patches/aws-lambda-cpp-make-lto-optional.patch +++ /dev/null @@ -1,36 +0,0 @@ -diff --git a/CMakeLists.txt b/CMakeLists.txt -index eb8327f..e6eeda5 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -4,10 +4,9 @@ project(aws-lambda-runtime - VERSION 0.2.6 - LANGUAGES CXX) - -+option(ENABLE_LTO "Enables link-time optimization, requires compiler support." ON) - option(ENABLE_TESTS "Enables building the test project, requires AWS C++ SDK." OFF) - --include(CheckIPOSupported) -- - add_library(${PROJECT_NAME} - "src/logging.cpp" - "src/runtime.cpp" -@@ -23,11 +22,14 @@ target_include_directories(${PROJECT_NAME} PUBLIC - $ - $) - --check_ipo_supported(RESULT has_lto OUTPUT lto_check_output) --if(has_lto) -- set_property(TARGET ${PROJECT_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) --else() -- message(WARNING "Link-time optimization (LTO) is not supported: ${lto_check_output}") -+if (ENABLE_LTO) -+ include(CheckIPOSupported) -+ check_ipo_supported(RESULT has_lto OUTPUT lto_check_output) -+ if(has_lto) -+ set_property(TARGET ${PROJECT_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) -+ else() -+ message(WARNING "Link-time optimization (LTO) is not supported: ${lto_check_output}") -+ endif() - endif() - - find_package(CURL REQUIRED) diff --git a/deps/patches/aws-lambda-cpp-make-the-runtime-client-user-agent-overrideable.patch b/deps/patches/aws-lambda-cpp-make-the-runtime-client-user-agent-overrideable.patch deleted file mode 100644 index 8be3552..0000000 --- a/deps/patches/aws-lambda-cpp-make-the-runtime-client-user-agent-overrideable.patch +++ /dev/null @@ -1,90 +0,0 @@ -From 846392515b2b0215902aaf7368651af196835f10 Mon Sep 17 00:00:00 2001 -From: Bryan Moffatt -Date: Wed, 21 Oct 2020 12:42:37 -0700 -Subject: [PATCH] make the Runtime Interface Client's user agent overrideable (#106) - -* make the Runtime Interface Client's user agent overrideable - -* remove extra empty string - -* clang-format -i src/* ---- - include/aws/lambda-runtime/runtime.h | 2 ++ - src/runtime.cpp | 20 ++++++++------------ - 2 files changed, 10 insertions(+), 12 deletions(-) - -diff --git a/include/aws/lambda-runtime/runtime.h b/include/aws/lambda-runtime/runtime.h -index 0dc292c..94e1e22 100644 ---- a/include/aws/lambda-runtime/runtime.h -+++ b/include/aws/lambda-runtime/runtime.h -@@ -137,6 +137,7 @@ public: - using next_outcome = aws::lambda_runtime::outcome; - using post_outcome = aws::lambda_runtime::outcome; - -+ runtime(std::string const& endpoint, std::string const& user_agent); - runtime(std::string const& endpoint); - ~runtime(); - -@@ -164,6 +165,7 @@ private: - invocation_response const& handler_response); - - private: -+ std::string const m_user_agent_header; - std::array const m_endpoints; - CURL* const m_curl_handle; - }; -diff --git a/src/runtime.cpp b/src/runtime.cpp -index e2ee7cd..f6131a4 100644 ---- a/src/runtime.cpp -+++ b/src/runtime.cpp -@@ -124,12 +124,6 @@ static size_t write_header(char* ptr, size_t size, size_t nmemb, void* userdata) - return size * nmemb; - } - --static std::string const& get_user_agent_header() --{ -- static std::string user_agent = std::string("User-Agent: AWS_Lambda_Cpp/") + get_version(); -- return user_agent; --} -- - static size_t read_data(char* buffer, size_t size, size_t nitems, void* userdata) - { - auto const limit = size * nitems; -@@ -163,10 +157,12 @@ static int rt_curl_debug_callback(CURL* handle, curl_infotype type, char* data, - } - #endif - --runtime::runtime(std::string const& endpoint) -- : m_endpoints{{endpoint + "/2018-06-01/runtime/init/error", -- endpoint + "/2018-06-01/runtime/invocation/next", -- endpoint + "/2018-06-01/runtime/invocation/"}}, -+runtime::runtime(std::string const& endpoint) : runtime(endpoint, "AWS_Lambda_Cpp/" + std::string(get_version())) {} -+ -+runtime::runtime(std::string const& endpoint, std::string const& user_agent) -+ : m_user_agent_header("User-Agent: " + user_agent), m_endpoints{{endpoint + "/2018-06-01/runtime/init/error", -+ endpoint + "/2018-06-01/runtime/invocation/next", -+ endpoint + "/2018-06-01/runtime/invocation/"}}, - m_curl_handle(curl_easy_init()) - { - if (!m_curl_handle) { -@@ -234,7 +230,7 @@ runtime::next_outcome runtime::get_next() - curl_easy_setopt(m_curl_handle, CURLOPT_HEADERDATA, &resp); - - curl_slist* headers = nullptr; -- headers = curl_slist_append(headers, get_user_agent_header().c_str()); -+ headers = curl_slist_append(headers, m_user_agent_header.c_str()); - curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, headers); - - logging::log_debug(LOG_TAG, "Making request to %s", m_endpoints[Endpoints::NEXT].c_str()); -@@ -343,7 +343,7 @@ runtime::post_outcome runtime::do_post( - 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, get_user_agent_header().c_str()); -+ headers = curl_slist_append(headers, m_user_agent_header.c_str()); - - logging::log_debug( - LOG_TAG, "calculating content length... %s", ("content-length: " + std::to_string(payload.length())).c_str()); --- -2.25.2 - diff --git a/deps/patches/aws-lambda-cpp-posting-init-errors.patch b/deps/patches/aws-lambda-cpp-posting-init-errors.patch deleted file mode 100644 index 7635443..0000000 --- a/deps/patches/aws-lambda-cpp-posting-init-errors.patch +++ /dev/null @@ -1,206 +0,0 @@ -diff --git a/include/aws/lambda-runtime/runtime.h b/include/aws/lambda-runtime/runtime.h -index be77d93..9597272 100644 ---- a/include/aws/lambda-runtime/runtime.h -+++ b/include/aws/lambda-runtime/runtime.h -@@ -67,28 +67,58 @@ 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 -@@ -102,12 +132,10 @@ public: - // To support clients that need to control the entire error response body (e.g. adding a stack trace), this - // 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, std::string const& xray_response): -- m_payload(payload), -- m_content_type(content_type), -- m_success(success), -- m_xray_response(xray_response) -- {} -+ invocation_response(std::string const& payload, std::string const& content_type, bool success, std::string const& xray_response) -+ : runtime_response(payload, content_type, xray_response), m_success(success) -+ { -+ } - - /** - * Create a successful invocation response with the given payload and content-type. -@@ -120,25 +148,10 @@ public: - */ - static invocation_response failure(std::string const& error_message, 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 { -@@ -167,13 +180,19 @@ public: - */ - 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(); - void set_curl_post_result_options(); - post_outcome 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); - - private: - std::array const m_endpoints; -diff --git a/src/runtime.cpp b/src/runtime.cpp -index d895c4b..659666e 100644 ---- a/src/runtime.cpp -+++ b/src/runtime.cpp -@@ -311,37 +311,44 @@ 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(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()); - } - -- 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, get_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()); -@@ -358,10 +365,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; - } - diff --git a/deps/versions b/deps/versions deleted file mode 100644 index bdb03af..0000000 --- a/deps/versions +++ /dev/null @@ -1,2 +0,0 @@ -AWS_LAMBDA_CPP_RELEASE=0.2.6 -CURL_VERSION=7_65_3 \ No newline at end of file diff --git a/scripts/preinstall.sh b/scripts/preinstall.sh deleted file mode 100755 index 288b79f..0000000 --- a/scripts/preinstall.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/sh -# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -set -e - -ARTIFACTS_DIR=$(pwd)/deps/artifacts - -if [ "$(uname)" = "Darwin" ]; then - echo "aws-lambda-cpp does not build on OS X. Skipping the preinstall step." -else - if [ -x "$(command -v cmake3)" ]; then - CMAKE=cmake3 - elif [ -x "$(command -v cmake)" ]; then - CMAKE=cmake - else - echo 'Error: cmake is not installed.' >&2 - exit 1 - fi - - cd deps - . ./versions - - rm -rf ./curl-curl-$CURL_VERSION - rm -rf ./aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE - - # unpack dependencies - tar xzf ./curl-$CURL_VERSION.tar.gz && \ - tar xzf ./aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE.tar.gz - - ( - # Build Curl - cd curl-curl-$CURL_VERSION && \ - ./buildconf && \ - ./configure \ - --prefix "$ARTIFACTS_DIR" \ - --disable-shared \ - --without-ssl \ - --without-zlib && \ - make && \ - make install - ) - - ( - # Build aws-lambda-cpp - mkdir -p ./aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE/build && \ - cd ./aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE/build - - $CMAKE .. \ - -DCMAKE_CXX_FLAGS="-fPIC" \ - -DCMAKE_INSTALL_PREFIX="$ARTIFACTS_DIR" \ - -DENABLE_LTO=$ENABLE_LTO \ - -DCMAKE_MODULE_PATH="$ARTIFACTS_DIR"/lib/pkgconfig && \ - make && make install - ) -fi \ No newline at end of file diff --git a/scripts/update_deps.sh b/scripts/update_deps.sh deleted file mode 100755 index db34a11..0000000 --- a/scripts/update_deps.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. -set -e - -cd deps -source versions - -# Clean up old files -rm -f aws-lambda-cpp-*.tar.gz && rm -f curl-*.tar.gz - -# Grab Curl -wget -c https://github.com/curl/curl/archive/curl-$CURL_VERSION.tar.gz - -# Grab aws-lambda-cpp -wget -c https://github.com/awslabs/aws-lambda-cpp/archive/v$AWS_LAMBDA_CPP_RELEASE.tar.gz -O - | tar -xz - -## Apply patches to aws-lambda-cpp -( - cd aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE && \ - patch -p1 < ../patches/aws-lambda-cpp-add-xray-response.patch && \ - patch -p1 < ../patches/aws-lambda-cpp-posting-init-errors.patch && \ - patch -p1 < ../patches/aws-lambda-cpp-make-the-runtime-client-user-agent-overrideable.patch && \ - patch -p1 < ../patches/aws-lambda-cpp-make-lto-optional.patch && \ - patch -p1 < ../patches/aws-lambda-cpp-add-content-type.patch -) - -## Pack again and remove the folder -tar -czvf aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE.tar.gz aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE && \ - rm -rf aws-lambda-cpp-$AWS_LAMBDA_CPP_RELEASE diff --git a/setup.py b/setup.py index 8aa17a1..e3f2507 100644 --- a/setup.py +++ b/setup.py @@ -9,46 +9,6 @@ from setuptools import Extension, find_packages, setup -def get_curl_extra_linker_flags(): - # We do not want to build the dependencies during packaging - if platform.system() != "Linux" or os.getenv("BUILD") == "true": - return [] - - # Build the dependencies - check_call(["./scripts/preinstall.sh"]) - - # call curl-config to get the required linker flags - cmd = ["./deps/artifacts/bin/curl-config", "--static-libs"] - curl_config = check_output(cmd).decode("utf-8").replace("\n", "") - - # It is expected that the result of the curl-config call is similar to - # "/tmp/pip-req-build-g9dlug7g/deps/artifacts/lib/libcurl.a -lidn2" - # we want to return just the extra flags - flags = curl_config.split(" ")[1:] - - return flags - - -def get_runtime_client_extension(): - if platform.system() != "Linux" and os.getenv("BUILD") != "true": - print( - "The native runtime_client only builds in Linux. Skiping its compilation." - ) - return [] - - runtime_client = Extension( - "runtime_client", - ["awslambdaric/runtime_client.cpp"], - extra_compile_args=["--std=c++11"], - library_dirs=["deps/artifacts/lib", "deps/artifacts/lib64"], - libraries=["aws-lambda-runtime", "curl"], - extra_link_args=get_curl_extra_linker_flags(), - include_dirs=["deps/artifacts/include"], - ) - - return [runtime_client] - - def read(*filenames, **kwargs): encoding = kwargs.get("encoding", "utf-8") sep = kwargs.get("sep", os.linesep) @@ -91,6 +51,5 @@ def read_requirements(req="base.txt"): "Operating System :: OS Independent", ], python_requires=">=3.6", - ext_modules=get_runtime_client_extension(), test_suite="tests", ) diff --git a/test/test_lambda_runtime_client.py b/test/test_lambda_runtime_client.py index 7b1bf66..40392c7 100644 --- a/test/test_lambda_runtime_client.py +++ b/test/test_lambda_runtime_client.py @@ -11,9 +11,14 @@ LambdaRuntimeClient, LambdaRuntimeClientError, InvocationRequest, + user_agent, ) +def mock_headers(headers): + return lambda name: headers[name] + + class TestInvocationRequest(unittest.TestCase): def test_constructor(self): invocation_request = InvocationRequest( @@ -55,10 +60,10 @@ def test_constructor(self): class TestLambdaRuntime(unittest.TestCase): - @patch("awslambdaric.lambda_runtime_client.runtime_client") - def test_wait_next_invocation(self, mock_runtime_client): + @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection) + def test_wait_next_invocation(self, MockHTTPConnection): response_body = b"{}" - headears = { + headers = { "Lambda-Runtime-Aws-Request-Id": "RID1234", "Lambda-Runtime-Trace-Id": "TID1234", "Lambda-Runtime-Invoked-Function-Arn": "FARN1234", @@ -67,7 +72,13 @@ def test_wait_next_invocation(self, mock_runtime_client): "Lambda-Runtime-Cognito-Identity": "cognito_identity", "Content-Type": "application/json", } - mock_runtime_client.next.return_value = response_body, headears + mock_response = MagicMock(autospec=http.client.HTTPResponse) + mock_response.read.return_value = response_body + mock_response.getheader.side_effect = mock_headers(headers) + mock_response.code = http.HTTPStatus.OK + mock_conn = MockHTTPConnection.return_value + mock_conn.getresponse.return_value = mock_response + runtime_client = LambdaRuntimeClient("localhost:1234") event_request = runtime_client.wait_next_invocation() @@ -95,7 +106,10 @@ def test_post_init_error(self, MockHTTPConnection): MockHTTPConnection.assert_called_with("localhost:1234") mock_conn.request.assert_called_once_with( - "POST", "/2018-06-01/runtime/init/error", "error_data" + "POST", + "/2018-06-01/runtime/init/error", + "error_data", + {"User-Agent": user_agent()}, ) mock_response.read.assert_called_once() @@ -116,20 +130,35 @@ def test_post_init_error_non_accepted_status_code(self, MockHTTPConnection): self.assertEqual(returned_exception.endpoint, "/2018-06-01/runtime/init/error") self.assertEqual(returned_exception.response_code, http.HTTPStatus.IM_USED) - @patch("awslambdaric.lambda_runtime_client.runtime_client") - def test_post_invocation_result(self, mock_runtime_client): + @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection) + def test_post_invocation_result(self, MockHTTPConnection): + mock_conn = MockHTTPConnection.return_value + mock_response = MagicMock(autospec=http.client.HTTPResponse) + mock_conn.getresponse.return_value = mock_response + mock_response.read.return_value = b"" + mock_response.code = http.HTTPStatus.OK + runtime_client = LambdaRuntimeClient("localhost:1234") response_data = "data" invoke_id = "1234" runtime_client.post_invocation_result(invoke_id, response_data) - mock_runtime_client.post_invocation_result.assert_called_once_with( - invoke_id, response_data.encode("utf-8"), "application/json" + mock_conn.request.assert_called_once_with( + "POST", + f"/2018-06-01/runtime/invocation/{invoke_id}/response", + response_data.encode("utf-8"), + {"Content-Type": "application/json", "User-Agent": user_agent()}, ) - @patch("awslambdaric.lambda_runtime_client.runtime_client") - def test_post_invocation_result_binary_data(self, mock_runtime_client): + @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection) + def test_post_invocation_result_binary_data(self, MockHTTPConnection): + mock_conn = MockHTTPConnection.return_value + mock_response = MagicMock(autospec=http.client.HTTPResponse) + mock_conn.getresponse.return_value = mock_response + mock_response.read.return_value = b"" + mock_response.code = http.HTTPStatus.OK + runtime_client = LambdaRuntimeClient("localhost:1234") response_data = b"binary_data" invoke_id = "1234" @@ -137,25 +166,40 @@ def test_post_invocation_result_binary_data(self, mock_runtime_client): runtime_client.post_invocation_result(invoke_id, response_data, content_type) - mock_runtime_client.post_invocation_result.assert_called_once_with( - invoke_id, response_data, content_type + mock_conn.request.assert_called_once_with( + "POST", + f"/2018-06-01/runtime/invocation/{invoke_id}/response", + response_data, + {"Content-Type": "application/octet-stream", "User-Agent": user_agent()}, ) - @patch("awslambdaric.lambda_runtime_client.runtime_client") - def test_post_invocation_result_failure(self, mock_runtime_client): + @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection) + def test_post_invocation_result_failure(self, MockHTTPConnection): + mock_conn = MockHTTPConnection.return_value + mock_response = MagicMock(autospec=http.client.HTTPResponse) + mock_conn.getresponse.return_value = mock_response + mock_response.read.return_value = b"" + mock_response.code = http.HTTPStatus.OK + runtime_client = LambdaRuntimeClient("localhost:1234") response_data = "data" invoke_id = "1234" - mock_runtime_client.post_invocation_result.side_effect = RuntimeError( + mock_conn.request.side_effect = RuntimeError( "Failed to post invocation response" ) with self.assertRaisesRegex(RuntimeError, "Failed to post invocation response"): runtime_client.post_invocation_result(invoke_id, response_data) - @patch("awslambdaric.lambda_runtime_client.runtime_client") - def test_post_invocation_error(self, mock_runtime_client): + @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection) + def test_post_invocation_error(self, MockHTTPConnection): + mock_conn = MockHTTPConnection.return_value + mock_response = MagicMock(autospec=http.client.HTTPResponse) + mock_conn.getresponse.return_value = mock_response + mock_response.read.return_value = b"" + mock_response.code = http.HTTPStatus.OK + runtime_client = LambdaRuntimeClient("localhost:1234") error_data = "data" invoke_id = "1234" @@ -163,12 +207,25 @@ def test_post_invocation_error(self, mock_runtime_client): runtime_client.post_invocation_error(invoke_id, error_data, xray_fault) - mock_runtime_client.post_error.assert_called_once_with( - invoke_id, error_data, xray_fault + mock_conn.request.assert_called_once_with( + "POST", + f"/2018-06-01/runtime/invocation/{invoke_id}/error", + error_data, + { + "User-Agent": user_agent(), + "Content-Type": "application/json", + "Lambda-Runtime-Function-XRay-Error-Cause": xray_fault, + }, ) - @patch("awslambdaric.lambda_runtime_client.runtime_client") - def test_post_invocation_error_with_large_xray_cause(self, mock_runtime_client): + @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection) + def test_post_invocation_error_with_large_xray_cause(self, MockHTTPConnection): + mock_conn = MockHTTPConnection.return_value + mock_response = MagicMock(autospec=http.client.HTTPResponse) + mock_conn.getresponse.return_value = mock_response + mock_response.read.return_value = b"" + mock_response.code = http.HTTPStatus.OK + runtime_client = LambdaRuntimeClient("localhost:1234") error_data = "data" invoke_id = "1234" @@ -176,12 +233,25 @@ def test_post_invocation_error_with_large_xray_cause(self, mock_runtime_client): runtime_client.post_invocation_error(invoke_id, error_data, large_xray_fault) - mock_runtime_client.post_error.assert_called_once_with( - invoke_id, error_data, large_xray_fault + mock_conn.request.assert_called_once_with( + "POST", + f"/2018-06-01/runtime/invocation/{invoke_id}/error", + error_data, + { + "User-Agent": user_agent(), + "Content-Type": "application/json", + "Lambda-Runtime-Function-XRay-Error-Cause": large_xray_fault, + }, ) - @patch("awslambdaric.lambda_runtime_client.runtime_client") - def test_post_invocation_error_with_too_large_xray_cause(self, mock_runtime_client): + @patch("http.client.HTTPConnection", autospec=http.client.HTTPConnection) + def test_post_invocation_error_with_too_large_xray_cause(self, MockHTTPConnection): + mock_conn = MockHTTPConnection.return_value + mock_response = MagicMock(autospec=http.client.HTTPResponse) + mock_conn.getresponse.return_value = mock_response + mock_response.read.return_value = b"" + mock_response.code = http.HTTPStatus.OK + runtime_client = LambdaRuntimeClient("localhost:1234") error_data = "data" invoke_id = "1234" @@ -191,8 +261,15 @@ def test_post_invocation_error_with_too_large_xray_cause(self, mock_runtime_clie invoke_id, error_data, too_large_xray_fault ) - mock_runtime_client.post_error.assert_called_once_with( - invoke_id, error_data, "" + mock_conn.request.assert_called_once_with( + "POST", + f"/2018-06-01/runtime/invocation/{invoke_id}/error", + error_data, + { + "User-Agent": user_agent(), + "Content-Type": "application/json", + "Lambda-Runtime-Function-XRay-Error-Cause": "", + }, ) def test_connection_refused(self):