Problem with OPTIONS requests
See original GitHub issueDescription:
Trying to access my API from a test app in my browser and the OPTIONS
request is being handled by SAM CLI API Gateway but it’s not returning the configuration specified in my template file.
The weird part is that if I try to access a different endpoint from the same template it executes the lambda function (as expected since that’s what SAM Local did).
Additional environment details (Ex: Windows, Mac, Amazon Linux etc) Mac
Output of sam --version
:
SAM CLI, version 0.3.0
Optional Debug logs:
> export AWS_SDK_LOAD_CONFIG=true && sam local start-api --docker-network myblueprint "--debug-port" "5858"
2018-05-10 16:23:38 Mounting HealthCheckFunction at http://127.0.0.1:3000/healthcheck [OPTIONS, GET]
2018-05-10 16:23:38 Mounting EnsureUserFunction at http://127.0.0.1:3000/ensure_user [OPTIONS, PUT]
2018-05-10 16:23:38 Mounting GraphQLServerFunction at http://127.0.0.1:3000/graphql [GET, POST, OPTIONS]
2018-05-10 16:23:38 You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2018-05-10 16:23:38 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
2018-05-10 16:24:07 127.0.0.1 - - [10/May/2018 16:24:07] "OPTIONS /graphql HTTP/1.1" 200 -
2018-05-10 16:24:45 Invoking dist/healthcheck.handler (nodejs8.10)
2018-05-10 16:24:45 Found credentials in shared credentials file: ~/.aws/credentials
Fetching lambci/lambda:nodejs8.10 Docker container image......
2018-05-10 16:24:49 Mounting /Users/stefan/Projects/blueprint-api/packages/api as /var/task:ro inside runtime container
Debugger listening on ws://0.0.0.0:5858/698de0ac-e3af-4f27-b5dd-bf3caf4cbc58
For help see https://nodejs.org/en/docs/inspector
Debugger attached.
Debugger listening on ws://0.0.0.0:5858/698de0ac-e3af-4f27-b5dd-bf3caf4cbc58
For help see https://nodejs.org/en/docs/inspector
START RequestId: 2ee985d7-416b-182e-ec4a-79af497b0cc2 Version: $LATEST
2018-05-10T04:25:06.756Z 2ee985d7-416b-182e-ec4a-79af497b0cc2 ---TRIMMED ERROR---
END RequestId: 2ee985d7-416b-182e-ec4a-79af497b0cc2
REPORT RequestId: 2ee985d7-416b-182e-ec4a-79af497b0cc2 Duration: 2838.28 ms Billed Duration: 2900 ms Memory Size: 1024 MB Max Memory Used: 52 MB
2018-05-10 16:25:03 No Content-Type given. Defaulting to 'application/json'.
2018-05-10 16:25:03 127.0.0.1 - - [10/May/2018 16:25:03] "OPTIONS /healthcheck HTTP/1.1" 503 -
Template file for reference
---
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: MyBlueprint API
Parameters:
Environment:
Type: String
AllowedValues:
- DEVELOPMENT
- STAGING
- PRODUCTION
# DEV SHOULD NEVER BE RUN OUTSIDE OF SAM LOCAL
Mappings:
Config:
'DEVELOPMENT':
EnvironmentLowerCase: 'development'
HostedZoneName: 'localhost'
HostedZoneId: 'NONE'
S3FilesBucket: 'blah'
'STAGING':
EnvironmentLowerCase: 'staging'
HostedZoneName: '<REDACTED>'
HostedZoneId: '<REDACTED>'
S3FilesBucket: '<REDACTED>'
'PRODUCTION':
EnvironmentLowerCase: 'production'
HostedZoneName: '<REDACTED>'
HostedZoneId: '<REDACTED>'
S3FilesBucket: '<REDACTED>'
Conditions:
CreateS3FileProcessingResources:
!Equals [!Ref 'AWS::Region', 'ap-southeast-2']
Globals:
Function:
Runtime: 'nodejs8.10'
MemorySize: 1024
Timeout: 5
Tracing: PassThrough
Environment:
Variables:
CODE_ENVIRONMENT: !Ref Environment
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Action: 'sts:AssumeRole'
Effect: Allow
Principal:
Service: 'lambda.amazonaws.com'
Path: /
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
- 'arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess'
Policies:
- PolicyName: s3
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- 's3:*'
Effect: Allow
Resource: '*'
- PolicyName: parameters
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- 'ssm:GetParameter'
- 'ssm:GetParameters'
- 'ssm:GetParametersByPath'
Resource:
- !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${Environment}/API/*'
- Effect: Allow
Action:
- 'kms:Decrypt'
Resource:
- !Sub 'arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*'
MyBlueprintApi:
Type: 'AWS::Serverless::Api'
Properties:
StageName: 'prod'
EndpointConfiguration: 'REGIONAL'
Cors:
AllowMethods: "'GET,OPTIONS,POST,PUT'"
AllowHeaders: "'Authorization,Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
AllowOrigin: "'*'"
MaxAge: "'86400'"
DefinitionBody:
swagger: '2.0'
info:
version: '0.0.1'
title:
'Fn::Sub':
- 'myblueprint-${EnvironmentLowerCase}'
- EnvironmentLowerCase: !FindInMap [Config, !Ref Environment, EnvironmentLowerCase]
basePath: '/prod'
schemes:
- 'https'
paths:
'/graphql':
get:
consumes:
- 'application/json'
produces:
- 'application/json'
responses:
'200':
description: '200 response'
schema:
$ref: '#/definitions/Empty'
headers:
Access-Control-Allow-Origin:
type: 'string'
x-amazon-apigateway-integration:
responses:
default:
statusCode: '200'
responseParameters:
method.response.header.Access-Control-Allow-Origin: "'*'"
uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GraphQLServerFunction.Arn}/invocations'
passthroughBehavior: 'when_no_templates'
httpMethod: 'POST'
contentHandling: 'CONVERT_TO_TEXT'
type: 'aws_proxy'
post:
consumes:
- 'application/json'
produces:
- 'application/json'
responses:
'200':
description: '200 response'
schema:
$ref: '#/definitions/Empty'
headers:
Access-Control-Allow-Origin:
type: 'string'
x-amazon-apigateway-integration:
responses:
default:
statusCode: '200'
responseParameters:
method.response.header.Access-Control-Allow-Origin: "'*'"
uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GraphQLServerFunction.Arn}/invocations'
passthroughBehavior: 'when_no_templates'
httpMethod: 'POST'
contentHandling: 'CONVERT_TO_TEXT'
type: 'aws_proxy'
options:
consumes:
- 'application/json'
produces:
- 'application/json'
responses:
'200':
description: '200 response'
schema:
$ref: '#/definitions/Empty'
headers:
Access-Control-Allow-Origin:
type: 'string'
Access-Control-Allow-Methods:
type: 'string'
Access-Control-Allow-Headers:
type: 'string'
x-amazon-apigateway-integration:
responses:
default:
statusCode: '200'
responseParameters:
method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS,POST'"
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Origin: "'*'"
requestTemplates:
application/json: '{"statusCode": 200}'
uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GraphQLServerFunction.Arn}/invocations'
passthroughBehavior: 'when_no_match'
type: 'mock'
'/ensure_user':
put:
consumes:
- 'application/json'
produces:
- 'application/json'
responses:
'200':
description: '200 response'
schema:
$ref: '#/definitions/Empty'
headers:
Access-Control-Allow-Origin:
type: 'string'
x-amazon-apigateway-integration:
responses:
default:
statusCode: '200'
responseParameters:
method.response.header.Access-Control-Allow-Origin: "'*'"
uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EnsureUserFunction.Arn}/invocations'
passthroughBehavior: 'when_no_templates'
httpMethod: 'POST'
contentHandling: 'CONVERT_TO_TEXT'
type: 'aws_proxy'
options:
consumes:
- 'application/json'
produces:
- 'application/json'
responses:
'200':
description: '200 response'
schema:
$ref: '#/definitions/Empty'
headers:
Access-Control-Allow-Origin:
type: 'string'
Access-Control-Allow-Methods:
type: 'string'
Access-Control-Allow-Headers:
type: 'string'
x-amazon-apigateway-integration:
responses:
default:
statusCode: '200'
responseParameters:
method.response.header.Access-Control-Allow-Methods: "'PUT,OPTIONS'"
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Origin: "'*'"
requestTemplates:
application/json: '{"statusCode": 200}'
uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${EnsureUserFunction.Arn}/invocations'
passthroughBehavior: 'when_no_match'
type: 'mock'
'/healthcheck':
get:
consumes:
- 'application/json'
produces:
- 'application/json'
responses:
'200':
description: '200 response'
schema:
$ref: '#/definitions/Empty'
headers:
Access-Control-Allow-Origin:
type: 'string'
x-amazon-apigateway-integration:
responses:
default:
statusCode: '200'
responseParameters:
method.response.header.Access-Control-Allow-Origin: "'*'"
uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HealthCheckFunction.Arn}/invocations'
passthroughBehavior: 'when_no_templates'
httpMethod: 'POST'
contentHandling: 'CONVERT_TO_TEXT'
type: 'aws_proxy'
options:
consumes:
- 'application/json'
produces:
- 'application/json'
responses:
'200':
description: '200 response'
schema:
$ref: '#/definitions/Empty'
headers:
Access-Control-Allow-Origin:
type: 'string'
Access-Control-Allow-Methods:
type: 'string'
Access-Control-Allow-Headers:
type: 'string'
x-amazon-apigateway-integration:
responses:
default:
statusCode: '200'
responseParameters:
method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'"
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
method.response.header.Access-Control-Allow-Origin: "'*'"
requestTemplates:
application/json: '{"statusCode": 200}'
uri: !Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HealthCheckFunction.Arn}/invocations'
passthroughBehavior: 'when_no_match'
type: 'mock'
definitions:
Empty:
type: 'object'
title: 'Empty Schema'
GraphQLServerFunction:
Type: 'AWS::Serverless::Function'
Properties:
Handler: 'dist/graphql.handler'
CodeUri: './packages/api/'
Role: !GetAtt LambdaExecutionRole.Arn
MemorySize: 1536
Timeout: 30
Events:
GetResource:
Type: Api
Properties:
RestApiId: !Ref MyBlueprintApi
Path: '/graphql'
Method: get
OptionsResource:
Type: Api
Properties:
RestApiId: !Ref MyBlueprintApi
Path: '/graphql'
Method: options
PostResource:
Type: Api
Properties:
RestApiId: !Ref MyBlueprintApi
Path: '/graphql'
Method: post
EnsureUserFunction:
Type: 'AWS::Serverless::Function'
Properties:
Handler: 'dist/ensure_user.handler'
CodeUri: './packages/api/'
Role: !GetAtt LambdaExecutionRole.Arn
Events:
PutResource:
Type: Api
Properties:
RestApiId: !Ref MyBlueprintApi
Path: '/ensure_user'
Method: put
OptionsResource:
Type: Api
Properties:
RestApiId: !Ref MyBlueprintApi
Path: '/ensure_user'
Method: options
S3FileProcessingFunction:
Type: 'AWS::Serverless::Function'
Condition: 'CreateS3FileProcessingResources'
Properties:
Handler: 'dist/s3files.handler'
CodeUri: './packages/api/'
Role: !GetAtt LambdaExecutionRole.Arn
Timeout: 30
# Cant use this because the bucket must be created in the same template
# Events:
# ObjectCreated:
# Type: S3
# Properties:
# Bucket: !FindInMap [Config, !Ref Environment, S3FilesBucket]
# Events: s3:ObjectCreated:*
HealthCheckFunction:
Type: 'AWS::Serverless::Function'
Properties:
Handler: 'dist/healthcheck.handler'
CodeUri: './packages/api/'
Role: !GetAtt LambdaExecutionRole.Arn
Events:
GetResource:
Type: Api
Properties:
RestApiId: !Ref MyBlueprintApi
Path: '/healthcheck'
Method: get
OptionsResource:
Type: Api
Properties:
RestApiId: !Ref MyBlueprintApi
Path: '/healthcheck'
Method: options
# TODO: update this to use DNS validation when CF supports it
ApiCertificate:
Type: 'AWS::CertificateManager::Certificate'
Properties:
DomainName:
'Fn::Sub':
- 'api.${HostedZoneName}'
- HostedZoneName: !FindInMap [Config, !Ref Environment, HostedZoneName]
DomainValidationOptions:
- DomainName:
'Fn::Sub':
- 'api.${HostedZoneName}'
- HostedZoneName: !FindInMap [Config, !Ref Environment, HostedZoneName]
ValidationDomain: 'myblueprint.cloud'
CustomResourceLambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Action: 'sts:AssumeRole'
Effect: Allow
Principal:
Service: 'lambda.amazonaws.com'
Path: /
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
Policies:
- PolicyName: ApiGateway
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- 'apigateway:*'
Effect: Allow
Resource: '*'
DomainNameInfoCustomResourceFunction:
Type: 'AWS::Lambda::Function'
Properties:
Handler: index.handler
Role: !GetAtt CustomResourceLambdaExecutionRole.Arn
Runtime: 'nodejs6.10'
Timeout: 300
Code:
ZipFile: |
const AWS = require('aws-sdk');
const response = require('cfn-response');
exports.handler = function(event, context) {
const ApiGateway = new AWS.APIGateway();
ApiGateway.getDomainName({
domainName: event.ResourceProperties.DomainName
}, (err, data) => {
if (err != null) {
response.send(event, context, response.FAILED, undefined);
} else {
response.send(event, context, response.SUCCESS, {
DomainName: data.domainName,
RegionalDomainName: data.regionalDomainName,
RegionalHostedZoneId: data.regionalHostedZoneId,
DistributionDomainName: data.distributionDomainName,
DistributionHostedZoneId: data.distributionHostedZoneId
});
}
});
}
ApiDomainName:
Type: 'AWS::ApiGateway::DomainName'
Properties:
DomainName:
'Fn::Sub':
- 'api.${HostedZoneName}'
- HostedZoneName: !FindInMap [Config, !Ref Environment, HostedZoneName]
EndpointConfiguration:
Types:
- REGIONAL
RegionalCertificateArn: !Ref ApiCertificate
ApiBasePathMapping:
Type: 'AWS::ApiGateway::BasePathMapping'
DependsOn: [MyBlueprintApi, ApiDomainName]
Properties:
DomainName:
'Fn::Sub':
- 'api.${HostedZoneName}'
- HostedZoneName: !FindInMap [Config, !Ref Environment, HostedZoneName]
RestApiId: !Ref MyBlueprintApi
Stage: 'prod'
ApiDomainNameInfo:
Type: 'Custom::DomainNameInfo'
DependsOn: [ApiDomainName, ApiBasePathMapping]
Properties:
ServiceToken: !GetAtt DomainNameInfoCustomResourceFunction.Arn
DomainName: !Ref ApiDomainName
ApiHealthCheck:
Type: 'AWS::Route53::HealthCheck'
Properties:
HealthCheckConfig:
Port: 443
Type: 'HTTPS_STR_MATCH'
SearchString: 'ok'
ResourcePath: '/prod/healthcheck'
FullyQualifiedDomainName: !Sub '${MyBlueprintApi}.execute-api.${AWS::Region}.amazonaws.com'
RequestInterval: 60
FailureThreshold: 2
HealthCheckTags:
- Key: Name
Value:
'Fn::Sub':
- 'api-regional-${EnvironmentLowerCase}-${Region}'
- EnvironmentLowerCase: !FindInMap [Config, !Ref Environment, EnvironmentLowerCase]
Region: !Ref 'AWS::Region'
ApiRecordSet:
Type: 'AWS::Route53::RecordSet'
DependsOn: [ApiDomainNameInfo]
Properties:
HostedZoneId: !FindInMap [Config, !Ref Environment, HostedZoneId]
Name:
'Fn::Sub':
- 'lbr-api.${HostedZoneName}'
- HostedZoneName: !FindInMap [Config, !Ref Environment, HostedZoneName]
ResourceRecords:
- !GetAtt ApiDomainNameInfo.RegionalDomainName
Region: !Ref 'AWS::Region'
SetIdentifier: !Sub 'api-${AWS::Region}'
HealthCheckId: !Ref ApiHealthCheck
Type: CNAME
TTL: 60
Outputs:
RestAPIID:
Description: Rest API ID
Value: !Ref MyBlueprintApi
ApiUrl:
Description: URL of your API endpoint
Value: !Ref ApiRecordSet
HealthcheckApiUrl:
Description: URL of your API health check endpoint
Value: !Sub 'https://${MyBlueprintApi}.execute-api.${AWS::Region}.amazonaws.com/prod/healthcheck'
Issue Analytics
- State:
- Created 5 years ago
- Reactions:3
- Comments:33 (14 by maintainers)
Top Results From Across the Web
Why is an OPTIONS request sent and can I disable it?
Options request is a preflight request when you send (post) any data to another domain. It's a browser security issue. But we can...
Read more >OPTIONS - HTTP - MDN Web Docs
The HTTP OPTIONS method requests permitted communication options for a given URL or server. A client can specify a URL with this method, ......
Read more >Why is my browser sending an OPTIONS HTTP request ...
As far as I know, only requests that are meant to be sent to a different origin and are not a form content-type...
Read more >Request not responding, Why an OPTIONS method is sent ...
I've been having a problem i don't really have using jQuery Ajax methods, when i use axios, my request sends one Request Method:OPTIONS...
Read more >Why Is an OPTIONS Request Sent? - Baeldung
The OPTIONS request mentioned in the introduction is a preflight request, which is part of the CORS (Cross-Origin Resource Sharing).
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
@rayhaanq Give this a try in your cloudformation
yaml
Finally make it work after a long day suffer from this issue.
It’s a good idea to write another lambda for options method which response successful pre-light check.
I didn’t notice that AWS has mentioned it in their docs [https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html](how to cors)