tile_path and error handling for lambda

This commit is contained in:
Brandon Liu
2022-10-19 22:36:28 +08:00
parent 9346762aa5
commit 543b8ef50f

View File

@@ -1,14 +1,20 @@
import { Readable } from "stream";
import { Context, APIGatewayProxyResult, APIGatewayEvent } from "aws-lambda";
import {
Context,
APIGatewayProxyResult,
APIGatewayProxyEventV2,
} from "aws-lambda";
import {
PMTiles,
ResolvedValueCache,
RangeResponse,
Source,
Compression,
} from "../../../js";
// @ts-ignore
import https from "https";
// @ts-ignore
import s3client from "/var/runtime/node_modules/aws-sdk/clients/s3.js";
@@ -21,6 +27,47 @@ const s3 = new s3client({
// TODO: figure out how much memory to allocate
const CACHE = new ResolvedValueCache();
export const pmtiles_path = (name: string, setting?: string): string => {
if (setting) {
return setting.replace("{name}", name);
}
return name + ".pmtiles";
};
const TILE =
/^\/(?<NAME>[0-9a-zA-Z\/!\-_\.\*\'\(\)]+)\/(?<Z>\d+)\/(?<X>\d+)\/(?<Y>\d+).(?<EXT>[a-z]+)$/;
export const tile_path = (
path: string,
setting?: string
): {
ok: boolean;
name: string;
tile: [number, number, number];
ext: string;
} => {
let pattern = TILE;
if (setting) {
// escape regex
setting = setting.replace(/[.*+?^$()|[\]\\]/g, "\\$&");
setting = setting.replace("{name}", "(?<NAME>[0-9a-zA-Z/!-_.*'()]+)");
setting = setting.replace("{z}", "(?<Z>\\d+)");
setting = setting.replace("{x}", "(?<X>\\d+)");
setting = setting.replace("{y}", "(?<Y>\\d+)");
setting = setting.replace("{ext}", "(?<EXT>[a-z]+)");
pattern = new RegExp(setting);
}
let match = path.match(pattern);
if (match) {
const g = match.groups!;
return { ok: true, name: g.NAME, tile: [+g.Z, +g.X, +g.Y], ext: g.EXT };
}
return { ok: false, name: "", tile: [0, 0, 0], ext: "" };
};
class S3Source implements Source {
archive_name: string;
@@ -36,7 +83,7 @@ class S3Source implements Source {
const resp = await s3
.getObject({
Bucket: process.env.BUCKET!,
Key: this.archive_name + ".pmtiles",
Key: pmtiles_path(this.archive_name, process.env.PMTILES_PATH),
Range: "bytes=" + offset + "-" + (offset + length - 1),
})
.promise();
@@ -45,37 +92,86 @@ class S3Source implements Source {
}
}
interface Headers {
[key: string]: string;
}
const apiResp = (
statusCode: number,
body: string,
isBase64Encoded = false,
headers: Headers = {}
): APIGatewayProxyResult => {
return {
statusCode: statusCode,
body: body,
headers: headers,
isBase64Encoded: isBase64Encoded,
};
};
// Assumes event is a API Gateway V2 or Lambda Function URL formatted dict
// and returns API Gateway V2 / Lambda Function dict responses
// Does not work with CloudFront events/Lambda@Edge; see README
export const handler = async (
event: APIGatewayEvent,
event: APIGatewayProxyEventV2,
context: Context
): Promise<APIGatewayProxyResult> => {
var path;
var is_api_gateway;
if (event.pathParameters) {
is_api_gateway = true;
if (event.pathParameters.proxy) {
path = "/" + event.pathParameters.proxy;
} else {
return apiResp(500, "Proxy integration missing tile_path parameter");
}
} else {
path = event.rawPath;
}
if (!path) {
return apiResp(500, "Invalid event configuration");
}
const { ok, name, tile } = tile_path(path, process.env.TILE_PATH);
if (!ok) {
return apiResp(400, "Invalid tile URL");
}
var headers: Headers = {};
if (process.env.CORS) {
headers["Access-Control-Allow-Origin"] = process.env.CORS;
}
// TODO: extension enforcement and MIME types
const source = new S3Source("stamen_toner_z3");
const p = new PMTiles(source, CACHE);
try {
const source = new S3Source("stamen_toner_z3");
const p = new PMTiles(source, CACHE);
const header = await p.getHeader();
// TODO optimize by checking min/max zoom, return 404
headers["Content-Type"] = "application/vnd.vector-tile";
const tile = await p.getZxy(0, 0, 0);
if (tile) {
return {
statusCode: 200,
body: Buffer.from(tile.data).toString("base64"),
};
// return suncompressed response
// TODO: may need to special case API gateway to return compressed response with gzip content-encoding header
return apiResp(
200,
Buffer.from(tile.data).toString("base64"),
true,
headers
);
} else {
return {
statusCode: 204,
body: "",
};
return apiResp(204, "", false, headers);
}
} catch (e) {
if ((e as Error).name === "AccessDenied") {
return {
statusCode: 403,
body: "Bucket access failed: Unauthorized",
};
return apiResp(403, "Bucket access unauthorized");
}
throw e;
}
return {
statusCode: 404,
body: "Invalid URL",
};
return apiResp(404, "Invalid URL");
};