Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support range header to support partial downloads #24

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 22 additions & 16 deletions lib/s3.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ var debug = require('debug')('s3-proxy');
require('simple-errors');

// HTTP headers from the AWS request to forward along
var awsForwardHeaders = ['content-type', 'last-modified', 'etag', 'cache-control'];
var awsForwardHeaders = ['content-type', 'last-modified', 'etag', 'cache-control',
'content-length', 'accept-ranges', 'content-range'];

module.exports = function(options) {
module.exports = function (options) {
var s3 = new AWS.S3(assign(awsConfig(options),
pick(options, 'endpoint', 's3ForcePathStyle')));

Expand All @@ -29,7 +30,7 @@ module.exports = function(options) {
};

debug('list s3 keys at', s3Params.Prefix);
s3.listObjects(s3Params, function(err, data) {
s3.listObjects(s3Params, function (err, data) {
if (err) {
return next(Error.create('Could not read S3 keys', {
prefix: s3Params.prefix,
Expand All @@ -38,7 +39,7 @@ module.exports = function(options) {
}

var keys = [];
map(data.Contents, 'Key').forEach(function(key) {
map(data.Contents, 'Key').forEach(function (key) {
// Chop off the prefix path
if (key !== s3Params.Prefix) {
if (isEmpty(s3Params.Prefix)) {
Expand All @@ -60,7 +61,7 @@ module.exports = function(options) {
// If the key is empty (this occurs if a request comes in for a url ending in '/'), and there is a defaultKey
// option present on options, use the default key
// E.g. if someone wants to route '/' to '/index.html'
if ( s3Key === '' && options.defaultKey ) s3Key = options.defaultKey;
if (s3Key === '' && options.defaultKey) s3Key = options.defaultKey;

// Chop off the querystring, it causes problems with SDK.
var queryIndex = s3Key.indexOf('?');
Expand All @@ -70,13 +71,14 @@ module.exports = function(options) {

// Strip out any path segments that start with a double dash '--'. This is just used
// to force a cache invalidation.
s3Key = reject(s3Key.split('/'), function(segment) {
s3Key = reject(s3Key.split('/'), function (segment) {
return segment.slice(0, 2) === '--';
}).join('/');

var s3Params = {
Bucket: options.bucket,
Key: options.prefix ? urljoin(options.prefix, s3Key) : s3Key
Key: options.prefix ? urljoin(options.prefix, s3Key) : s3Key,
Range: req.headers['range'] || null,
};

debug('get s3 object with key %s', s3Params.Key);
Expand All @@ -92,15 +94,18 @@ module.exports = function(options) {
var s3Request = s3.getObject(s3Params);

// Write a custom http header with the path to the S3 object being proxied
var headerPrefix = req.app.settings.customHttpHeaderPrefix || 'x-4front-';
res.setHeader(headerPrefix + 's3-proxy-key', s3Params.Key);
if (options.proxyKey != false) {
var headerPrefix = req.app.settings.customHttpHeaderPrefix || 'x-4front-';
res.setHeader(headerPrefix + 's3-proxy-key', s3Params.Key);
}

s3Request.on('httpHeaders', function(statusCode, s3Headers) {
debug('received httpHeaders');
s3Request.on('httpHeaders', function (statusCode, s3Headers) {
debug('received httpHeaders', s3Headers);

// Get the contentType from the headers
awsForwardHeaders.forEach(function(header) {
awsForwardHeaders.forEach(function (header) {
var headerValue = s3Headers[header];
debug('got header from s3: ', header, headerValue);

if (header === 'content-type') {
if (headerValue === 'application/octet-stream') {
Expand All @@ -120,6 +125,7 @@ module.exports = function(options) {
headerValue = '"' + trim(headerValue, '"') + '_base64' + '"';
} else if (header === 'content-length' && base64Encode) {
// Clear out the content-length if we are going to base64 encode the response
debug('clearing out content-length as base64Encode is enabled');
headerValue = null;
}

Expand All @@ -133,16 +139,16 @@ module.exports = function(options) {
debug('read stream %s', s3Params.Key);

var readStream = s3Request.createReadStream()
.on('error', function(err) {
debug('readStream error');
.on('error', function (err) {
debug('readStream error', err);
// If the code is PreconditionFailed and we passed an IfNoneMatch param
// the object has not changed, so just return a 304 Not Modified response.
if (err.code === 'NotModified' ||
(err.code === 'PreconditionFailed' && s3Params.IfNoneMatch)) {
return res.status(304).end();
}
if (err.code === 'NoSuchKey') {
return next(Error.http(404, 'Missing S3 key', {code: 'missingS3Key', key: s3Params.Key}));
return next(Error.http(404, 'Missing S3 key', { code: 'missingS3Key', key: s3Params.Key }));
}
return next(err);
});
Expand All @@ -156,7 +162,7 @@ module.exports = function(options) {
readStream.pipe(res);
}

return function(req, res, next) {
return function (req, res, next) {
if (req.method !== 'GET') return next();

//If a request is made to a url ending in '/', but there isn't a default file name,
Expand Down
Loading