# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 defmodule ElixirRuntime.LambdaServiceClient do @moduledoc """ This module represents an HTTP client for the Lambda Runtime service. """ require Logger @behaviour ElixirRuntime.Loop.Client @behaviour ElixirRuntime.Monitor.Client alias ElixirRuntime.LambdaServiceClient def service_endpoint do System.get_env("AWS_LAMBDA_RUNTIME_API") end @impl true @spec invocation_error( ElixirRuntime.Monitor.Client.error(), ElixirRuntime.Monitor.Client.id() ) :: no_return def invocation_error(err_msg, id) do url = 'http://#{service_endpoint()}/2018-06-01/runtime/invocation/#{id}/error' request = {url, [], 'text/plain', err_msg} {:ok, _response} = :httpc.request(:post, request, [], []) end @impl true @spec init_error(Monitor.Client.error()) :: no_return def init_error(err_msg) do url = 'http://#{service_endpoint()}/2018-06-01/runtime/init/error' request = {url, [], 'text/plain', err_msg} {:ok, _response} = :httpc.request(:post, request, [], []) end @impl true @spec complete_invocation( Runtime.Client.id(), Runtime.Client.response() ) :: no_return def complete_invocation(id, response) do url = 'http://#{service_endpoint()}/2018-06-01/runtime/invocation/#{id}/response' request = {url, [], 'text/plain', response} {:ok, _response} = :httpc.request(:post, request, [], []) end @impl true @spec next_invocation() :: Runtime.Client.invocation() def next_invocation do url = 'http://#{service_endpoint()}/2018-06-01/runtime/invocation/next' response = :httpc.request(:get, {url, []}, [], []) Logger.debug("Http get from #{url} was #{Kernel.inspect(response)}") parse(response) end defp parse(_response = {:ok, {{_, 200, _}, headers, body}}) do context = LambdaServiceClient.Context.from_headers(headers) {LambdaServiceClient.Context.request_id(context), body, context} end defp parse(_response) do :no_invocation end end defmodule ElixirRuntime.LambdaServiceClient.Context do @request_id "lambda-runtime-aws-request-id" @trace_id "lambda-runtime-trace-id" @client_context "x-amz-client-context" @cognito_identity "x-amz-cognito-identity" @deadline_ns "lambda-runtime-deadline-ms" @invoked_function_arn "lambda-runtime-invoked-function-arn" @known_headers [ @request_id, @trace_id, @client_context, @cognito_identity, @deadline_ns, @invoked_function_arn ] @spec from_headers([{String.t(), String.t()}]) :: Map.t() def from_headers(headers) do headers |> Enum.map(fn {field, value} -> {to_string(field), to_string(value)} end) |> Enum.map(fn {field, value} -> {String.downcase(field), value} end) |> Enum.filter(fn {field, _} -> field in @known_headers end) |> Map.new() end @spec request_id(Map.t()) :: String.t() def request_id(context) when is_map(context) do context[@request_id] end end