github.com/mweagle/Sparta@v1.15.0/docs_source/content/reference/apigateway/echo_event.md (about)

     1  ---
     2  date: 2016-03-09T19:56:50+01:00
     3  title: Echo
     4  weight: 10
     5  ---
     6  
     7  To start, we'll create a HTTPS accessible lambda function that simply echoes back the contents of incoming API Gateway
     8  Lambda event. The source for this is the [SpartaHTML](https://github.com/mweagle/SpartaHTML).
     9  
    10  For reference, the `helloWorld` function is below.
    11  
    12  ```go
    13  import (
    14    awsLambdaEvents "github.com/aws/aws-lambda-go/events"
    15    spartaAPIGateway "github.com/mweagle/Sparta/aws/apigateway"
    16  )
    17  
    18  func helloWorld(ctx context.Context,
    19    gatewayEvent spartaAWSEvents.APIGatewayRequest) (*spartaAPIGateway.Response, error) {
    20    logger, loggerOk := ctx.Value(sparta.ContextKeyLogger).(*logrus.Logger)
    21    if loggerOk {
    22      logger.Info("Hello world structured log message")
    23    }
    24    // Return a message, together with the incoming input...
    25    return spartaAPIGateway.NewResponse(http.StatusOK, &helloWorldResponse{
    26      Message: fmt.Sprintf("Hello world 🌏"),
    27      Request: gatewayEvent,
    28    }), nil
    29  }
    30  ```
    31  
    32  ## API Gateway
    33  
    34  The first requirement is to create a new [API](https://godoc.org/github.com/mweagle/Sparta#API) instance via [sparta.NewAPIGateway()](https://godoc.org/github.com/mweagle/Sparta#NewAPIGateway).
    35  
    36  ```go
    37  stage := sparta.NewStage("prod")
    38  apiGateway := sparta.NewAPIGateway("MySpartaAPI", stage)
    39  ```
    40  
    41  In the example above, we're also including a [Stage](https://godoc.org/github.com/mweagle/Sparta#Stage) value.
    42  A non-`nil` Stage value will cause the registered API to be deployed.  If the Stage value is `nil`, a REST API will be created,
    43  but it will not be [deployed](http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-deploy-api.html)
    44  (and therefore not publicly accessible).
    45  
    46  ## Resource
    47  
    48  The next step is to associate a URL path with the `sparta.LambdaAWSInfo` struct that represents the **go** function:
    49  
    50  ```go
    51  func spartaHTMLLambdaFunctions(api *sparta.API) []*sparta.LambdaAWSInfo {
    52    var lambdaFunctions []*sparta.LambdaAWSInfo
    53    lambdaFn, _ := sparta.NewAWSLambda(sparta.LambdaName(helloWorld),
    54      helloWorld,
    55      sparta.IAMRoleDefinition{})
    56  
    57    if nil != api {
    58      apiGatewayResource, _ := api.NewResource("/hello", lambdaFn)
    59  
    60      // We only return http.StatusOK
    61      apiMethod, apiMethodErr := apiGatewayResource.NewMethod("GET",
    62        http.StatusOK,
    63        http.StatusInternalServerError)
    64      if nil != apiMethodErr {
    65        panic("Failed to create /hello resource: " + apiMethodErr.Error())
    66      }
    67      // The lambda resource only supports application/json Unmarshallable
    68      // requests.
    69      apiMethod.SupportedRequestContentTypes = []string{"application/json"}
    70    }
    71    return append(lambdaFunctions, lambdaFn)
    72  }
    73  ```
    74  
    75  Our `helloWorld` only supports `GET`.  We'll see how a single lambda function can support multiple HTTP methods shortly.
    76  
    77  ## Provision
    78  
    79  The final step is to to provide the API instance to `Sparta.Main()`
    80  
    81  ```go
    82  // Register the function with the API Gateway
    83  apiStage := sparta.NewStage("v1")
    84  apiGateway := sparta.NewAPIGateway("SpartaHTML", apiStage)
    85  ```
    86  
    87  Once the service is successfully provisioned, the `Outputs` key will include the API Gateway Deployed URL (sample):
    88  
    89  ```text
    90  INFO[0096] ────────────────────────────────────────────────
    91  INFO[0096] Stack Outputs
    92  INFO[0096] ────────────────────────────────────────────────
    93  INFO[0096] S3SiteURL                                     Description="S3 Website URL" Value="http://spartahtml-mweagle-s3site89c05c24a06599753eb3ae4e-1w6rehqu6x04c.s3-website-us-west-2.amazonaws.com"
    94  INFO[0096] APIGatewayURL                                 Description="API Gateway URL" Value="https://w2tefhnt4b.execute-api.us-west-2.amazonaws.com/v1"
    95  INFO[0096] ────────────────────────────────────────────────
    96  ```
    97  
    98  Combining the _API Gateway URL_ `OutputValue` with our resource path (_/hello/world/test_), we get the absolute URL to our lambda function: [https://w2tefhnt4b.execute-api.us-west-2.amazonaws.com/v1/hello](https://w2tefhnt4b.execute-api.us-west-2.amazonaws.com/v1/hello)
    99  
   100  ## Verify
   101  
   102  Let's query the lambda function and see what the `event` data is at execution time. The
   103  snippet below is pretty printed by piping the response through [jq](https://stedolan.github.io/jq/).
   104  
   105  ```nohighlight
   106  $ curl -vs https://3e7ux226ga.execute-api.us-west-2.amazonaws.com/v1/hello | jq .
   107  *   Trying 52.84.237.220...
   108  * TCP_NODELAY set
   109  * Connected to 3e7ux226ga.execute-api.us-west-2.amazonaws.com (52.84.237.220) port 443 (#0)
   110  * TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
   111  * Server certificate: *.execute-api.us-west-2.amazonaws.com
   112  * Server certificate: Amazon
   113  * Server certificate: Amazon Root CA 1
   114  * Server certificate: Starfield Services Root Certificate Authority - G2
   115  > GET /v1/hello HTTP/1.1
   116  > Host: 3e7ux226ga.execute-api.us-west-2.amazonaws.com
   117  > User-Agent: curl/7.54.0
   118  > Accept: */*
   119  >
   120  < HTTP/1.1 200 OK
   121  < Content-Type: application/json
   122  < Content-Length: 1137
   123  < Connection: keep-alive
   124  < Date: Mon, 29 Jan 2018 14:15:28 GMT
   125  < x-amzn-RequestId: db7f5734-04fe-11e8-b264-c70ecab3a032
   126  < Access-Control-Allow-Origin: http://spartahtml-mweagle-s3site89c05c24a06599753eb3ae4e-419zo4dp8n2d.s3-website-us-west-2.amazonaws.com
   127  < Access-Control-Allow-Headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key
   128  < Access-Control-Allow-Methods: *
   129  < X-Amzn-Trace-Id: sampled=0;root=1-5a6f2c80-efb0f84554384252abca6d15
   130  < X-Cache: Miss from cloudfront
   131  < Via: 1.1 570a1979c411cb4529fa1e711db52490.cloudfront.net (CloudFront)
   132  < X-Amz-Cf-Id: -UsCegiR1K3vJUFyAo9IMrWGdH8rKW6UBrtJLjxZqke19r0cxMl1NA==
   133  <
   134  { [1137 bytes data]
   135  * Connection #0 to host 3e7ux226ga.execute-api.us-west-2.amazonaws.com left intact
   136  {
   137    "Message": "Hello world 🌏",
   138    "Request": {
   139      "method": "GET",
   140      "body": {},
   141      "headers": {
   142        "Accept": "*/*",
   143        "CloudFront-Forwarded-Proto": "https",
   144        "CloudFront-Is-Desktop-Viewer": "true",
   145        "CloudFront-Is-Mobile-Viewer": "false",
   146        "CloudFront-Is-SmartTV-Viewer": "false",
   147        "CloudFront-Is-Tablet-Viewer": "false",
   148        "CloudFront-Viewer-Country": "US",
   149        "Host": "3e7ux226ga.execute-api.us-west-2.amazonaws.com",
   150        "User-Agent": "curl/7.54.0",
   151        "Via": "1.1 570a1979c411cb4529fa1e711db52490.cloudfront.net (CloudFront)",
   152        "X-Amz-Cf-Id": "vAFNTV5uAMeTG9JN6IORnA7LYJhZyB3jHV7vh-7lXn2uZQUR6eHQUw==",
   153        "X-Amzn-Trace-Id": "Root=1-5a6f2c80-2b48a9c86a30b0162d8ab1f1",
   154        "X-Forwarded-For": "73.118.138.121, 205.251.214.60",
   155        "X-Forwarded-Port": "443",
   156        "X-Forwarded-Proto": "https"
   157      },
   158      "queryParams": {},
   159      "pathParams": {},
   160      "context": {
   161        "appId": "",
   162        "method": "GET",
   163        "requestId": "db7f5734-04fe-11e8-b264-c70ecab3a032",
   164        "resourceId": "401s9n",
   165        "resourcePath": "/hello",
   166        "stage": "v1",
   167        "identity": {
   168          "accountId": "",
   169          "apiKey": "",
   170          "caller": "",
   171          "cognitoAuthenticationProvider": "",
   172          "cognitoAuthenticationType": "",
   173          "cognitoIdentityId": "",
   174          "cognitoIdentityPoolId": "",
   175          "sourceIp": "73.118.138.121",
   176          "user": "",
   177          "userAgent": "curl/7.54.0",
   178          "userArn": ""
   179        }
   180      }
   181    }
   182  }
   183  ```
   184  
   185  While this demonstrates that our lambda function is publicly accessible, it's not immediately obvious where the `*event` data is being populated.
   186  
   187  ## Mapping Templates
   188  
   189  The event data that's actually supplied to `echoS3Event` is the complete HTTP request body.  This content is what the API Gateway sends to our lambda function, which is defined by the integration mapping.  This event data also includes the values of any whitelisted parameters.  When the API Gateway Method is defined, it optionally includes any whitelisted query params and header values that should be forwarded to the integration target.  For this example, we're not whitelisting any params, so those fields (`queryParams`, `pathParams`) are empty.  Then for each integration target (which can be AWS Lambda, a mock, or a HTTP Proxy), it's possible to transform the API Gateway request data and whitelisted arguments into a format that's more amenable to the target.
   190  
   191  Sparta uses a pass-through template that passes all valid data, with minor **Body** differences based on the inbound _Content-Type_:
   192  
   193  ### _application/json_
   194  
   195    {{% import file="./static/source/resources/provision/apigateway/inputmapping_json.vtl" language="nohighlight" %}}
   196  
   197  ### _*_ (Default `Content-Type`)
   198  
   199    {{% import file="./static/source/resources/provision/apigateway/inputmapping_default.vtl" language="nohighlight" %}}
   200  
   201  The default mapping templates forwards all whitelisted data & body to the lambda function.  You can see by switching on the `method` field would allow a single function to handle different HTTP methods.
   202  
   203  The next example shows how to unmarshal this data and perform request-specific actions.
   204  
   205  ## Proxying Envelope
   206  
   207  Because the integration request returned a successful response, the API Gateway response body contains only our lambda's output (`$input.json('$.body')`).
   208  
   209  To return an error that API Gateway can properly translate into an HTTP
   210  status code, use an [apigateway.NewErrorResponse](https://godoc.org/github.com/mweagle/Sparta/aws/apigateway#NewErrorResponse) type. This
   211  custom error type includes fields that trigger integration mappings based on the
   212  inline [HTTP StatusCode](https://golang.org/src/net/http/status.go). The proper error
   213  code is extracted by lifting the `code` value from the Lambda's response body and
   214  using a [template override](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-override-request-response-parameters.html)
   215  
   216  If you look at the **Integration Response** section of the _/hello/world/test_ resource in the Console, you'll see a list of Regular Expression matches:
   217  
   218  ## Cleanup
   219  
   220  Before moving on, remember to decommission the service via:
   221  
   222  ```bash
   223  go run application.go delete
   224  ```
   225  
   226  ## Wrapping Up
   227  
   228  Now that we know what data is actually being sent to our API Gateway-connected Lambda function, we'll move on to performing a more complex operation, including returning a custom HTTP response body.
   229  
   230  ### Notes
   231  
   232  * [Mapping Template Reference](http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html)