aws cdk: serve swagger ui with api gateway - amazon-web-services

I want to use AWS ApiGateway to serve a swagger-ui of an API from a lambda function using express. For this I want to use the endpoint GET /docs.
My Lambda function swaggerUiLambdaHandler looks like this:
import express from 'express';
import serverless from 'serverless-http';
import swaggerUI from 'swagger-ui-express';
const options = {
swaggerOptions: {
url: 'https://petstore.swagger.io/v2/swagger.json',
},
};
const app = express();
app.use('/v1/docs', swaggerUI.serve, swaggerUI.setup(undefined, options));
module.exports.handler = serverless(app);
In the cdk stack of the api, I added the following 2 endpoints for serving the index.html and the .js/.css resources:
const docs = myApi.root.addResource('docs');
docs.addMethod('GET', new apigw.LambdaIntegration(swaggerUiLambdaHandler));
const docsProxy = docs.addResource('{proxy+}');
docsProxy.addMethod('GET', new apigw.LambdaIntegration(swaggerUiLambdaHandler));
However, calling the endpoint with the browser, all network requests succeed but I get a white canvas and the error
Uncaught SyntaxError: Unexpected token '<'
Do you have any idea? I think it might be related to the content-type returned by api gateway, but I would appreciate some help.

Related

Aws Api gateway Unable to load fast api swagger docs page

I have deployed a simple fast api to aws API gateway .All the end points working fine however i am unable to load the swagger docs page I see below error
Api Code:
from fastapi import FastAPI
from mangum import Mangum
import os
from fastapi.middleware.cors import CORSMiddleware
stage = os.environ.get('STAGE', None)
openapi_prefix = f"/{stage}" if stage else "/"
app = FastAPI(title="MyAwesomeApp",root_path="stage")
#app.get("/")
def get_root():
return {"message": "FastAPI running in a Lambda function"}
#app.get("/info")
def get_root():
return {"message": "TestInfo"}
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
handler = Mangum(app)
I tried adding the root as mentioned below
https://fastapi.tiangolo.com/advanced/behind-a-proxy/
Any help on this will be appreciated .
I found the solution we need to add root_path="/dev" like app = FastAPI(title=settings.NAME,root_path="/dev")
while creating fast api app .This root path should be same as the api gatway stage name .
However this approach does not work if you are using fastapi versioning

AWS Api-Gateway error 500 on postman / success on api gateway test

I am facing a strange issue with a lambda intergration in api gateway ( tried proxy as well same issue)
lambda first hits AppSync and returns either JSON content on error or a XLXS file on success.
while testing on API gateway test console it brings back status 200 and the binary results as expected. but when i try it externally through postman it fails.
More info :
Intergration type : Lambda
Success response :
response = buffer.toString("base64");
Error Response:
response= JSON.stringify(err);
Serverless apigateway setup:
exportXls:
handler: ./src/apiGatewayLambdas/exportxls/exportXls.handler
role: AppSyncLambdaRole
events:
- http:
path: /api/exportxls
method: post
integration: lambda
contentHandling: CONVERT_TO_BINARY
Apparently Apigateway with lambda or proxy integration encodes body to base64. so i changed my lambda to
let buffer = new Buffer(_event.body, "base64");
let body = buffer.toString("ascii");
body = JSON.parse(body);
and everything worked as expected .

Can I use AWS API Gateway as a reverse proxy for a S3 website?

I have a serverless website on AWS S3. But S3 have a limitation that I want to overcome: it don't allow me to have friendly URLs.
For example, I would like to replace URL:
www.mywebsite.com/user.html?login=daniel
With this URL friendly:
www.mywebsite.com/user/daniel
So, I would like to know if I can use Lambda together with API Gateway to achieve this.
My idea is:
API Gateway ---> Lambda function ---> fetch S3 resource
The API Gateway will get ANY request, and pass information to a Lambda funcion, that will process some logic using the request URL (including maybe some database query) and then fetch the resource from S3.
I know AWS API Gateway main purpose is to be a gateway to REST APIs, but can we also use it as a proxy to an entire website?
The good option can be to use CloudFront as a reverse proxy, you can use Viewer/Origin response request to trigger lambda and fetch the resource from S3.
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-examples.html
https://aws.amazon.com/blogs/networking-and-content-delivery/amazon-s3-amazon-cloudfront-a-match-made-in-the-cloud/
It is possible to use API Gateway as a reverse proxy for a S3 website.
I was able to do that following steps below:
In AWS API Gateway, create a "proxy resource" with resource path = "{proxy+}"
Go to AWS Certificate Manager and request a wildcard certificate for your website (*.mywebsite.com)
AWS will tell you to create a CNAME record in you domain registrar, to verify that you own that domain
After your certificate is validated, go to AWS API Gateway and create a Custom Domain Name (click on "Custom Domain Names" and then "Create Custom Domain Name"). In "domain name" type your domain (www.mywebsite.com) and select the ACM Certificate that you just created (step 1 above). Create a "Base Path Mapping" with path = "/" and in "destination" select your API and stage.
After that, you will need to add another CNAME record, with the CloudFront "Target Domain Name" that was generated for that Custom Domain Name.
In the Lambda, we can route the requests:
'use strict';
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const myBucket = 'myBucket';
exports.handler = async (event) => {
var responseBody = "";
if (event.path=="/") {
responseBody = "<h1>My Landing Page</h1>";
responseBody += "<a href='/xpto'>link to another page</a>";
return buildResponse(200, responseBody);
}
if (event.path=="/xpto") {
responseBody = "<h1>Another Page</h1>";
responseBody += "<a href='/'>home</a>";
return buildResponse(200, responseBody);
}
if (event.path=="/my-s3-resource") {
var params = {
Bucket: myBucket,
Key: 'path/to/my-s3-resource.html',
};
const data = await s3.getObject(params).promise();
return buildResponse(200, data.Body.toString('utf-8'));
}
return buildResponse(404, '404 Error');
};
function buildResponse(statusCode, responseBody) {
var response = {
"isBase64Encoded": false,
"statusCode": statusCode,
"headers": {
"Content-Type" : "text/html; charset=utf-8"
},
"body": responseBody,
};
return response;
}
A good bet would be to use CloudFront and Lambda#Edge.
Lambda#Edge allows you to run Lambda function in the edge location of the CloudFront CDN network.
CloudFront gives you the option to hook into various events during its lifecycle and apply logic.
This article looks like it might be describing something similar to what you're talking about.
https://aws.amazon.com/blogs/networking-and-content-delivery/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using-lambdaedge/

Password protect s3 bucket with lambda function in aws

I added website authentication for s3 bucket using lambda function and then connect the lambda function with the CloudFront by using behavior settings in distribution settings and it worked fine and added authentication(means htaccess authentication in simple servers). Now I want to change the password for my website authentication. For that, I updated the password and published the new version of the lambda function and then in the distribution settings; I created a new invalidation to clear cache. But it didn't work, and website authentication password didn't change. Below is my lambda function code to add authentication.
'use strict';
exports.handler = (event, context, callback) => {
// Get request and request headers
const request = event.Records[0].cf.request;
const headers = request.headers;
// Configure authentication
const authUser = 'user';
const authPass = 'pass';
// Construct the Basic Auth string
const authString = 'Basic ' + new Buffer(authUser + ':' + authPass).toString('base64');
// Require Basic authentication
if (typeof headers.authorization == 'undefined' || headers.authorization[0].value != authString) {
const body = 'Unauthorized';
const response = {
status: '401',
statusDescription: 'Unauthorized',
body: body,
headers: {
'www-authenticate': [{key: 'WWW-Authenticate', value:'Basic'}]
},
};
callback(null, response);
}
// Continue request processing if authentication passed
callback(null, request);
};
Can anyone please help me to solve the problem.
Thanks in advance.
On Lambda function view, After you save your changes (using Firefox could be a safer option, see below if you wonder why)
you will see a menu item under Configuration - > Designer -> CloudFront. You will see following screens.
After you deploy :
You can publish your change to CloudFront distribution. Once you publish this, it will automatically start deploying CF distribution which you can view on CF menu.
Also i would prefer using "Viewer Request" as a CloudFront trigger event, not sure which one you are using as this should avoid Cloudfront caching. On top of this Chrome sometimes fails to save changes on Lambda. There should be a bug on aws console. Try Firefox just to be safe when you are editing lambda functions.

How use segmented URL in AWS API Gateway?

I have a Lambda Function that it is accessible by an API Gateway. I can handle all POST and GET submitted requests to API endpoint (https://XXXXXXX.execute-api.us-east-1.amazonaws.com/default/myapi) inside my Lambda, but I need to use some segments at end of my URL when I am using PUT requests.
My Python code to call the API is here and it is working correctly:
import requests
import json
url = 'https://XXXXXXX.execute-api.us-east-1.amazonaws.com/default/myapi'
token = "my token"
data = {
"first_name": "Reza",
"birthday": "1986-09-12"
}
headers = {"Content-Type" : "application/json", "x-api-key":"MY_API_KEY"}
response = requests.put(url, data=json.dumps(data), headers=headers)
print(response.text)
But if I add users segment to end of the URL like this:
url = 'https://XXXXXXX.execute-api.us-east-1.amazonaws.com/default/myapi/users'
it will show this error:
{"message":"Missing Authentication Token"}
I need to add some static segments like users to return the list of all users and some dynamic segments like users/USER_ID (when USER_ID is a dynamic number) to return the information for a special user.
can you please guide me how I can use segmented URL in my AWS API Gateway?
The term you are using segmented URL might have caused your confusion. It is called path parameters with AWS. There is more than one way to do it. ANY+ integration is the easiest to handle.
Integrate with ANY+ integration to your lambda and you are good to go. All the path parameters will be delivered to your lambda.
http://www.1strategy.com/blog/2017/06/06/how-to-use-amazon-api-gateway-proxy/
Additional path parameter documentation,
https://docs.aws.amazon.com/apigateway/latest/developerguide/integrating-api-with-aws-services-lambda.html#api-as-lambda-proxy-expose-get-method-with-path-parameters-to-call-lambda-function
Good luck.