serveSpaFrom method

Future<CloudFrontOriginResponse> serveSpaFrom({
  1. required CloudFrontOriginRequestEvent event,
  2. String defaultFileName = 'index.html',
})

Implementation

Future<CloudFrontOriginResponse> serveSpaFrom({
  required CloudFrontOriginRequestEvent event,
  String defaultFileName = 'index.html',
}) async {
  final bool canServe = event.hasHeader('Host') &&
      event.hasEnv('BASE_DOMAIN') &&
      event.hasEnv('BUCKET_NAME') &&
      event.hasEnv('BUCKET_REGION');
  if (!canServe) {
    throw StateError("Cannot serve static content from this event. It's "
        "missing one or more required aspects: Host header, BASE_DOMAIN, "
        "BUCKET_NAME, or BUCKET_REGION.");
  }

  final request = event.records.first.cf.request;
  final String hostName = event.getHeader('Host')!;
  final String baseName = event.getEnv('BASE_DOMAIN')!;
  final String bucketName = event.getEnv('BUCKET_NAME')!;
  final String bucketRegion = event.getEnv('BUCKET_REGION')!;
  final int subSize = (hostName.length - baseName.length);
  final String subName = subSize > 0 ? hostName.substring(0, subSize) : 'www';
  final String safeUri = request.uri.startsWith('/')
      ? request.uri.replaceFirst(r'/', '')
      : request.uri;
  final String reqExt = path.extension(request.uri);
  final String reqFileName = reqExt.isEmpty ? defaultFileName : '';
  final String bucketPath = path.join(subName, safeUri, reqFileName);
  final String spaBucketPath = path.join(subName, defaultFileName);

  late final int status;
  late final Encoding enc;
  late final GetObjectOutput s3Resp;

  final S3 s3 = S3(
    region: bucketRegion,
    client: httpClient,
    credentialsProvider: _credentialsProvider,
  );
  try {
    s3Resp = await s3.getObject(
      bucket: bucketName,
      key: bucketPath,
    );
    enc = Encoding.getByName(s3Resp.contentEncoding) ?? utf8;
    status = 200;
  } on NoSuchBucket {
    status = 500;
    throw StateError("The bucket doesn't exist or is not accessible.");
  } on NoSuchKey {
    if (bucketPath != spaBucketPath) {
      return serveSpaFrom(event: event.redirectTo(defaultFileName));
    }
    status = 404;
    throw StateError("The item doesn't exist or is not accessible.");
  }

  return CloudFrontOriginResponse(
    status: status,
    statusDescription: getHttpReason(status),
    body: s3Resp.body != null && s3Resp.body!.isNotEmpty
        ? enc.decode(s3Resp.body!)
        : null,
    bodyEncoding: CloudFrontBodyEncoding.text,
    headers: CloudFrontHeaders(
      headers: Map<String, List<Map<String, String>>>.fromEntries(
        [
          _headerFrom(
            name: 'Content-Disposition',
            value: s3Resp.contentDisposition,
          ),
          _headerFrom(
            name: 'Cache-Control',
            value: s3Resp.cacheControl,
          ),
          _headerFrom(
            name: 'Content-Encoding',
            value: s3Resp.contentEncoding,
          ),
          _headerFrom(
            name: 'Content-Language',
            value: s3Resp.contentLanguage,
          ),
          _headerFrom(
            name: 'Content-Length',
            value: s3Resp.contentLength?.toString(),
          ),
          _headerFrom(
            name: 'Content-Type',
            value: s3Resp.contentType,
          )
        ].whereType<MapEntry<String, List<Map<String, String>>>>().toList(),
      ),
    ),
  );
}