From 4eca6fc30a3ceadd192ade2c44a2c65a1602d57b Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 13 Sep 2024 14:01:15 -0400 Subject: [PATCH] docs: router --- platform/src/components/aws/router.ts | 596 +++++++++++++------------- 1 file changed, 296 insertions(+), 300 deletions(-) diff --git a/platform/src/components/aws/router.ts b/platform/src/components/aws/router.ts index ba163eb84..11c0074c3 100644 --- a/platform/src/components/aws/router.ts +++ b/platform/src/components/aws/router.ts @@ -8,6 +8,255 @@ import { hashStringToPrettyString, physicalName } from "../naming"; import { Bucket } from "./bucket"; import { OriginAccessControl } from "./providers/origin-access-control"; +export interface RouterUrlRouteArgs extends BaseRouteArgs { + /** + * The destination URL. + * + * @example + * + * ```js + * { + * routes: { + * "/api/*": { + * url: "https://example.com" + * } + * } + * } + * ``` + */ + url: Input; +} + +export interface RouterBucketRouteArgs extends BaseRouteArgs { + /** + * A bucket to route to. + * + * @example + * + * For example, let's say you have a bucket. + * + * ```ts title="sst.config.ts" {2} + * const myBucket = new sst.aws.Bucket("MyBucket", { + * access: "cloudfront" + * }); + * ``` + * + * :::note + * The `access` props lets CloudFront access this bucket. + * ::: + * + * You can set this directly as the destination for the route. + * + * ```js + * { + * routes: { + * "/files/*": { + * bucket: myBucket + * } + * } + * } + * ``` + * + * Or if you have an existing bucket, you can pass in its regional domain. + * + * ```js + * { + * routes: { + * "/files/*": { + * bucket: "my-bucket.s3.us-east-1.amazonaws.com" + * } + * } + * } + * ``` + */ + bucket?: Input; +} + +interface BaseRouteArgs { + /** + * Rewrite the request path. + * + * @example + * + * By default, if the route path is `/files/*` and a request comes in for `/files/logo.png`, + * the request path the destination sees is `/files/logo.png`. In the case of a bucket route, + * the file `logo.png` is served from the `files` directory in the bucket. + * + * If you want to serve the file from the root of the bucket, you can rewrite + * the request path to `/logo.png`. + * + * ```js + * { + * routes: { + * "/files/*": { + * bucket: myBucket, + * rewrite: { + * regex: "^/files/(.*)$", + * to: "/$1" + * } + * } + * } + * } + * ``` + */ + rewrite?: Input<{ + /** + * The regex to match the request path. + */ + regex: Input; + /** + * The replacement for the matched path. + */ + to: Input; + }>; + /** + * Configure CloudFront Functions to customize the behavior of HTTP requests and responses at the edge. + */ + edge?: { + /** + * Configure the viewer request function. + * + * The viewer request function can be used to modify incoming requests before they + * reach your origin server. For example, you can redirect users, rewrite URLs, + * or add headers. + */ + viewerRequest?: Input<{ + /** + * The code to inject into the viewer request function. + * + * By default, a viewer request function is created to add the `x-forwarded-host` + * header. The given code will be injected at the end of this function. + * + * ```js + * function handler(event) { + * // Default behavior code + * + * // User injected code + * + * return event.request; + * } + * ``` + * + * @example + * To add a custom header to all requests. + * + * ```js + * { + * server: { + * edge: { + * viewerRequest: { + * injection: `event.request.headers["x-foo"] = "bar";` + * } + * } + * } + * } + * ``` + */ + injection: Input; + /** + * The KV stores to associate with the viewer request function. + * + * Takes a list of CloudFront KeyValueStore ARNs. + * + * @example + * ```js + * { + * routes: { + * "/api/*": { + * edge: { + * viewerRequest: { + * kvStores: ["arn:aws:cloudfront::123456789012:key-value-store/my-store"] + * } + * } + * } + * } + * } + * ``` + */ + kvStores?: Input[]>; + }>; + /** + * Configure the viewer response function. + * + * The viewer response function can be used to modify outgoing responses before + * they are sent to the client. For example, you can add security headers or change + * the response status code. + * + * By default, no viewer response function is set. A new function will be created + * with the provided code. + * + * @example + * Add a custom header to all responses + * ```js + * { + * routes: { + * "/api/*": { + * edge: { + * viewerResponse: { + * injection: `event.response.headers["x-foo"] = "bar";` + * } + * } + * } + * } + * } + * ``` + */ + viewerResponse?: Input<{ + /** + * The code to inject into the viewer response function. + * + * By default, no viewer response function is set. A new function will be created with + * the provided code. + * + * ```js + * function handler(event) { + * // User injected code + * + * return event.response; + * } + * ``` + * + * @example + * To add a custom header to all responses. + * + * ```js + * { + * server: { + * edge: { + * viewerResponse: { + * injection: `event.response.headers["x-foo"] = "bar";` + * } + * } + * } + * } + * ``` + */ + injection: Input; + /** + * The KV stores to associate with the viewer response function. + * + * Takes a list of CloudFront KeyValueStore ARNs. + * + * @example + * ```js + * { + * routes: { + * "/api/*": { + * edge: { + * viewerResponse: { + * kvStores: ["arn:aws:cloudfront::123456789012:key-value-store/my-store"] + * } + * } + * } + * } + * } + * ``` + */ + kvStores?: Input[]>; + }>; + }; +} + export interface RouterArgs { /** * Set a custom domain for your Router. @@ -161,264 +410,7 @@ export interface RouterArgs { * ``` */ routes: Input< - Record< - string, - Input< - | string - | { - /** - * The destination URL. - * - * @example - * - * ```js - * { - * routes: { - * "/api/*": { - * url: "https://example.com" - * } - * } - * } - * ``` - */ - url?: Input; - /** - * A bucket to route to. - * - * @example - * - * For example, let's say you have a bucket. - * - * ```ts title="sst.config.ts" {2} - * const myBucket = new sst.aws.Bucket("MyBucket", { - * access: "cloudfront" - * }); - * ``` - * - * :::note - * The `access` props lets CloudFront access this bucket. - * ::: - * - * You can set this directly as the destination for the route. - * - * ```js - * { - * routes: { - * "/files/*": { - * bucket: myBucket - * } - * } - * } - * ``` - * - * Or if you have an existing bucket, you can pass in its regional domain. - * - * ```js - * { - * routes: { - * "/files/*": { - * bucket: { - * regionalDomain: "my-bucket.s3.us-east-1.amazonaws.com" - * } - * } - * } - * } - * ``` - */ - bucket?: Input< - | Bucket - | { - /** - * The regional domain of the bucket. - */ - regionalDomain: Input; - } - >; - /** - * Rewrite the request path. - * - * @example - * - * By default, if the route path is `/files/*` and a request comes in for `/files/logo.png`, - * the request path the destination sees is `/files/logo.png`. In the case of a bucket route, - * the file `logo.png` is served from the `files` directory in the bucket. - * - * If you want to serve the file from the root of the bucket, you can rewrite - * the request path to `/logo.png`. - * - * ```js - * { - * routes: { - * "/files/*": { - * bucket: myBucket, - * rewrite: { - * regex: "^/files/(.*)$", - * to: "/$1" - * } - * } - * } - * } - * ``` - */ - rewrite?: Input<{ - /** - * The regex to match the request path. - */ - regex: Input; - /** - * The replacement for the matched path. - */ - to: Input; - }>; - /** - * Configure CloudFront Functions to customize the behavior of HTTP requests and responses at the edge. - */ - edge?: { - /** - * Configure the viewer request function. - * - * The viewer request function can be used to modify incoming requests before they - * reach your origin server. For example, you can redirect users, rewrite URLs, - * or add headers. - */ - viewerRequest?: Input<{ - /** - * The code to inject into the viewer request function. - * - * By default, a viewer request function is created to add the `x-forwarded-host` - * header. The given code will be injected at the end of this function. - * - * ```js - * function handler(event) { - * // Default behavior code - * - * // User injected code - * - * return event.request; - * } - * ``` - * - * @example - * To add a custom header to all requests. - * - * ```js - * { - * server: { - * edge: { - * viewerRequest: { - * injection: `event.request.headers["x-foo"] = "bar";` - * } - * } - * } - * } - * ``` - */ - injection: Input; - /** - * The KV stores to associate with the viewer request function. - * - * Takes a list of CloudFront KeyValueStore ARNs. - * - * @example - * ```js - * { - * routes: { - * "/api/*": { - * edge: { - * viewerRequest: { - * kvStores: ["arn:aws:cloudfront::123456789012:key-value-store/my-store"] - * } - * } - * } - * } - * } - * ``` - */ - kvStores?: Input[]>; - }>; - /** - * Configure the viewer response function. - * - * The viewer response function can be used to modify outgoing responses before - * they are sent to the client. For example, you can add security headers or change - * the response status code. - * - * By default, no viewer response function is set. A new function will be created - * with the provided code. - * - * @example - * Add a custom header to all responses - * ```js - * { - * routes: { - * "/api/*": { - * edge: { - * viewerResponse: { - * injection: `event.response.headers["x-foo"] = "bar";` - * } - * } - * } - * } - * } - * ``` - */ - viewerResponse?: Input<{ - /** - * The code to inject into the viewer response function. - * - * By default, no viewer response function is set. A new function will be created with - * the provided code. - * - * ```js - * function handler(event) { - * // User injected code - * - * return event.response; - * } - * ``` - * - * @example - * To add a custom header to all responses. - * - * ```js - * { - * server: { - * edge: { - * viewerResponse: { - * injection: `event.response.headers["x-foo"] = "bar";` - * } - * } - * } - * } - * ``` - */ - injection: Input; - /** - * The KV stores to associate with the viewer response function. - * - * Takes a list of CloudFront KeyValueStore ARNs. - * - * @example - * ```js - * { - * routes: { - * "/api/*": { - * edge: { - * viewerResponse: { - * kvStores: ["arn:aws:cloudfront::123456789012:key-value-store/my-store"] - * } - * } - * } - * } - * } - * ``` - */ - kvStores?: Input[]>; - }>; - }; - } - > - > + Record> >; /** * Configure how the CloudFront cache invalidations are handled. @@ -524,7 +516,6 @@ export interface RouterArgs { */ export class Router extends Component implements Link.Linkable { private cdn: Cdn; - private cachePolicy: cloudfront.CachePolicy; constructor( name: string, @@ -542,7 +533,6 @@ export class Router extends Component implements Link.Linkable { const cachePolicy = createCachePolicy(); const cdn = createCdn(); - this.cachePolicy = cachePolicy; this.cdn = cdn; this.registerOutputs({ @@ -559,7 +549,11 @@ export class Router extends Component implements Link.Linkable { `In "${name}" Router, the route path "${path}" must start with a "/"`, ); - if (typeof route !== "string" && route.url && route.bucket) + if ( + typeof route !== "string" && + "url" in route && + "bucket" in route + ) throw new Error( `In "${name}" Router, the route path "${path}" cannot have both a url and a bucket`, ); @@ -593,15 +587,15 @@ export class Router extends Component implements Link.Linkable { path: string, config: | { - injection: string; - kvStores?: string[]; - } + injection: string; + kvStores?: string[]; + } | undefined, rewrite: | { - regex: string; - to: string; - } + regex: string; + to: string; + } | undefined, injectHostHeader: boolean, ) { @@ -612,16 +606,18 @@ export class Router extends Component implements Link.Linkable { keyValueStoreAssociations: config?.kvStores ?? [], code: ` function handler(event) { - ${injectHostHeader - ? `event.request.headers["x-forwarded-host"] = event.request.headers.host;` - : "" - } - ${rewrite - ? ` + ${ + injectHostHeader + ? `event.request.headers["x-forwarded-host"] = event.request.headers.host;` + : "" + } + ${ + rewrite + ? ` const re = new RegExp("${rewrite.regex}"); event.request.uri = event.request.uri.replace(re, "${rewrite.to}");` - : "" - } + : "" + } ${config?.injection ?? ""} return event.request; }`, @@ -734,7 +730,7 @@ function handler(event) { return output(routes).apply((routes) => { const origins = Object.entries(routes).map(([path, route]) => { - if (route.url) { + if ("url" in route) { return { originId: path, domainName: new URL(route.url).host, @@ -747,7 +743,7 @@ function handler(event) { domainName: route.bucket instanceof Bucket ? route.bucket.nodes.bucket.bucketRegionalDomainName - : route.bucket!.regionalDomain, + : route.bucket!, originPath: "", originAccessControlId: createOriginAccessControl().id, }; @@ -798,35 +794,35 @@ function handler(event) { ...(path === "/*" ? {} : { pathPattern: path }), targetOriginId: path, functionAssociations: [ - ...(route.url || route.edge?.viewerRequest || route.rewrite + ...("url" in route || route.edge?.viewerRequest || route.rewrite ? [ - { - eventType: "viewer-request", - functionArn: - route.edge?.viewerRequest || route.rewrite - ? createCfRequestFunction( - path, - route.edge?.viewerRequest, - route.rewrite, - route.url !== undefined, - ).arn - : createCfRequestDefaultFunction().arn, - }, - ] + { + eventType: "viewer-request", + functionArn: + route.edge?.viewerRequest || route.rewrite + ? createCfRequestFunction( + path, + route.edge?.viewerRequest, + route.rewrite, + "url" in route, + ).arn + : createCfRequestDefaultFunction().arn, + }, + ] : []), ...(route.edge?.viewerResponse ? [ - { - eventType: "viewer-response", - functionArn: createCfResponseFunction( - path, - route.edge.viewerResponse, - ).arn, - }, - ] + { + eventType: "viewer-response", + functionArn: createCfResponseFunction( + path, + route.edge.viewerResponse, + ).arn, + }, + ] : []), ], - ...(route.url ? urlDefaultConfig : bucketDefaultConfig), + ...("url" in route ? urlDefaultConfig : bucketDefaultConfig), })); if (!routes["/*"]) {