I am somewhat new to writing yaml files and deploying them. I have one built from things that I have worked on in the past, but it is producing results that I don't understand and I believe that this is causing a CORS error when I used the API Gateway (It works using insomnia/postman).
Here is the YAML file.
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Storygraf backend API
Globals:
Function:
Timeout: 3
Resources:
ExpressApi:
Type: AWS::Serverless::Api
Properties:
StageName: prod
StageName: dev
ExpressLambdaFunction:
Type: AWS::Serverless::Function
Properties:
Environment:
Variables:
CodeUri: ./
Handler: lambda.handler
MemorySize: 512
Runtime: nodejs14.x
Timeout: 30
Events:
ProxyApiRoot:
Type: Api
Properties:
RestApiId: !Ref ExpressApi
Path: /
Method: ANY
cors: true
ProxyApiGreedy:
Type: Api
Properties:
RestApiId: !Ref ExpressApi
Path: /{proxy+}
Method: ANY
cors: true
I don't really understand the ProxyAPIRoot and ProxyAPIGreedy settings, but I have run the deployment with and without that section and gotten the same results.
I am also attempting to create two stages on the API gateway - one for production and one for development but it occurs to me that I don't really understand the stages very well and the result that I am getting has a "dev" stage (which is correct) and a "Stage" stage, which is not correct.
Here are the resources and stages for the resulting gateway.
Why is there are "Stage" stage and not a "prod" stage?
What is the proxy? Is it causing my CORS problem?
You have to create separate SAM YAML files for different environments. You cannot specify two stages in the same YAML file. If you want to deploy the complete stack in one YAML file, I suggest you switch to pure Cloudformation template, then you would have a lot more flexibility on defining resources.
You can checkout this link and create multiple stages.
Regarding the /{proxy+} resource, it will capture all the requests, like /products, /users/1. Whereas, the / resource will only respond to requests to the endpoint root url, i.e / only.
Related
I am trying to build out an app with an api gateway and a node back end running on lambda.
The node backend is very basic with express and sequelize. It isn't hooked up to a database yet. I can run the node app locally, navigate to an end point and get the standard "hello world" response that I send back from that end point.
When I build and deploy the stack, it shows that the code deployed to an s3 bucket and that the api and lambda were updated. However, I get the following error when trying to access an end point -
502 Bad Gateway - "message": "Internal server error"
Again, there is no logic on the back end, just a text response sent back. I have no idea how to address this. It doesn't seem to be an API issue, although I don't know how to test that. If it is an issue with the lambda deploy, why does it work locally?
In addition to this, the api gateway created by the cloudformation file has a "dev" stage and a "Stage" stage. I am not sure how this happened or if it is a problem.
Below is the yaml file.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Storygraf backend API
Globals:
Function:
Timeout: 3
Resources:
ExpressApi:
Type: AWS::Serverless::Api
Properties:
StageName: dev
ExpressLambdaFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://storygraf-backend/xxxx
Handler: lambda.handler
MemorySize: 512
Runtime: nodejs14.x
Timeout: 30
Events:
ProxyApiRoot:
Type: Api
Properties:
RestApiId:
Ref: ExpressApi
Path: /
Method: ANY
ProxyApiGreedy:
Type: Api
Properties:
RestApiId:
Ref: ExpressApi
Path: /{proxy+}
Method: ANY
You should activate logs to be able to troubleshoot:
ExpressApi:
Type: AWS::Serverless::Api
Properties:
StageName: dev
MethodSettings:
- ResourcePath: "/*"
HttpMethod: "*"
DataTraceEnabled: true
LoggingLevel: INFO # Or ERROR
OpenApiVersion: 3.0.1 # This will remove the stage Stage
Note about OpenApiVersion:
Note: Setting this property to any valid value will also remove the stage Stage that SAM creates.
Then copy your apiId:
Send your request to your API and go to CloudWatch, there should be a Log Group named API-Gateway-Execution-Logs_{apiId}/dev (you can paste your apiId in the search box to find it).
In there you can see detailed logs about the request and integration.
You most likely have an error like:
Execution failed due to configuration error: Malformed Lambda proxy response
Because lambda response should at least contain a statusCode field:
With the Lambda proxy integration, the Lambda function must return output of the following format:
statusCode: "...", // a valid HTTP status code
headers: {
custom-header: "..." // any API-specific custom header
},
body: "...", // a JSON string.
isBase64Encoded: true|false // for binary support } ```
See API Gateway developer guide and specifically:
Getting Started
Integration Response
Lambda Integration
Exactly as the title says. I have an API with an HTTP POST request. When I run the POST request with the API Running locally (with sam local start-api), it works perfectly fine. However, when I deploy it to and run it in API Gateway, I get a timeout error after 30 seconds, even though I set the max timeout to 300 seconds. My SAM file is as below:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
python3.8
Sample SAM Template for manipulation-api
Globals:
Function:
Timeout: 300
Resources:
rManipulationAPI:
Type: AWS::Serverless::Api
Properties:
Cors:
AllowMethods: "'GET, POST'"
AllowOrigin: "'*'"
Name: manipulation-api
StageName: dev
rManipulationFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: Manipulate
MemorySize: 10000
PackageType: Image
ImageUri: <My Docker Repo>
Events:
Paraphrase:
Type: Api
Properties:
Path: /manipulate
Method: POST
RestApiId: !Ref rManipulationAPI
Metadata:
Dockerfile: Dockerfile
DockerContext: ./src/manipulate
DockerTag: python3.8-v1
rHealthCheckFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: HealthCheck
PackageType: Image
ImageUri: <My Docker Repo>
Events:
HealthCheck:
Type: Api
Properties:
Path: /health
Method: GET
RestApiId: !Ref rManipulationAPI
Metadata:
Dockerfile: Dockerfile
DockerContext: ./src/health_check
DockerTag: python3.8-v1
Outputs:
ManipulationApi:
Value: !Sub "https://${rManipulationAPI}.execute-api.${AWS::Region}.amazonaws.com/dev/manipulate"
This is a limitation of the API Gateway service, the API Gateway will timeout after 30 seconds regardless of what your Lambda timeout is set to.
If you expect your Lambdas execution times consistently get close to or take longer than 30 seconds, you'll need to move to more of an asynchronous pattern.
A few common patterns:
Client Polling: You provide two endpoints: one for submitting a request that returns instantly (i.e. /submitRequest) with a request ID; and another that allows a customer get the status of the request and determine if it's done and get the final result (i.e. /getResult).
Callbacks: Provide an endpoint that allows users to submit requests but also allow them to provide a URL that the service invokes when the request is completed with the results. Or just provide an SNS topic that users can subscribe to to get the results of their request.
Regardless, API Gateway can't support 30+ second responses. In general, it's not a good practice to design APIs that have these long waits as well since many things can happen to the connection. It's better to offer an asynchronous solution to clients which will be much less error prone.
I'm trying to work my way through AWS sam and setting up a new ApiGateway. I want the lambdas to run without the 'use lambda proxy integration' setting. I've been trying for ages now, and haven't made much progress.
A simplified version of my template.yaml
Resources:
MyLambda:
Type: AWS::Serverless::Function
Properties:
#snip#
Events:
PostEvent:
Type: Api
Properties:
Path: /Some/Path
Method: Post
RestApiId:
Ref: MyApi
MyApi:
Type: AWS::Serverless::Api
Properties:
Name: Some-Api
StageName: Prod
As you can see, I haven't made any progress at all. I'm simply getting lost in the configuration here, and hoping that anyone can point me in the right direction
Sicne you have opted out of integration proxy, the request and response may need mapping.
Have you tried updating the integration response - mapping template, try below,
AWS::Serverless::Api
GatewayResponses
Using Serverless Framework to
deploy AWS Lambda functions, Serverless creates (or receives) the
specific URL endpoint string. I want to use that string (as a variable)
in another section of the serverless.yml specification file.
Is that URL endpoint available as a variable in serverless.yml?
The Serverless Framework documentation on AWS-related variables
does not seem to answer that case.
Details: my serverless.yml contains a provider: specification
similar to:
provider:
name: aws
runtime: python3.6
memorySize: 512
region: ${opt:region, 'eu-west-1'}
profile: ${opt:profile, 'default'}
stage: ${opt:stage, 'staging'}
and a functions: section starting with:
functions:
round-control:
name: ${self:provider.stage}-round-control
runtime: nodejs8.10
handler: round/control/app.lambdaHandler
events:
- http:
path: round/control
method: get
After a
serverless deploy --profile [my-aws-profile]
the Lambda function sample-experiments-staging-round-control
is reported to be available at endpoint
https://1a234bc5de.execute-api.eu-west-1.amazonaws.com/staging/round/control.
Question: is there a variable in Serverless available that contains
that 1a234bc5de, or 1a234bc5de.execute-api or perhaps even
1a234bc5de.execute-api.eu-west-1.amazonaws.com?
(Obviously, I can also construct the last two if I know the first.)
With that variable, I can construct the full URL endpoint, which I
need in another place in the serverless.yml file.
N.B. That 1a234bc5de isn't a dynamically generated random
string - my current project is (per stage, per region) 'fixed' to
the same string. Perhaps that string is generated at AWS Lambda or
AWS API Gateway?
I was able to pass the URL and unique ID for the API Gateway endpoint to a Lambda function as environment variables as follows:
mylambda:
handler: mylambda.handler
runtime: python3.7
events:
- http:
path: id
cors: true
environment:
APIG_UID: !Ref "ApiGatewayRestApi"
APIG_URL:
!Join
- ''
- - 'https://'
- !Ref ApiGatewayRestApi
- '.execute-api.'
- ${opt:region, self:provider.region}
- '.amazonaws.com/'
- ${opt:stage, self:provider.stage}
Thanks to goingserverless.
Here is a simpler and more modern alternative:
provider:
environment:
API_URL: !Sub 'https://${ApiGatewayRestApi}.execute-api.${aws:region}.amazonaws.com/${sls:stage}'
Note: the code above applies to REST API (API Gateway v1), not HTTP API.
If you are using the new HTTP API (API Gateway v2), the solution is simpler:
provider:
environment:
API_URL: !GetAtt HttpApi.ApiEndpoint
We use Cloud Formation for define a bunch of Lambda functions:
AWSTemplateFormatVersion: '2010-09-09'
Transform:
- 'AWS::Serverless-2016-10-31'
Resources:
MyLambda:
Type: 'AWS::Serverless::Function'
Properties:
Handler: com.handler::MyLambda
Runtime: java8
CodeUri: .
Description: Some desc
MemorySize: 512
Timeout: 15
Role: !Ref LambdaRole
FunctionName: MyLambda
Events:
MyLambdaEvt:
Type: Api
Properties:
RestApiId: !Ref MyApiDef
Path: /lambda/my
Method: get
MyApiDef:
Type: AWS::Serverless::Api
Properties:
DefinitionUri: s3://a-bucket/api-gateway.yml
StageName: prod
Outputs:
ApiUrl:
Description: URL of your API endpoint
Value: !Join
- ''
- - https://
- !Ref MyApiDef
- '.execute-api.'
- !Ref 'AWS::Region'
- '.amazonaws.com/prod'
A CodePipeline generate a changeset and execute it.
In this way all the Lambda function are correctly updated but the API Gateway endpoint are not update correctly and we need to import and deploy the YML in s3://a-bucket/api-gateway.yml manually.
Why the API doesn't update (an educated guess)
In order for a change to be added to a a change set, CloudFormation has to detect a change. If the only thing that changes (for MyApiDef) between deployments is the contents of the .yaml file out on S3, CloudFormation isn't going to detect a change that it needs to add to the change set.
If this API definition lived in the CF template, rather than a file on S3, CF would (obviously) detect every change and update the API for you.
Since the definition lives in S3, and the file name hasn't changed, no change is detected, so nothing gets updated.
Possible work arounds
You have to convince CloudFormation that something has changed with your API definition. These two things worked for me:
Updating the MyApiDef key itself each run works. (MyApiDefv2,
MyApiDefv3, etc)
Updating the DefinitionUri works. (i.e. version the
filename in S3).
Neither of these is great, but appending a version to the filename in S3 seems more reasonable than the other option.
There are probably other ways to convince CloudFormation a change has taken place. Notably, I could not get Variables to work for this purpose.