Search code examples
amazon-web-servicesaws-lambdaaws-lambda-edge

Validation error when I replace body in Lambda@Edge origin request function


I have Cloudfront in front of an s3 bucket that serves HLS videos. I'm trying to dynamically modify the manifest files to add an auth token to the segments inside of them.

What I would really like to do is modify the body I send back to the client in a viewer response function, but since that isn't possible, I'm attempting to use a origin request function to manually fetch the object from S3, modify it, and return a Cloudfront request with the new body. I get a 503 error of "The Lambda function result failed validation: The body is not a string, is not an object, or exceeds the maximum size"

My body is under 8kb (1MB is the limit in the docs). As far as I can tell the cloudfront request object I'm generating looks good and the base64 data decodes to what I want. I've also tried using text instead of base64. I have "include body" enabled in Cloudfront.

const fs = require('fs');
const querystring = require('querystring');
const AWS = require('aws-sdk');
const S3 = new AWS.S3();

exports.handler = async (event) => {
  const cfrequest = event.Records[0].cf.request;
  const queryString = querystring.parse(event.Records[0].cf.request.querystring);
  const jwtToken = queryString.token;

  if (cfrequest.uri.match(/\.m3u8?$/mi)) {

    const s3Response = await (new Promise((resolve, reject) => {
      S3.getObject({
        Bucket: 'bucket',
        Key: cfrequest.uri.substring(1)
      }, (err, data) => {
        if (err) {
          reject(err)
        } else {
          resolve(data);
        }
      });
    }));

    const manifestFile = s3Response.Body.toString('utf8');
    const newManifest = manifestFile.replace(/^((\S+)\.(m3u8|ts|vtt))$/gmi, (_, url) => `${url}?token=${jwtToken}`);
    const base64NewManifest = Buffer.from(newManifest, 'utf8').toString('base64');

    const tokenizedCfRequest = {
     ...cfrequest,
      body: {
        action: 'replace',
        data: base64NewManifest,
        encoding: 'base64'
      }
    };

    return tokenizedCfRequest;
  }

  return cfrequest;
}

Solution

  • If you want to generate your own response you need to use a viewer request or origin request event and return a response like this:

    exports.handler = async (event) => {
      const cfRequest = event.Records[0].cf.request;
      const queryString = querystring.parse(event.Records[0].cf.request.querystring);
      const jwtToken = queryString.token;
    
      if (cfrequest.uri.match(/\.m3u8?$/mi)) {
    
        // ... your code here ...
    
        const response = {
          status: 200, // only mandatory field
          body: base64NewManifest,
          bodyEncoding: 'base64',
        };
        return response;
      }
    
      // Return original request if no uri match
      return cfRequest;
    }
    

    See also Generating HTTP Responses in Request Triggers.