AwsHttps

HTTPS client with AWS Signature v4.

Overview

The AwsHttps class is an HTTPS (notice, not HTTP) client purpose made for use in and with AWS environments.

  • Simple Promise or async syntax
  • Optionally authenticates to AWS via AWS Signature v4 using aws4
  • Familiar options.
  • Helper to build request options from URL object
  • Light-weight
  • Easily extended for unit testing

AwsHttps is dependent on Logger and AWS4 for signing.

Install

npm install @sailplane/aws-https @sailplane/logger

Examples

Simple example to GET from URL:

const url = new URL('https://www.onica.com/ping.json');
const http = new AwsHttps();

// Build request options from a method and URL
const options = http.buildOptions('GET' url);

// Make request and parse JSON response.
const ping = await http.request(options);

Example hitting API with the container’s AWS credentials:

const awsHttp = new AwsHttps();
const options: AwsHttpsOptions = {
    // Same options as https://nodejs.org/api/http.html#http_http_request_options_callback
    method: 'GET',
    hostname: apiEndpoint,
    path: '/cloud-help',
    headers: {
        'accept': 'application/json; charset=utf-8',
        'content-type': 'application/json; charset=utf-8'
    },
    timeout: 10000,

    // Additional option for POST, PUT, or PATCH:
    body: JSON.stringify({ website: "https://www.onica.com" }),

    // Additional option to apply AWS Signature v4
    awsSign: true
};

try {
    const responseObj = await awsHttp.request(options);
    process(responseObj);
}
catch (err) {
    // HTTP status response is in statusCode field
    if (err.statusCode === 404) {
        process(undefined);
    }
    else {
        throw err;
    }
}

Example hitting API with the custom AWS credentials:

// Call my helper function to get credentials with AWS.STS
const roleCredentials = await this.getAssumeRoleCredentials();

const awsCredentials = {
    accessKey: roleCredentials.AccessKeyId,
    secretKey: roleCredentials.SecretAccessKey,
    sessionToken: roleCredentials.SessionToken,
};
const http = new AwsHttps(false, awsCredentials);

// Build request options from a method and URL
const url = new URL('https://www.onica.com/ping.json');
const options = http.buildOptions('GET' url);

// Make request and parse JSON response.
const ping = await http.request(options);

The ElasticsearchClient package is a simple example using AwsHttps.

Unit testing your services

  • Have your service receive AwsHttps in the constructor. Consider using Injector.
  • In your service unit tests, create a new class that extends AwsHttps and returns your canned response.
  • Pass your fake AwsHttps class into the constructor of your service under test.
export class AwsHttpsFake extends AwsHttps {
    constructor() {
        super();
    }

    async request(options: AwsHttpsOptions): Promise<any | null> {
        // Check for expected options. Example:
        expect(options.path).toEqual('/expected-path');

        // Return canned response
        return Promise.resolve({ success: true });
    }
}

Type Declarations

/// <reference types="node" />
import { Credentials, CredentialsOptions } from "aws-sdk/lib/credentials";
import * as https from "https";
import { URL } from "url";
/**
 * Same options as https://nodejs.org/api/http.html#http_http_request_options_callback
 * with the addition of optional body to send with POST, PUT, or PATCH
 * and option to AWS Sig4 sign the request.
 */
export declare type AwsHttpsOptions = https.RequestOptions & {
    /** Body content of HTTP POST, PUT or PATCH */
    body?: string;
    /** If true, apply AWS Signature v4 to the request */
    awsSign?: boolean;
};
/**
 * Light-weight utility for making HTTPS requests in AWS environments.
 */
export declare class AwsHttps {
    private readonly verbose?;
    /** Resolves when credentials are available - shared by all instances */
    private static credentialsInitializedPromise;
    /** Credentials to use in this instance */
    private awsCredentials?;
    /**
     * Constructor.
     * @param verbose true to log everything, false for silence,
     *                undefined (default) for normal logging.
     * @param credentials
     *      If not defined, credentials will be obtained by default SDK behavior for the runtime environment.
     *                      This happens once and then is cached; good for Lambdas.
     *      If `true`, clear cached to obtain fresh credentials from SDK.
     *                 Good for longer running containers that rotate credentials.
     *      If an object with accessKeyId, secretAccessKey, and sessionToken,
     *                 use these credentials for this instance.
     */
    constructor(verbose?: boolean | undefined, credentials?: boolean | Credentials | CredentialsOptions);
    /**
     * Perform an HTTPS request and return the JSON body of the result.
     *
     * @params options https request options, with optional body and awsSign
     * @returns parsed JSON content, or null if none.
     * @throws {Error{message,status,statusCode}} error if HTTP result is not 2xx or unable
     *              to parse response. Compatible with http-errors package.
     */
    request(options: AwsHttpsOptions): Promise<any | null>;
    /**
     * Helper to build a starter AwsHttpsOptions object from a URL.
     *
     * @param method an HTTP method/verb
     * @param url the URL to request from
     * @param connectTimeout (default 5000) milliseconds to wait for connection to establish
     * @returns an AwsHttpsOptions object, which may be further modified before use.
     */
    buildOptions(method: 'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'POST' | 'PUT' | 'PATCH', url: URL, connectTimeout?: number): AwsHttpsOptions;
    /**
     * Helper for signing AWS requests
     * @param request to make
     * @return signed version of the request.
     */
    private awsSign;
}