Skip to content

Commit 0d67515

Browse files
committed
support streaming
1 parent 3aaa6d0 commit 0d67515

File tree

8 files changed

+103
-60
lines changed

8 files changed

+103
-60
lines changed

.npmrc

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@stackla:registry=https://npm.pkg.github.com
2+
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}

lib/framework/get-framework.js

+12-12
Original file line numberDiff line numberDiff line change
@@ -3,62 +3,62 @@
33
const http = require('http')
44
const Response = require('../response');
55

6-
function common(cb) {
6+
function common(cb, opts) {
77
return request => {
8-
const response = new Response(request);
8+
const response = new Response(request, opts);
99

1010
cb(request, response);
1111

1212
return response;
1313
};
1414
}
1515

16-
module.exports = function getFramework(app) {
16+
module.exports = function getFramework(app, opts) {
1717
if (app instanceof http.Server) {
1818
return request => {
19-
const response = new Response(request);
19+
const response = new Response(request, opts);
2020
app.emit('request', request, response)
2121
return response
2222
}
2323
}
2424

2525
if (typeof app.callback === 'function') {
26-
return common(app.callback());
26+
return common(app.callback(), opts);
2727
}
2828

2929
if (typeof app.handle === 'function') {
3030
return common((request, response) => {
3131
app.handle(request, response);
32-
});
32+
}, opts);
3333
}
3434

3535
if (typeof app.handler === 'function') {
3636
return common((request, response) => {
3737
app.handler(request, response);
38-
});
38+
}, opts);
3939
}
4040

4141
if (typeof app._onRequest === 'function') {
4242
return common((request, response) => {
4343
app._onRequest(request, response);
44-
});
44+
}, opts);
4545
}
4646

4747
if (typeof app === 'function') {
48-
return common(app);
48+
return common(app, opts);
4949
}
5050

5151
if (app.router && typeof app.router.route == 'function') {
5252
return common((req, res) => {
5353
const { url, method, headers, body } = req;
5454
app.router.route({ url, method, headers, body }, res);
55-
});
55+
}, opts);
5656
}
5757

5858
if (app._core && typeof app._core._dispatch === 'function') {
5959
return common(app._core._dispatch({
6060
app
61-
}));
61+
}), opts);
6262
}
6363

6464
if (typeof app.inject === 'function') {
@@ -72,7 +72,7 @@ module.exports = function getFramework(app) {
7272
}
7373

7474
if (typeof app.main === 'function') {
75-
return common(app.main);
75+
return common(app.main, opts);
7676
}
7777

7878
throw new Error('Unsupported framework');

lib/provider/aws/format-response.js

+17-13
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,26 @@ module.exports = (event, response, options) => {
2626
if (multiValueHeaders['set-cookie']) {
2727
cookies = multiValueHeaders['set-cookie'];
2828
}
29+
const isBase64Encoded = !response.streaming && isBinary(headers, options);
30+
let body;
31+
if (response.streaming) {
32+
body = Response.stream(response);
33+
} else {
34+
const encoding = isBase64Encoded ? 'base64' : 'utf8';
35+
body = Response.body(response).toString(encoding);
2936

30-
const isBase64Encoded = isBinary(headers, options);
31-
const encoding = isBase64Encoded ? 'base64' : 'utf8';
32-
let body = Response.body(response).toString(encoding);
33-
34-
if (headers['transfer-encoding'] === 'chunked' || response.chunkedEncoding) {
35-
const raw = Response.body(response).toString().split('\r\n');
36-
const parsed = [];
37-
for (let i = 0; i < raw.length; i +=2) {
38-
const size = parseInt(raw[i], 16);
39-
const value = raw[i + 1];
40-
if (value) {
41-
parsed.push(value.substring(0, size));
37+
if (headers['transfer-encoding'] === 'chunked' || response.chunkedEncoding) {
38+
const raw = Response.body(response).toString().split('\r\n');
39+
const parsed = [];
40+
for (let i = 0; i < raw.length; i +=2) {
41+
const size = parseInt(raw[i], 16);
42+
const value = raw[i + 1];
43+
if (value) {
44+
parsed.push(value.substring(0, size));
45+
}
4246
}
47+
body = parsed.join('')
4348
}
44-
body = parsed.join('')
4549
}
4650

4751
if (eventType === LAMBDA_EVENT_TYPES.ALB) {

lib/response.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
'use strict';
22

33
const http = require('http');
4+
const stream = require('stream');
45

56
const headerEnd = '\r\n\r\n';
67

78
const BODY = Symbol();
89
const HEADERS = Symbol();
10+
const STREAM = Symbol();
911

1012
function getString(data) {
1113
if (Buffer.isBuffer(data)) {
@@ -19,7 +21,11 @@ function getString(data) {
1921

2022
function addData(stream, data) {
2123
if (Buffer.isBuffer(data) || typeof data === 'string' || data instanceof Uint8Array) {
22-
stream[BODY].push(Buffer.from(data));
24+
if (stream.streaming) {
25+
stream[STREAM].push(Buffer.from(data));
26+
} else {
27+
stream[BODY].push(Buffer.from(data));
28+
}
2329
} else {
2430
throw new Error(`response.write() of unexpected type: ${typeof data}`);
2531
}
@@ -42,6 +48,10 @@ module.exports = class ServerlessResponse extends http.ServerResponse {
4248
return Buffer.concat(res[BODY]);
4349
}
4450

51+
static stream(res) {
52+
return res[STREAM];
53+
}
54+
4555
static headers(res) {
4656
const headers = typeof res.getHeaders === 'function'
4757
? res.getHeaders()
@@ -80,11 +90,15 @@ module.exports = class ServerlessResponse extends http.ServerResponse {
8090
super.writeHead(statusCode, reason, obj);
8191
}
8292

83-
constructor({ method }) {
93+
constructor({ method }, { streaming = false } = {}) {
8494
super({ method });
8595

8696
this[BODY] = [];
8797
this[HEADERS] = {};
98+
if (streaming) {
99+
this[STREAM] = stream.PassThrough();
100+
this.streaming = streaming;
101+
}
88102

89103
this.useChunkedEncodingByDefault = false;
90104
this.chunkedEncoding = false;

package-lock.json

+46-27
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "serverless-http",
3-
"version": "3.2.0",
2+
"name": "@stackla/serverless-http",
3+
"version": "3.2.1",
44
"description": "Use existing web application frameworks in serverless environments",
55
"main": "serverless-http.js",
66
"types": "serverless-http.d.ts",
@@ -19,8 +19,7 @@
1919
"test": "nyc mocha",
2020
"posttest": "eslint lib test",
2121
"test:integration:offline": "mocha test/integration/test-offline.js --exit",
22-
"test:integration:aws": "mocha test/integration/test-aws.js --exit",
23-
"postpublish": "git push origin master --tags"
22+
"test:integration:aws": "mocha test/integration/test-aws.js --exit"
2423
},
2524
"keywords": [
2625
"serverless",
@@ -93,5 +92,8 @@
9392
"supertest": "^6.2.2",
9493
"tree-kill": "^1.2.2",
9594
"typescript": "^4.6.3"
95+
},
96+
"dependencies": {
97+
"is-stream": "^3.0.0"
9698
}
9799
}

serverless-http.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ declare namespace ServerlessHttp {
2424
request?: Object | Function,
2525
response?: Object | Function,
2626
binary?: boolean | Function | string | string[],
27+
streaming?: boolean,
2728
basePath?: string
2829
}
2930
/**

serverless-http.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ const getFramework = require('./lib/framework/get-framework');
55
const getProvider = require('./lib/provider/get-provider');
66

77
const defaultOptions = {
8-
requestId: 'x-request-id'
8+
requestId: 'x-request-id',
9+
streaming: false,
910
};
1011

1112
module.exports = function (app, opts) {
1213
const options = Object.assign({}, defaultOptions, opts);
1314

14-
const framework = getFramework(app);
15+
const framework = getFramework(app, opts);
1516
const provider = getProvider(options);
1617

1718
return provider(async (request, ...context) => {

0 commit comments

Comments
 (0)