diff --git a/src/handlers/s3.ts b/src/handlers/s3.ts index 7c3dc4c..ab6c71c 100644 --- a/src/handlers/s3.ts +++ b/src/handlers/s3.ts @@ -68,15 +68,14 @@ export default function s3HandlerFactory({ ? utils.resolveParams(`${resolvedEndpoint}/{file}`, ctx.params) : resolvedEndpoint; // Bucket operations - const headers: Record = {}; + const headers = { + ...(ctx.request?.headers?.range && { range: ctx.request.headers.range }), + }; - if (ctx.request.headers.range) { - headers.range = ctx.request.headers.range; - } - - const response = await aws.fetch(url, { + const response = await aws.fetch(url + (ctx.request.search || ''), { method: ctx.method || ctx.request.method, headers, + ...(ctx.request.body && { body: ctx.request.body }), }); ctx.status = response.status; diff --git a/test/handlers/s3.test.ts b/test/handlers/s3.test.ts index 3e878c2..0cdfe6c 100644 --- a/test/handlers/s3.test.ts +++ b/test/handlers/s3.test.ts @@ -81,4 +81,78 @@ describe('s3', () => { await s3(ctx); expect(ctx.status).to.equal(200); }); + + it('Range headers are forwarded', async () => { + fetchMock.mock(`http://localhost:9000/myBucket`, (req) => { + return { + status: 200, + }; + }); + const s3 = s3Factory({ + endpoint: 'http://localhost:9000', + forcePathStyle: true, + bucket: 'myBucket', + accessKeyId: 'DERP', + secretAccessKey: 'DERP', + enableBucketOperations: true, + }); + + const ctx = helpers.getCtx(); + ctx.params = {}; + ctx.request.headers['range'] = 'blah'; + await s3(ctx); + expect(ctx.status).to.equal(200); + expect(fetchMock.lastOptions().headers['range']).to.equal('blah'); + }); + + it('Request body is forwarded', async () => { + fetchMock.mock(`http://localhost:9000/myBucket`, (req) => { + return { + status: 200, + }; + }); + const s3 = s3Factory({ + endpoint: 'http://localhost:9000', + forcePathStyle: true, + bucket: 'myBucket', + accessKeyId: 'DERP', + secretAccessKey: 'DERP', + enableBucketOperations: true, + }); + + const ctx = helpers.getCtx(); + ctx.request.method = 'PUT'; + ctx.request.body = 'hello world'; + ctx.params = {}; + ctx.request.headers['If-None-Match'] = 'blah'; + await s3(ctx); + expect(ctx.status).to.equal(200); + // aws4fetch converts body to readable stream so it is not so easy to figure out the + // actual value + expect(await fetchMock.lastOptions().body).is.not.undefined; + }); + + + + it('List bucket forwards query params', async () => { + fetchMock.mock(`http://localhost:9000/myBucket?prefix=foo&list-type=2`, { + status: 200, + }); + + const s3 = s3Factory({ + endpoint: 'http://localhost:9000', + forcePathStyle: true, + bucket: 'myBucket', + accessKeyId: 'DERP', + secretAccessKey: 'DERP', + enableBucketOperations: true, + }); + + const ctx = helpers.getCtx(); + ctx.request.search = '?prefix=foo&list-type=2'; + ctx.params = {}; + await s3(ctx); + + expect(ctx.status).to.equal(200); + }); }); diff --git a/test/helpers.ts b/test/helpers.ts index a837ce1..27687c4 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -2,7 +2,7 @@ class Context { request: { method: string; path: string; - query: {}; + search?: string; hostname: string; host: string; protocol: string; @@ -23,7 +23,7 @@ class Context { host: 'example.com', hostname: 'example.com', protocol: 'http', - query: {}, + search: '', headers: {}, }; this.event = {}; @@ -34,9 +34,6 @@ class Context { }; this.body = undefined; this.status = 404; - - // Shortcuts directly on the context - this.query = this.request.query; } set(key: string, value: string) {