From 78833c91a8b9de9032a6168b97e551e5d0fff588 Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Thu, 16 Jul 2020 13:47:33 +0100 Subject: [PATCH 1/3] Fix proxy file Seems an update to the AWS SDK has meant that the `proxy-file.js` no longer worked properly due to `s3.makeRequest` changing their behaviour. Odd handling of `this` meant that requests for gifs were returning the error message `this.makeRequest is not a function`. In addition the proxy-file fallback was never supported by the local-server.js or regular server.js files so this functionality was broken on Docker builds --- Dockerfile | 1 + lambda-handler.js | 2 +- local-server.js | 54 +++++++++++++++------------- package-lock.json | 2 +- proxy-file.js | 57 +++++++++++++++-------------- server.js | 91 ++++++++++++++++++++++++++++++----------------- 6 files changed, 122 insertions(+), 85 deletions(-) diff --git a/Dockerfile b/Dockerfile index b37ef24..681182d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,7 @@ FROM lambci/lambda:nodejs10.x COPY node_modules/ /var/task/node_modules COPY server.js /var/task/ COPY index.js /var/task +COPY proxy-file.js /var/task ARG AWS_REGION ARG AWS_S3_BUCKET diff --git a/lambda-handler.js b/lambda-handler.js index dddf44b..210520b 100644 --- a/lambda-handler.js +++ b/lambda-handler.js @@ -17,7 +17,7 @@ exports.handler = function(event, context, callback) { ) { if (err) { if (err.message === 'fallback-to-original') { - return proxyFile(region, bucket, key, callback); + return proxyFile({ region: region }, bucket, key, callback); } else if ( err.code === 'AccessDenied' ) { // An AccessDenied error means the file is either protected, or doesn't exist. // We don't get a NotFound error because Tachyon makes unauthenticated calls diff --git a/local-server.js b/local-server.js index ecacb2d..58b8efc 100644 --- a/local-server.js +++ b/local-server.js @@ -1,46 +1,52 @@ -var http = require("http"), - url = require("url"), - path = require("path"), - fs = require("fs"), - tachyon= require( './index' ), +var http = require("http"), + url = require("url"), + fs = require("fs"), + tachyon = require( './index' ), args = process.argv.slice(2), - port = Number( args[0] ) ? args[0] : 8080, - debug = args.indexOf( '--debug' ) > -1 + port = Number( args[0] ) ? args[0] : 8080, + debug = args.indexOf( '--debug' ) > -1; http.createServer( function( request, response ) { - var params = url.parse( request.url, true ) + var params = url.parse( request.url, true ); if ( debug ) { - console.log( Date(), request.url ) + console.log( Date(), request.url ); } try { - var imageData = fs.readFileSync( decodeURI( params.pathname.substr(1) ) ) + var imageData = fs.readFileSync( decodeURI( params.pathname.substr(1) ) ); } catch ( err ) { - response.writeHead( err.statusCode ? err.statusCode : 500 ) - response.write( err.message ) - return response.end() + response.writeHead( err.statusCode ? err.statusCode : 500 ); + response.write( err.message ); + return response.end(); } - params.query.key = decodeURI( params.pathname.substr(1) ) + params.query.key = decodeURI( params.pathname.substr(1) ); return tachyon.resizeBuffer( imageData, params.query, function( err, data, info ) { - if ( err ) { + if ( err.message === 'fallback-to-original' ) { + response.writeHead( 200, { + 'Content-Type': 'image/' + path.extname( params.query.key ).replace( '.', '' ), + 'Content-Length': Buffer.byteLength( imageData ), + } ); + response.write( imageData ); + return response.end(); + } if ( debug ) { - console.error( Date(), err ) + console.error( Date(), err ); } - response.writeHead( err.statusCode ? err.statusCode : 500 ) - response.write( err.message ) - return response.end() + response.writeHead( err.statusCode ? err.statusCode : 500 ); + response.write( err.message ); + return response.end(); } response.writeHead( 200, { 'Content-Type': 'image/' + info.format, - 'Content-Length': info.size - }) - response.write( data ) - return response.end() + 'Content-Length': info.size, + } ); + response.write( data ); + return response.end(); } ); -}).listen( parseInt( port, 10 ) ) +} ).listen( parseInt( port, 10 ) ); console.log( "Server running at\n => http://localhost:" + port + "/\nCTRL + C to shutdown" ) diff --git a/package-lock.json b/package-lock.json index 40112e9..aa37646 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "node-tachyon", - "version": "2.1.10", + "version": "2.2.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/proxy-file.js b/proxy-file.js index af49933..0ed7588 100644 --- a/proxy-file.js +++ b/proxy-file.js @@ -1,30 +1,35 @@ -var AWS = require('aws-sdk'); - -var authenticatedRequest = !!process.env.S3_AUTHENTICATED_REQUEST - -function sendOriginal(region, bucket, key, callback) { - var s3 = new AWS.S3(Object.assign({ region: region })); - var s3Request = authenticatedRequest ? s3.makeRequest : s3.makeUnauthenticatedRequest - return s3Request( - 'getObject', - { Bucket: bucket, Key: key }, - function(err, data) { - if (err) { - return callback(err); - } - - var resp = { - statusCode: 200, - headers: { - 'Content-Type': data.ContentType, - }, - body: Buffer.from(data.Body).toString('base64'), - isBase64Encoded: true, - }; - - callback(null, resp); +const AWS = require( 'aws-sdk' ); + +const authenticatedRequest = !!process.env.S3_AUTHENTICATED_REQUEST ? process.env.S3_AUTHENTICATED_REQUEST.toLowerCase() === 'true' : false; + +function sendOriginal( config, bucket, key, callback ) { + const s3 = new AWS.S3(config); + + let request; + if ( authenticatedRequest ) { + request = s3.makeRequest( 'getObject', { Bucket: bucket, Key: key } ); + } else { + request = s3.makeUnauthenticatedRequest( 'getObject', { Bucket: bucket, Key: key } ); + } + + request.send( function( err, data ) { + if (err) { + return callback(err); } - ); + + var resp = { + statusCode: 200, + headers: { + 'Content-Type': data.ContentType, + }, + body: Buffer.from(data.Body).toString('base64'), + isBase64Encoded: true, + }; + + callback(null, resp); + } ); + + return request; } module.exports = sendOriginal; diff --git a/server.js b/server.js index 52dd97b..8d8a99c 100644 --- a/server.js +++ b/server.js @@ -1,66 +1,91 @@ -var http = require("http"), - url = require("url"), - path = require("path"), - fs = require("fs"), - os = require("os"), - tachyon= require( './index' ), +var http = require("http"), + url = require("url"), + fs = require("fs"), + os = require("os"), + path = require( 'path' ), + tachyon = require( './index' ), + proxyFile = require( './proxy-file' ), args = process.argv.slice(2), - port = Number( args[0] ) ? args[0] : 8080, - debug = args.indexOf( '--debug' ) > -1 + port = Number( args[0] ) ? args[0] : 8080, + debug = args.indexOf( '--debug' ) > -1; -var config = {} +var config = {}; if ( process.env.AWS_REGION && process.env.AWS_S3_BUCKET ) { config = { region: process.env.AWS_REGION, bucket: process.env.AWS_S3_BUCKET, endpoint: process.env.AWS_S3_ENDPOINT, - } + }; } else if ( fs.existsSync( 'config.json' ) ) { - config = JSON.parse( fs.readFileSync( 'config.json' ) ) + config = JSON.parse( fs.readFileSync( 'config.json' ) ); } http.createServer( function( request, response ) { - var params = url.parse( request.url, true ) + var params = url.parse( request.url, true ); if ( debug ) { - console.log( Date(), request.url ) + console.log( Date(), request.url ); } // healthcheck file if ( params.pathname === '/healthcheck.php' ) { - response.writeHead( 200 ) - response.write( 'All good.' ) - return response.end() + response.writeHead( 200 ); + response.write( 'All good.' ); + return response.end(); } // robots.txt if ( params.pathname === '/robots.txt' ) { response.writeHead( 200, { - 'Content-Type': 'text/plain' + 'Content-Type': 'text/plain', } ); - response.write( 'User-agent: *' + os.EOL + 'Allow: /' ) - return response.end() + response.write( 'User-agent: *' + os.EOL + 'Allow: /' ); + return response.end(); + } + + const key = decodeURIComponent( params.pathname.substr(1) ).replace( '/uploads/tachyon/', '/uploads/' ); + const args = params.query || {}; + if ( typeof args.webp === 'undefined' ) { + args.webp = !!( request.headers && request.headers['accept'] && request.headers['accept'].match( 'image/webp' ) ); } - return tachyon.s3( config, decodeURIComponent( params.pathname.substr(1) ), params.query, function( err, data, info ) { + return tachyon.s3( config, key, args, function( err, data, info ) { if ( err ) { - if ( debug ) { - console.error( Date(), err ) + function callback( error, rsp ) { + if ( error ) { + if ( debug ) { + console.error( Date(), error ); + } + response.writeHead( error.statusCode ? error.statusCode : 500, { + 'Cache-Control': 'no-cache', + } ); + response.write( error.message ); + return response.end(); + } + response.writeHead( rsp.statusCode, Object.assign( { + 'Content-Type': 'image/' + path.extname( key ).replace( '.', '' ), + 'Cache-Control': 'public, max-age=31557600', + } ) ); + response.write( Buffer.from( rsp.body, 'base64' ) ); + return response.end(); } - response.writeHead( err.statusCode ? err.statusCode : 500, { - 'Cache-Control': 'no-cache' - } ) - response.write( err.message ) - return response.end() + if ( err.message === 'fallback-to-original' ) { + const s3config = { region: config.region }; + if ( config.endpoint ) { + s3config.endpoint = config.endpoint; + } + return proxyFile( s3config, config.bucket, key, callback ); + } + return callback( err ); } response.writeHead( 200, { 'Content-Type': 'image/' + info.format, 'Content-Length': info.size, - 'Cache-Control': 'public, max-age=31557600' - }) - response.write( data ) - return response.end() + 'Cache-Control': 'public, max-age=31557600', + } ); + response.write( data ); + return response.end(); } ); -}).listen( parseInt( port, 10 ) ) +} ).listen( parseInt( port, 10 ) ); -console.log( "Server running at\n => http://localhost:" + port + "/\nCTRL + C to shutdown" ) +console.log( "Server running at\n => http://localhost:" + port + "/\nCTRL + C to shutdown" ); From 40ea7817828982f4db7b29409097c07e389147db Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Thu, 16 Jul 2020 13:52:14 +0100 Subject: [PATCH 2/3] Update version and description in package.json --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ca3be6a..856cdf8 100755 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "node-tachyon", - "version": "2.2.1", + "version": "2.3.2", "repository": { "type": "git", "url": "https://github.com/humanmade/node-tachyon.git" }, - "description": "human made tachyon in node", + "description": "Human Made Tachyon in node", "main": "index.js", "config": { "bucket": "", From 1955590bb0cb332ba772c9e01029ed43736c510b Mon Sep 17 00:00:00 2001 From: Robert O'Rourke Date: Fri, 17 Jul 2020 11:03:31 +0100 Subject: [PATCH 3/3] Hardcode file type header to gif --- local-server.js | 2 +- server.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/local-server.js b/local-server.js index 58b8efc..77ac64f 100644 --- a/local-server.js +++ b/local-server.js @@ -27,7 +27,7 @@ http.createServer( function( request, response ) { if ( err ) { if ( err.message === 'fallback-to-original' ) { response.writeHead( 200, { - 'Content-Type': 'image/' + path.extname( params.query.key ).replace( '.', '' ), + 'Content-Type': 'image/gif', 'Content-Length': Buffer.byteLength( imageData ), } ); response.write( imageData ); diff --git a/server.js b/server.js index 8d8a99c..08cb8d2 100644 --- a/server.js +++ b/server.js @@ -2,7 +2,6 @@ var http = require("http"), url = require("url"), fs = require("fs"), os = require("os"), - path = require( 'path' ), tachyon = require( './index' ), proxyFile = require( './proxy-file' ), args = process.argv.slice(2), @@ -63,7 +62,7 @@ http.createServer( function( request, response ) { return response.end(); } response.writeHead( rsp.statusCode, Object.assign( { - 'Content-Type': 'image/' + path.extname( key ).replace( '.', '' ), + 'Content-Type': 'image/gif', 'Cache-Control': 'public, max-age=31557600', } ) ); response.write( Buffer.from( rsp.body, 'base64' ) );