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

     1  ---
     2  date: 2016-03-09T19:56:50+01:00
     3  title: Request Parameters
     4  weight: 11
     5  ---
     6  
     7  # Request Parameters
     8  
     9  This example demonstrates how to accept client request params supplied as HTTP query params and return an expiring S3 URL to access content.
    10  The source for this is the [s3ItemInfo](https://github.com/mweagle/SpartaImager/blob/master/application.go#L149)
    11  function defined as part of the  [SpartaApplication](https://github.com/mweagle/SpartaApplication).
    12  
    13  ## Lambda Definition
    14  
    15  Our function will accept two params:
    16  
    17  * `bucketName` : The S3 bucket name storing the asset
    18  * `keyName` : The S3 item key
    19  
    20  Those params will be passed as part of the URL query string.  The function will fetch the item metadata, generate an expiring URL for public S3 access, and return a JSON response body with the item data.
    21  
    22  Because [s3ItemInfo](https://github.com/mweagle/SpartaImager/blob/master/application.go#L149) is expected to be invoked by the API Gateway, we'll use the AWS Lambda Go type in the function signature:
    23  
    24  ```go
    25  import (
    26    spartaAPIGateway "github.com/mweagle/Sparta/aws/apigateway"
    27    spartaEvents "github.com/mweagle/Sparta/aws/events"
    28  )
    29  
    30  func s3ItemInfo(ctx context.Context,
    31    apigRequest spartaEvents.APIGatewayRequest) (*spartaAPIGateway.Response, error) {
    32    logger, _ := ctx.Value(sparta.ContextKeyLogger).(*logrus.Logger)
    33    lambdaContext, _ := awsLambdaContext.FromContext(ctx)
    34  
    35    logger.WithFields(logrus.Fields{
    36      "RequestID": lambdaContext.AwsRequestID,
    37    }).Info("Request received")
    38  
    39    getObjectInput := &s3.GetObjectInput{
    40      Bucket: aws.String(apigRequest.QueryParams["bucketName"]),
    41      Key:    aws.String(apigRequest.QueryParams["keyName"]),
    42    }
    43  
    44    awsSession := spartaAWS.NewSession(logger)
    45    svc := s3.New(awsSession)
    46    result, err := svc.GetObject(getObjectInput)
    47    if nil != err {
    48      return nil, err
    49    }
    50    presignedReq, _ := svc.GetObjectRequest(getObjectInput)
    51    url, err := presignedReq.Presign(5 * time.Minute)
    52    if nil != err {
    53      return nil, err
    54    }
    55    return spartaAPIGateway.NewResponse(http.StatusOK,
    56      &itemInfoResponse{
    57        S3:  result,
    58        URL: url,
    59      }), nil
    60  }
    61  ```
    62  
    63  The [sparta.APIGatewayRequest](https://godoc.org/github.com/mweagle/Sparta/aws/events#APIGatewayRequest) fields
    64  correspond to the Integration Response Mapping template discussed in the [previous example](/reference/apigateway/echo_event)
    65  (see the full mapping template [here](/reference/apigateway).
    66  
    67  Once the event is unmarshaled, we can use it to fetch the S3 item info:
    68  
    69  ```go
    70  getObjectInput := &s3.GetObjectInput{
    71    Bucket: aws.String(lambdaEvent.QueryParams["bucketName"]),
    72    Key:    aws.String(lambdaEvent.QueryParams["keyName"]),
    73  }
    74  ```
    75  
    76  Assuming there are no errors (including the case where the item does not exist), the
    77  remainder of the function fetches the data, generates a presigned URL, and returns a JSON response whose
    78  shape matches the Sparta default [mapping templates](https://docs.aws.amazon.com/apigateway/latest/developerguide/models-mappings.html):
    79  
    80  ```go
    81  awsSession := spartaAWS.NewSession(logger)
    82  svc := s3.New(awsSession)
    83  result, err := svc.GetObject(getObjectInput)
    84  if nil != err {
    85    return nil, err
    86  }
    87  presignedReq, _ := svc.GetObjectRequest(getObjectInput)
    88  url, err := presignedReq.Presign(5 * time.Minute)
    89  if nil != err {
    90    return nil, err
    91  }
    92  return spartaAPIGateway.NewResponse(http.StatusOK,
    93    &itemInfoResponse{
    94      S3:  result,
    95      URL: url,
    96    }), nil
    97  ```
    98  
    99  ## API Gateway
   100  
   101  The next step is to create a new [API](https://godoc.org/github.com/mweagle/Sparta#API) instance via `sparta.NewAPIGateway()`
   102  
   103  ```go
   104  apiStage := sparta.NewStage("v1")
   105  apiGateway := sparta.NewAPIGateway("SpartaImagerAPI", apiStage)
   106  ```
   107  
   108  ## Lambda Binding
   109  
   110  Next we create an `sparta.LambdaAWSInfo` struct that references the `s3ItemInfo` function:
   111  
   112  ```go
   113  var iamDynamicRole = sparta.IAMRoleDefinition{}
   114  iamDynamicRole.Privileges = append(iamDynamicRole.Privileges,
   115    sparta.IAMRolePrivilege{
   116      Actions:  []string{"s3:GetObject"},
   117      Resource: resourceArn,
   118    })
   119  s3ItemInfoLambdaFn, _ := sparta.NewAWSLambda(sparta.LambdaName(s3ItemInfo),
   120    s3ItemInfo,
   121    iamDynamicRole)
   122  s3ItemInfoOptions.Options = &sparta.LambdaFunctionOptions{
   123    Description: "Get information about an item in S3 via querystring params",
   124    MemorySize:  128,
   125    Timeout:     10,
   126  }
   127  ```
   128  
   129  A few items to note here:
   130  
   131  * We're providing a custom `LambdaFunctionOptions` in case the request to S3 to get item metadata exceeds the default 3 second timeout.
   132  * We also add a custom `iamDynamicRole.Privileges` entry to the `Privileges` slice that authorizes the lambda function to _only_ access objects in a single bucket (_resourceArn_).
   133    * This bucket ARN is externally created and the ARN provided to this code.
   134    * While the API will accept any _bucketName_ value, it is only authorized to access a single bucket.
   135  
   136  ## Resources
   137  
   138  The next step is to associate a URL path with the `sparta.LambdaAWSInfo` struct that represents the `s3ItemInfo` function. This will be the relative path component used to reference our lambda function via the API Gateway.
   139  
   140  ```go
   141  apiGatewayResource, _ := api.NewResource("/info", s3ItemInfoLambdaFn)
   142  method, err := apiGatewayResource.NewMethod("GET", http.StatusOK)
   143  if err != nil {
   144    return nil, err
   145  }
   146  ```
   147  
   148  ## Whitelist Input
   149  
   150  The final step is to add the whitelisted parameters to the Method definition.
   151  
   152  ```go
   153  // Whitelist query string params
   154  method.Parameters["method.request.querystring.keyName"] = true
   155  method.Parameters["method.request.querystring.bucketName"] = true
   156  ```
   157  
   158  Note that the keynames in the `method.Parameters` map must be of the form: **method.request.{location}.{name}** where location is one of:
   159  
   160  * `querystring`
   161  * `path`
   162  * `header`
   163  
   164  See the [REST documentation](http://docs.aws.amazon.com/apigateway/api-reference/resource/method/#requestParameters) for more information.
   165  
   166  ## Provision
   167  
   168  With everything configured, let's provision the stack:
   169  
   170  ```nohighlight
   171  go run application.go --level debug provision --s3Bucket $S3_BUCKET
   172  ```
   173  
   174  and check the results.
   175  
   176  ## Verify
   177  
   178  As this Sparta application includes an API Gateway definition, the stack `Outputs` includes the API Gateway URL:
   179  
   180  ```text
   181  INFO[0243] Stack Outputs ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
   182  INFO[0243]     APIGatewayURL                             Description="API Gateway URL" Value="https://xccmsl98p1.execute-api.us-west-2.amazonaws.com/v1"
   183  INFO[0243] Stack provisioned                             CreationTime="2018-12-11 14:56:41.051 +0000 UTC" StackId="arn:aws:cloudformation:us-west-2:123412341234:stack/SpartaImager-mweagle/f7b7d3e0-fd54-11e8-9064-0aa3372404a6" StackName=SpartaImager-mweagle
   184  INFO[0243] ════════════════════════════════════════════════
   185  ```
   186  
   187  Let's fetch an item we know exists:
   188  
   189  ```nohighlight
   190  $ curl -vs "https://xccmsl98p1.execute-api.us-west-2.amazonaws.com/v1/info?keyName=twitterAvatar.jpg&bucketName=weagle-public"
   191  
   192  *   Trying 13.32.254.241...
   193  * TCP_NODELAY set
   194  * Connected to xccmsl98p1.execute-api.us-west-2.amazonaws.com (13.32.254.241) port 443 (#0)
   195  * ALPN, offering h2
   196  * ALPN, offering http/1.1
   197  * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
   198  * successfully set certificate verify locations:
   199  *   CAfile: /etc/ssl/cert.pem
   200    CApath: none
   201  * TLSv1.2 (OUT), TLS handshake, Client hello (1):
   202  * TLSv1.2 (IN), TLS handshake, Server hello (2):
   203  * TLSv1.2 (IN), TLS handshake, Certificate (11):
   204  * TLSv1.2 (IN), TLS handshake, Server key exchange (12):
   205  * TLSv1.2 (IN), TLS handshake, Server finished (14):
   206  * TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
   207  * TLSv1.2 (OUT), TLS change cipher, Client hello (1):
   208  * TLSv1.2 (OUT), TLS handshake, Finished (20):
   209  * TLSv1.2 (IN), TLS change cipher, Client hello (1):
   210  * TLSv1.2 (IN), TLS handshake, Finished (20):
   211  * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
   212  * ALPN, server accepted to use h2
   213  * Server certificate:
   214  *  subject: CN=*.execute-api.us-west-2.amazonaws.com
   215  *  start date: Oct  9 00:00:00 2018 GMT
   216  *  expire date: Oct  9 12:00:00 2019 GMT
   217  *  subjectAltName: host "xccmsl98p1.execute-api.us-west-2.amazonaws.com" matched cert's "*.execute-api.us-west-2.amazonaws.com"
   218  *  issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon
   219  *  SSL certificate verify ok.
   220  * Using HTTP2, server supports multi-use
   221  * Connection state changed (HTTP/2 confirmed)
   222  * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
   223  * Using Stream ID: 1 (easy handle 0x7ff68b802c00)
   224  > GET /v1/info?keyName=twitterAvatar.jpg&bucketName=weagle-public HTTP/2
   225  > Host: xccmsl98p1.execute-api.us-west-2.amazonaws.com
   226  > User-Agent: curl/7.54.0
   227  > Accept: */*
   228  >
   229  * Connection state changed (MAX_CONCURRENT_STREAMS updated)!
   230  < HTTP/2 200
   231  < content-type: application/json
   232  < content-length: 1539
   233  < date: Tue, 11 Dec 2018 15:08:56 GMT
   234  < x-amzn-requestid: aded8786-fd56-11e8-836c-dff86eb3938d
   235  < access-control-allow-origin: *
   236  < access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key
   237  < x-amz-apigw-id: Rv3pRH8jPHcFTfA=
   238  < access-control-allow-methods: *
   239  < x-amzn-trace-id: Root=1-5c0fd308-f576dae00848eb44535a5c70;Sampled=0
   240  < x-cache: Miss from cloudfront
   241  < via: 1.1 8ddadd1ab84a7f1bef108d6a72eccf06.cloudfront.net (CloudFront)
   242  < x-amz-cf-id: OO01Dua9x5dHyXr-arKJ3LKu2ahbPYv5ESqUg2lAhlzLJDQTLVyW_A==
   243  <
   244  {"S3":{"AcceptRanges":"bytes","Body":{},"CacheControl":null,"ContentDisposition":null,"ContentEncoding":null,"ContentLanguage":null,"ContentLength":613560,"ContentRange":null,"ContentType":"image/jpeg","DeleteMarker":null,"ETag":"\"7250a1802a5e2f94532b9ee38429a3fd\"","Expiration":null,"Expires":null,"LastModified":"2018-03-14T14:55:19Z","Metadata":{},"MissingMeta":null,"ObjectLockLegalHoldStatus":null,"ObjectLockMode":null,"ObjectLockRetainUntilDate":null,"PartsCount":null,"ReplicationStatus":null,"RequestCharged":null,"Restore":null,"SSECustomerAlgorithm":null,"SSECustomerKeyMD5":null,"SSEKMSKeyId":null,"ServerSideEncryption":null,"StorageClass":null,"TagCount":null,"VersionId":null,"WebsiteRedirectLocation":null},"URL":"https://weagle-public.s3.us-west-2.amazonaws.com/twitterAvatar.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAQMUWTUUFF65WLRLE%2F20181211%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20181211T150856Z&X-Amz-Expires=300&X-Amz-Security-Token=FQoGZXIvYXdzEIH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDMMVITmbkwrrxznAHCL9AaUQwfC%2F%2F6go%2FKBZigDuI4BLLwJzqiwhquTZ9TR1oxVKOAA0h6WzWUEfjjOjZK56SFk3cIJ%2FjKIBmImKpTIGyN7fn48s6N51RFFxra2Mamrp1pDqEcP4VswnJH8C5Q7ZfmltJDiFqLbd4FCQdgoGT228Ls49Uo24EyT%2B%2BTL%2Fl0sKTVYtI1MbGSK%2B%2BKZ6rpPEsyR%2FTuIdeDvA1P%2BRlMEyvr0NhO7Wpf7ZZMs3taNcUMQDRmARyIgAp87ziwIavUTaPqbgpGNqJ6XAO%2Byf3y0g9JurYj44HrwpLWmuF5g%2B%2FtLv8VikzqD8GuWARJuo%2BPlH54KmcMrbXBpLq9sZl2Io3KO%2F4AU%3D&X-Amz-SignedHeaders=host&X-Amz-Signature=88976d33d4cdefff02265e1f40e4d18005231672f1a6e41ad12733f0ce97e91b"}
   245  ```
   246  
   247  Pretty printing the response body:
   248  
   249  ```json
   250  {
   251      "S3": {
   252          "AcceptRanges": "bytes",
   253          "Body": {},
   254          "CacheControl": null,
   255          "ContentDisposition": null,
   256          "ContentEncoding": null,
   257          "ContentLanguage": null,
   258          "ContentLength": 613560,
   259          "ContentRange": null,
   260          "ContentType": "image/jpeg",
   261          "DeleteMarker": null,
   262          "ETag": "\"7250a1802a5e2f94532b9ee38429a3fd\"",
   263          "Expiration": null,
   264          "Expires": null,
   265          "LastModified": "2018-03-14T14:55:19Z",
   266          "Metadata": {},
   267          "MissingMeta": null,
   268          "ObjectLockLegalHoldStatus": null,
   269          "ObjectLockMode": null,
   270          "ObjectLockRetainUntilDate": null,
   271          "PartsCount": null,
   272          "ReplicationStatus": null,
   273          "RequestCharged": null,
   274          "Restore": null,
   275          "SSECustomerAlgorithm": null,
   276          "SSECustomerKeyMD5": null,
   277          "SSEKMSKeyId": null,
   278          "ServerSideEncryption": null,
   279          "StorageClass": null,
   280          "TagCount": null,
   281          "VersionId": null,
   282          "WebsiteRedirectLocation": null
   283      },
   284      "URL": "https://weagle-public.s3.us-west-2.amazonaws.com/twitterAvatar.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAQMUWTUUFF65WLRLE%2F20181211%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20181211T150856Z&X-Amz-Expires=300&X-Amz-Security-Token=FQoGZXIvYXdzEIH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDMMVITmbkwrrxznAHCL9AaUQwfC%2F%2F6go%2FKBZigDuI4BLLwJzqiwhquTZ9TR1oxVKOAA0h6WzWUEfjjOjZK56SFk3cIJ%2FjKIBmImKpTIGyN7fn48s6N51RFFxra2Mamrp1pDqEcP4VswnJH8C5Q7ZfmltJDiFqLbd4FCQdgoGT228Ls49Uo24EyT%2B%2BTL%2Fl0sKTVYtI1MbGSK%2B%2BKZ6rpPEsyR%2FTuIdeDvA1P%2BRlMEyvr0NhO7Wpf7ZZMs3taNcUMQDRmARyIgAp87ziwIavUTaPqbgpGNqJ6XAO%2Byf3y0g9JurYj44HrwpLWmuF5g%2B%2FtLv8VikzqD8GuWARJuo%2BPlH54KmcMrbXBpLq9sZl2Io3KO%2F4AU%3D&X-Amz-SignedHeaders=host&X-Amz-Signature=88976d33d4cdefff02265e1f40e4d18005231672f1a6e41ad12733f0ce97e91b"
   285  }
   286  ```
   287  
   288  What about an item that we know doesn't exist, but is in the bucket our lambda function has privileges to access:
   289  
   290  ```text
   291  $ curl -vs "https://xccmsl98p1.execute-api.us-west-2.amazonaws.com/v1/info?keyName=NOT_HERE.jpg&bucketName=weagle-public"
   292  
   293  *   Trying 13.32.254.241...
   294  * TCP_NODELAY set
   295  * Connected to xccmsl98p1.execute-api.us-west-2.amazonaws.com (13.32.254.241) port 443 (#0)
   296  * ALPN, offering h2
   297  * ALPN, offering http/1.1
   298  * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
   299  * successfully set certificate verify locations:
   300  *   CAfile: /etc/ssl/cert.pem
   301    CApath: none
   302  * TLSv1.2 (OUT), TLS handshake, Client hello (1):
   303  * TLSv1.2 (IN), TLS handshake, Server hello (2):
   304  * TLSv1.2 (IN), TLS handshake, Certificate (11):
   305  * TLSv1.2 (IN), TLS handshake, Server key exchange (12):
   306  * TLSv1.2 (IN), TLS handshake, Server finished (14):
   307  * TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
   308  * TLSv1.2 (OUT), TLS change cipher, Client hello (1):
   309  * TLSv1.2 (OUT), TLS handshake, Finished (20):
   310  * TLSv1.2 (IN), TLS change cipher, Client hello (1):
   311  * TLSv1.2 (IN), TLS handshake, Finished (20):
   312  * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
   313  * ALPN, server accepted to use h2
   314  * Server certificate:
   315  *  subject: CN=*.execute-api.us-west-2.amazonaws.com
   316  *  start date: Oct  9 00:00:00 2018 GMT
   317  *  expire date: Oct  9 12:00:00 2019 GMT
   318  *  subjectAltName: host "xccmsl98p1.execute-api.us-west-2.amazonaws.com" matched cert's "*.execute-api.us-west-2.amazonaws.com"
   319  *  issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon
   320  *  SSL certificate verify ok.
   321  * Using HTTP2, server supports multi-use
   322  * Connection state changed (HTTP/2 confirmed)
   323  * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
   324  * Using Stream ID: 1 (easy handle 0x7f9e4f00b600)
   325  > GET /v1/info?keyName=twitterAvatarArgh.jpg&bucketName=weagle HTTP/2
   326  > Host: xccmsl98p1.execute-api.us-west-2.amazonaws.com
   327  > User-Agent: curl/7.54.0
   328  > Accept: */*
   329  >
   330  * Connection state changed (MAX_CONCURRENT_STREAMS updated)!
   331  < HTTP/2 404
   332  < content-type: application/json
   333  < content-length: 177
   334  < date: Tue, 11 Dec 2018 15:21:18 GMT
   335  < x-amzn-requestid: 675edef9-fd58-11e8-ae45-3fac75041f4d
   336  < access-control-allow-origin: *
   337  < access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key
   338  < x-amz-apigw-id: Rv5dAETkvHcFvYg=
   339  < access-control-allow-methods: *
   340  < x-amzn-trace-id: Root=1-5c0fd5ec-1d8bba64519f71126c12b4d6;Sampled=0
   341  < x-cache: Error from cloudfront
   342  < via: 1.1 4c4ed81695980f3c6829b9fd229bd0f8.cloudfront.net (CloudFront)
   343  < x-amz-cf-id: ZT5R4BUSAkZpT46s_wCjBImHsM3w6mHFlYG0lnfwONSkPCgxzOQ_lQ==
   344  <
   345  {"error":"AccessDenied: Access Denied\n\tstatus code: 403, request id: A10C69E17E4C9D00, host id: pAnhP+tg9rDh0yP5FJyC8bSnj1GJJjJvAFXwiluW4yHnVvt5EvkvkpKA4UzjJmCoFyI8hGST6YE="}
   346  * Connection #0 to host xccmsl98p1.execute-api.us-west-2.amazonaws.com left intact
   347  ```
   348  
   349  And finally, what if we try to access a bucket that our lambda function isn't authorized to access:
   350  
   351  ```text
   352  $ curl -vs "https://xccmsl98p1.execute-api.us-west-2.amazonaws.com/v1/info?keyName=NOT_HERE.jpg&bucketName=VERY_PRIVATE_BUCKET"
   353  
   354  *   Trying 13.32.254.241...
   355  * TCP_NODELAY set
   356  * Connected to xccmsl98p1.execute-api.us-west-2.amazonaws.com (13.32.254.241) port 443 (#0)
   357  * ALPN, offering h2
   358  * ALPN, offering http/1.1
   359  * Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
   360  * successfully set certificate verify locations:
   361  *   CAfile: /etc/ssl/cert.pem
   362    CApath: none
   363  * TLSv1.2 (OUT), TLS handshake, Client hello (1):
   364  * TLSv1.2 (IN), TLS handshake, Server hello (2):
   365  * TLSv1.2 (IN), TLS handshake, Certificate (11):
   366  * TLSv1.2 (IN), TLS handshake, Server key exchange (12):
   367  * TLSv1.2 (IN), TLS handshake, Server finished (14):
   368  * TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
   369  * TLSv1.2 (OUT), TLS change cipher, Client hello (1):
   370  * TLSv1.2 (OUT), TLS handshake, Finished (20):
   371  * TLSv1.2 (IN), TLS change cipher, Client hello (1):
   372  * TLSv1.2 (IN), TLS handshake, Finished (20):
   373  * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
   374  * ALPN, server accepted to use h2
   375  * Server certificate:
   376  *  subject: CN=*.execute-api.us-west-2.amazonaws.com
   377  *  start date: Oct  9 00:00:00 2018 GMT
   378  *  expire date: Oct  9 12:00:00 2019 GMT
   379  *  subjectAltName: host "xccmsl98p1.execute-api.us-west-2.amazonaws.com" matched cert's "*.execute-api.us-west-2.amazonaws.com"
   380  *  issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon
   381  *  SSL certificate verify ok.
   382  * Using HTTP2, server supports multi-use
   383  * Connection state changed (HTTP/2 confirmed)
   384  * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
   385  * Using Stream ID: 1 (easy handle 0x7f9e4f00b600)
   386  > GET /v1/info?keyName=twitterAvatarArgh.jpg&bucketName=weagle HTTP/2
   387  > Host: xccmsl98p1.execute-api.us-west-2.amazonaws.com
   388  > User-Agent: curl/7.54.0
   389  > Accept: */*
   390  >
   391  * Connection state changed (MAX_CONCURRENT_STREAMS updated)!
   392  < HTTP/2 404
   393  < content-type: application/json
   394  < content-length: 177
   395  < date: Tue, 11 Dec 2018 15:21:18 GMT
   396  < x-amzn-requestid: 675edef9-fd58-11e8-ae45-3fac75041f4d
   397  < access-control-allow-origin: *
   398  < access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key
   399  < x-amz-apigw-id: Rv5dAETkvHcFvYg=
   400  < access-control-allow-methods: *
   401  < x-amzn-trace-id: Root=1-5c0fd5ec-1d8bba64519f71126c12b4d6;Sampled=0
   402  < x-cache: Error from cloudfront
   403  < via: 1.1 4c4ed81695980f3c6829b9fd229bd0f8.cloudfront.net (CloudFront)
   404  < x-amz-cf-id: ZT5R4BUSAkZpT46s_wCjBImHsM3w6mHFlYG0lnfwONSkPCgxzOQ_lQ==
   405  <
   406  {"error":"AccessDenied: Access Denied\n\tstatus code: 403, request id: A10C69E17E4C9D00, host id: pAnhP+tg9rDh0yP5FJyC8bSnj1GJJjJvAFXwiluW4yHnVvt5EvkvkpKA4UzjJmCoFyI8hGST6YE="}
   407  * Connection #0 to host xccmsl98p1.execute-api.us-west-2.amazonaws.com left intact
   408  ```
   409  
   410  ## Cleanup
   411  
   412  Before moving on, remember to decommission the service via:
   413  
   414  ```nohighlight
   415  go run application.go delete
   416  ```
   417  
   418  ## Conclusion
   419  
   420  With this example we've walked through a simple example that whitelists user input, uses IAM Roles to
   421  limit what S3 buckets a lambda function may access, and returns an _application/json_ response to the caller.