3

Originally I had an Angular 8+ application with an upload form and some code that generated AWS presigned urls which was used to upload the file chosen in the upload form to an AWS S3 buckets. The code looked something like this:

const AWS = require('aws-sdk');

import { Component, OnInit, } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';

import { environment } from '../../../environments/environment';

@Component({
  selector: 'app-upload-page',
  templateUrl: './upload-page.component.html',
  styleUrls: ['./upload-page.component.scss']
})

export class UploadComponent implements OnInit {
  private S3: any;
  private UPLOAD_FOLDER = 'test/';
  public file: File;

  constructor(private http: HttpClient) {}

  ngOnInit(): void {
    AWS.config.update({
      accessKeyId: environment.amazon.s3.secretAccessId,
      secretAccessKey: environment.amazon.s3.secretAccessKey,
      region: environment.amazon.s3.region,
      signatureVersion: environment.amazon.s3.signatureVersion
    });

    this.S3 = new AWS.S3();
  }

  // Triggered when clicking the upload button after attaching a file to <input type="file"/>
  public async onUpload(): Promise<void> {
    try {
      const params = this.buildUploadParams(environment.amazon.s3.bucketname, this.UPLOAD_FOLDER + this.file.name, this.file.type, 120);
      const preSignedGetUrl = this.S3.getSignedUrl('putObject', params);
      await this.upload(preSignedGetUrl, this.file); // this.file is the body
      this.file = null;
    } catch (error) {
      console.error(error);
    }
  }

  // Triggered when selecting a file
  public onChange($event: any): void {
    const files = $event.srcElement.files;
    this.file = files[0];
  }

  // Triggers the presigned url and attaches this.file to body
  private upload(url: string, body: any): Promise<any> {
    return new Promise((resolve, reject) => {
      this.http.put(url, body).toPromise()
        .then(res => resolve(res), (error: any) => reject(error));
    });
  }

  // Builds params required to generate presigned urls
  private buildUploadParams(bucket: string, key: string, contentType: string, expires: number): any {
    return {
      Bucket: bucket,
      Key: key,
      ContentType: contentType,
      ACL: 'bucket-owner-full-control',
      Expires: expires
    };
  }
}

This code works fine, but I want to remove responsibility of generating presigned URLs from the client (Angular 8+) to AWS Lambda functions in a Node.js application.

So I add a new environment. A Node.js instance which I deploy through Netlify so I can easily access the AWS Lambda functions through URLs, e.g. https://my-url.netlify.com/.netlify/functions/upload?file=' + file.

This is how my AWS Lambda function looks like in the functions/upload.js file in the Node.js instance:

// Below is the content of the upload.js file
const AWS = require('aws-sdk');
const request = require('request');

// AWS credentials
const S3_ACCESS_KEY_ID = 'MY_ACCESS_KEY_ID';
const S3_SECRET_ACCESS_KEY = 'MY_SECRET_ACCESS_KEY';
const BUCKET_NAME = 'MY_BUCKET_NAME';
const UPLOAD_FOLDER = 'test/';

AWS.config.update({
  accessKeyId: S3_ACCESS_KEY_ID,
  secretAccessKey: S3_ACCESS_ACCESS_KEY,
  region: 'eu-west-1',
  signatureVersion: 'v4'
});

const s3 = new AWS.S3();

function buildUploadParams(bucket, key, contentType, expires) {
  return {
    Bucket: bucket,
    Key: key,
    ContentType: contentType,
    ACL: 'bucket-owner-full-control',
    Expires: expires
  }
}

function upload(url, body) {
  return new Promise((resolve, reject) => {
    request({ method: 'PUT', url: url, body: body }, (error, response, body) => {
      if (error) {
        return reject(error);
      }

      if (response.statusCode !== 200) {
        return reject(body.error_description);
      }

      resolve();
    });
  });
}

function response (code, body) {
  return {
    'statusCode': code,
    'headers': { 'Access-Control-Allow-Origin': '*' },
    'body': body,
    'isBase64Encoded': false
  }
}

async function uploadFile(file) {
  const params = buildUploadParams(BUCKET_NAME, UPLOAD_FOLDER, file.type, 120);
  const preSignedGetUrl = s3.getSignedUrl('putObject', params);

  await upload(preSignedGetUrl, file);

  return 'Successfully uploaded file';
}

// AWS Lambda Function 
exports.handler = async (event, context, callback) => {
  // Should I pass file content with the query and get it like this: event.queryStringParameters['file']; 
  const file = event.queryStringParameters['file'];

  callback(null, response(200, uploadFile(file)));
};

Then I could make a HTTP POST request to the Netlify URL where I pass the file as a param from the Angular 8+ client in the Netlify URL, but you can not really pass the file object [File object] as an URL param, so how can I proceed?

Questions that might help me figure out how to transport this.file from Angular to Node.js AWS Lambda functions:

  1. Instead of adding the whole this.file object when using a Presigned URL, can I just use File contents? If yes, in what forms?

  2. What is your opinion about this approach in general?

  3. Are there alternative approaches (Requirement: I still need to use Angular, Node.js and Netlify)

robtot
  • 863
  • 1
  • 9
  • 31

0 Answers0