serverless-offline 9.1.6 → 9.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/ServerlessOffline.js +32 -32
- package/src/config/defaultOptions.js +1 -1
- package/src/events/http/HttpServer.js +19 -14
- package/src/events/schedule/ScheduleEvent.js +19 -11
- package/src/events/websocket/HttpServer.js +2 -2
- package/src/events/websocket/WebSocketServer.js +2 -2
- package/src/events/websocket/lambda-events/WebSocketConnectEvent.js +1 -1
- package/src/lambda/HttpServer.js +5 -3
- package/src/lambda/LambdaFunction.js +1 -4
- package/src/lambda/__tests__/LambdaFunction.test.js +17 -17
- package/src/lambda/__tests__/fixtures/Lambda/{LambdaFunctionThatReturnsJSONObject.fixture.js → LambdaFunctionThatReturnsJSONObject-fixture.js} +2 -3
- package/src/lambda/__tests__/fixtures/Lambda/{LambdaFunctionThatReturnsNativeString.fixture.js → LambdaFunctionThatReturnsNativeString-fixture.js} +2 -2
- package/src/lambda/__tests__/fixtures/{lambdaFunction.fixture.js → lambdaFunction-fixture.js} +0 -0
- package/src/lambda/__tests__/routes/invocations/InvocationsController.test.js +2 -2
- package/src/lambda/handler-runner/HandlerRunner.js +3 -11
- package/src/lambda/handler-runner/child-process-runner/ChildProcessRunner.js +9 -6
- package/src/lambda/handler-runner/child-process-runner/childProcessHelper.js +8 -5
- package/src/lambda/handler-runner/go-runner/GoRunner.js +3 -1
- package/src/lambda/handler-runner/in-process-runner/InProcessRunner.js +16 -34
- package/src/lambda/handler-runner/in-process-runner/aws-lambda-ric/UserFunction.js +359 -0
- package/src/lambda/handler-runner/in-process-runner/aws-lambda-ric/package.json +3 -0
- package/src/lambda/handler-runner/python-runner/PythonRunner.js +17 -19
- package/src/lambda/handler-runner/ruby-runner/RubyRunner.js +4 -1
- package/src/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +5 -6
- package/src/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +8 -5
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dedicatedTo": "Blue, a great migrating bird.",
|
|
3
3
|
"name": "serverless-offline",
|
|
4
|
-
"version": "9.1
|
|
4
|
+
"version": "9.2.1",
|
|
5
5
|
"description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "./src/index.js",
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
"@hapi/h2o2": "^9.1.0",
|
|
196
196
|
"@hapi/hapi": "^20.2.2",
|
|
197
197
|
"@serverless/utils": "^6.7.0",
|
|
198
|
-
"aws-sdk": "^2.
|
|
198
|
+
"aws-sdk": "^2.1194.0",
|
|
199
199
|
"boxen": "^7.0.0",
|
|
200
200
|
"chalk": "^5.0.1",
|
|
201
201
|
"execa": "^6.1.0",
|
|
@@ -217,7 +217,7 @@
|
|
|
217
217
|
},
|
|
218
218
|
"devDependencies": {
|
|
219
219
|
"archiver": "^5.3.1",
|
|
220
|
-
"eslint": "^8.
|
|
220
|
+
"eslint": "^8.22.0",
|
|
221
221
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
222
222
|
"eslint-config-prettier": "^8.5.0",
|
|
223
223
|
"eslint-plugin-import": "^2.25.4",
|
package/src/ServerlessOffline.js
CHANGED
|
@@ -23,38 +23,38 @@ export default class ServerlessOffline {
|
|
|
23
23
|
|
|
24
24
|
#webSocket = null
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
lifecycleEvents: ['init', 'ready', 'end'],
|
|
40
|
-
options: commandOptions,
|
|
41
|
-
usage:
|
|
42
|
-
'Simulates API Gateway to call your lambda functions offline using backward compatible initialization.',
|
|
43
|
-
},
|
|
26
|
+
commands = {
|
|
27
|
+
offline: {
|
|
28
|
+
// add start nested options
|
|
29
|
+
commands: {
|
|
30
|
+
functionsUpdated: {
|
|
31
|
+
lifecycleEvents: ['cleanup'],
|
|
32
|
+
type: 'entrypoint',
|
|
33
|
+
},
|
|
34
|
+
start: {
|
|
35
|
+
lifecycleEvents: ['init', 'ready', 'end'],
|
|
36
|
+
options: commandOptions,
|
|
37
|
+
usage:
|
|
38
|
+
'Simulates API Gateway to call your lambda functions offline using backward compatible initialization.',
|
|
44
39
|
},
|
|
45
|
-
lifecycleEvents: ['start'],
|
|
46
|
-
options: commandOptions,
|
|
47
|
-
usage: 'Simulates API Gateway to call your lambda functions offline.',
|
|
48
40
|
},
|
|
49
|
-
|
|
41
|
+
lifecycleEvents: ['start'],
|
|
42
|
+
options: commandOptions,
|
|
43
|
+
usage: 'Simulates API Gateway to call your lambda functions offline.',
|
|
44
|
+
},
|
|
45
|
+
}
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
47
|
+
hooks = {
|
|
48
|
+
'offline:functionsUpdated:cleanup': this.#cleanupFunctions.bind(this),
|
|
49
|
+
'offline:start': this.#startWithExplicitEnd.bind(this),
|
|
50
|
+
'offline:start:end': this.end.bind(this),
|
|
51
|
+
'offline:start:init': this.start.bind(this),
|
|
52
|
+
'offline:start:ready': this.#ready.bind(this),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
constructor(serverless, cliOptions) {
|
|
56
|
+
this.#cliOptions = cliOptions
|
|
57
|
+
this.#serverless = serverless
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// Entry point for the plugin (sls offline) when running 'sls offline start'
|
|
@@ -245,9 +245,9 @@ export default class ServerlessOffline {
|
|
|
245
245
|
|
|
246
246
|
log.notice()
|
|
247
247
|
log.notice(
|
|
248
|
-
`Starting Offline at stage ${
|
|
249
|
-
|
|
250
|
-
)}`,
|
|
248
|
+
`Starting Offline at stage ${
|
|
249
|
+
this.#options.stage || provider.stage
|
|
250
|
+
} ${chalk.gray(`(${this.#options.region || provider.region})`)}`,
|
|
251
251
|
)
|
|
252
252
|
log.notice()
|
|
253
253
|
log.debug('options:', this.#options)
|
|
@@ -49,6 +49,18 @@ export default class HttpServer {
|
|
|
49
49
|
this.#serverless = serverless
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
async #loadCerts(httpsProtocol) {
|
|
53
|
+
const [cert, key] = await Promise.all([
|
|
54
|
+
readFile(resolve(httpsProtocol, 'cert.pem'), 'utf-8'),
|
|
55
|
+
readFile(resolve(httpsProtocol, 'key.pem'), 'utf-8'),
|
|
56
|
+
])
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
cert,
|
|
60
|
+
key,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
52
64
|
async createServer() {
|
|
53
65
|
const {
|
|
54
66
|
enforceSecureCookies,
|
|
@@ -77,19 +89,10 @@ export default class HttpServer {
|
|
|
77
89
|
isSameSite: false,
|
|
78
90
|
isSecure: false,
|
|
79
91
|
},
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const [cert, key] = await Promise.all([
|
|
85
|
-
readFile(resolve(httpsProtocol, 'cert.pem'), 'utf-8'),
|
|
86
|
-
readFile(resolve(httpsProtocol, 'key.pem'), 'utf-8'),
|
|
87
|
-
])
|
|
88
|
-
|
|
89
|
-
serverOptions.tls = {
|
|
90
|
-
cert,
|
|
91
|
-
key,
|
|
92
|
-
}
|
|
92
|
+
// https support
|
|
93
|
+
...(httpsProtocol != null && {
|
|
94
|
+
tls: await this.#loadCerts(httpsProtocol),
|
|
95
|
+
}),
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
// Hapijs server creation
|
|
@@ -1017,7 +1020,9 @@ export default class HttpServer {
|
|
|
1017
1020
|
auth: authStrategyName,
|
|
1018
1021
|
cors,
|
|
1019
1022
|
state,
|
|
1020
|
-
timeout: {
|
|
1023
|
+
timeout: {
|
|
1024
|
+
socket: false,
|
|
1025
|
+
},
|
|
1021
1026
|
}
|
|
1022
1027
|
|
|
1023
1028
|
// skip HEAD routes as hapi will fail with 'Method name not allowed: HEAD ...'
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import { createUniqueId } from '../../utils/index.js'
|
|
2
2
|
|
|
3
3
|
export default class ScheduleEvent {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
account = createUniqueId()
|
|
5
|
+
|
|
6
|
+
detail = {};
|
|
7
|
+
|
|
8
|
+
['detail-type'] = 'Scheduled Event'
|
|
9
|
+
|
|
10
|
+
id = createUniqueId()
|
|
11
|
+
|
|
12
|
+
region = null
|
|
7
13
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
resources = []
|
|
15
|
+
|
|
16
|
+
source = 'aws.events'
|
|
17
|
+
|
|
18
|
+
// format of aws displaying the time, e.g.: 2020-02-09T14:13:57Z
|
|
19
|
+
time = new Date().toISOString().replace(/\.(.*)(?=Z)/g, '')
|
|
20
|
+
|
|
21
|
+
version = '0'
|
|
22
|
+
|
|
23
|
+
constructor(region) {
|
|
12
24
|
this.region = region
|
|
13
|
-
this.resources = []
|
|
14
|
-
this.source = 'aws.events'
|
|
15
|
-
this.time = time
|
|
16
|
-
this.version = '0'
|
|
17
25
|
}
|
|
18
26
|
}
|
|
@@ -50,8 +50,8 @@ export default class HttpServer {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
log.notice(
|
|
53
|
-
`Offline [http for websocket] listening on
|
|
54
|
-
httpsProtocol ? '
|
|
53
|
+
`Offline [http for websocket] listening on ${
|
|
54
|
+
httpsProtocol ? 'https' : 'http'
|
|
55
55
|
}://${host}:${websocketPort}`,
|
|
56
56
|
)
|
|
57
57
|
}
|
|
@@ -58,8 +58,8 @@ export default class WebSocketServer {
|
|
|
58
58
|
const { host, httpsProtocol, websocketPort } = this.#options
|
|
59
59
|
|
|
60
60
|
log.notice(
|
|
61
|
-
`Offline [websocket] listening on
|
|
62
|
-
httpsProtocol ? '
|
|
61
|
+
`Offline [websocket] listening on ${
|
|
62
|
+
httpsProtocol ? 'wss' : 'ws'
|
|
63
63
|
}://${host}:${websocketPort}`,
|
|
64
64
|
)
|
|
65
65
|
}
|
|
@@ -37,7 +37,7 @@ export default class WebSocketConnectEvent {
|
|
|
37
37
|
// 'X-Amzn-Trace-Id': `Root=${createUniqueId()}`,
|
|
38
38
|
// 'X-Forwarded-For': '127.0.0.1',
|
|
39
39
|
// 'X-Forwarded-Port': String(this.#websocketPort),
|
|
40
|
-
// 'X-Forwarded-Proto':
|
|
40
|
+
// 'X-Forwarded-Proto': ${httpsProtocol ? 'https' : 'http'},
|
|
41
41
|
// }
|
|
42
42
|
|
|
43
43
|
const headers = parseHeaders(this.#rawHeaders)
|
package/src/lambda/HttpServer.js
CHANGED
|
@@ -44,13 +44,15 @@ export default class HttpServer {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
log.notice(
|
|
47
|
-
`Offline [http for lambda] listening on
|
|
48
|
-
httpsProtocol ? '
|
|
47
|
+
`Offline [http for lambda] listening on ${
|
|
48
|
+
httpsProtocol ? 'https' : 'http'
|
|
49
49
|
}://${host}:${lambdaPort}`,
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
// Print all the invocation routes to debug
|
|
53
|
-
const basePath =
|
|
53
|
+
const basePath = `${
|
|
54
|
+
httpsProtocol ? 'https' : 'http'
|
|
55
|
+
}://${host}:${lambdaPort}`
|
|
54
56
|
const funcNamePairs = this.#lambda.listFunctionNamePairs()
|
|
55
57
|
|
|
56
58
|
log.notice(
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
DEFAULT_LAMBDA_TIMEOUT,
|
|
14
14
|
supportedRuntimes,
|
|
15
15
|
} from '../config/index.js'
|
|
16
|
-
import { createUniqueId
|
|
16
|
+
import { createUniqueId } from '../utils/index.js'
|
|
17
17
|
|
|
18
18
|
const { ceil } = Math
|
|
19
19
|
const { entries, fromEntries } = Object
|
|
@@ -70,7 +70,6 @@ export default class LambdaFunction {
|
|
|
70
70
|
const servicepath = resolve(servicePath, options.location || '')
|
|
71
71
|
|
|
72
72
|
const { handler, name, package: functionPackage = {} } = functionDefinition
|
|
73
|
-
const [handlerPath, handlerName] = splitHandlerPathAndName(handler)
|
|
74
73
|
|
|
75
74
|
const memorySize =
|
|
76
75
|
functionDefinition.memorySize ||
|
|
@@ -140,8 +139,6 @@ export default class LambdaFunction {
|
|
|
140
139
|
? resolve(servicepath, functionPackage.artifact)
|
|
141
140
|
: undefined,
|
|
142
141
|
handler,
|
|
143
|
-
handlerName,
|
|
144
|
-
handlerPath: resolve(this.#codeDir, handlerPath),
|
|
145
142
|
layers: functionDefinition.layers || [],
|
|
146
143
|
provider,
|
|
147
144
|
runtime,
|
|
@@ -27,52 +27,52 @@ describe('LambdaFunction', () => {
|
|
|
27
27
|
{
|
|
28
28
|
description: 'should return result when handler is context.done',
|
|
29
29
|
expected: 'foo',
|
|
30
|
-
handler: 'fixtures/lambdaFunction
|
|
30
|
+
handler: 'fixtures/lambdaFunction-fixture.contextDoneHandler',
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
description:
|
|
34
34
|
'should return result when handler is context.done which is deferred',
|
|
35
35
|
expected: 'foo',
|
|
36
|
-
handler: 'fixtures/lambdaFunction
|
|
36
|
+
handler: 'fixtures/lambdaFunction-fixture.contextDoneHandlerDeferred',
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
description: 'should return result when handler is context.succeed',
|
|
40
40
|
expected: 'foo',
|
|
41
|
-
handler: 'fixtures/lambdaFunction
|
|
41
|
+
handler: 'fixtures/lambdaFunction-fixture.contextSucceedHandler',
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
description:
|
|
45
45
|
'should return result when handler is context.succeed which is deferred',
|
|
46
46
|
expected: 'foo',
|
|
47
47
|
handler:
|
|
48
|
-
'fixtures/lambdaFunction
|
|
48
|
+
'fixtures/lambdaFunction-fixture.contextSucceedHandlerDeferred',
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
description: 'should return result when handler is a callback',
|
|
52
52
|
expected: 'foo',
|
|
53
|
-
handler: 'fixtures/lambdaFunction
|
|
53
|
+
handler: 'fixtures/lambdaFunction-fixture.callbackHandler',
|
|
54
54
|
},
|
|
55
55
|
{
|
|
56
56
|
description:
|
|
57
57
|
'should return result when handler is a callback which is deferred',
|
|
58
58
|
expected: 'foo',
|
|
59
|
-
handler: 'fixtures/lambdaFunction
|
|
59
|
+
handler: 'fixtures/lambdaFunction-fixture.callbackHandlerDeferred',
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
description: 'should return result when handler returns a promise',
|
|
63
63
|
expected: 'foo',
|
|
64
|
-
handler: 'fixtures/lambdaFunction
|
|
64
|
+
handler: 'fixtures/lambdaFunction-fixture.promiseHandler',
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
description:
|
|
68
68
|
'should return result when handler returns a promise which is deferred',
|
|
69
69
|
expected: 'foo',
|
|
70
|
-
handler: 'fixtures/lambdaFunction
|
|
70
|
+
handler: 'fixtures/lambdaFunction-fixture.promiseHandlerDeferred',
|
|
71
71
|
},
|
|
72
72
|
{
|
|
73
73
|
description: 'should return result when handler is an async function',
|
|
74
74
|
expected: 'foo',
|
|
75
|
-
handler: 'fixtures/lambdaFunction
|
|
75
|
+
handler: 'fixtures/lambdaFunction-fixture.asyncFunctionHandler',
|
|
76
76
|
},
|
|
77
77
|
// NOTE: mix and matching of callbacks and promises is not recommended,
|
|
78
78
|
// nonetheless, we test some of the behaviour to match AWS execution precedence
|
|
@@ -81,33 +81,33 @@ describe('LambdaFunction', () => {
|
|
|
81
81
|
'should return result when handler returns a callback but defines a callback parameter',
|
|
82
82
|
expected: 'Hello Promise!',
|
|
83
83
|
handler:
|
|
84
|
-
'fixtures/lambdaFunction
|
|
84
|
+
'fixtures/lambdaFunction-fixture.promiseWithDefinedCallbackHandler',
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
description:
|
|
88
88
|
'should return result when handler calls context.succeed and context.done',
|
|
89
89
|
expected: 'Hello Context.succeed!',
|
|
90
90
|
handler:
|
|
91
|
-
'fixtures/lambdaFunction
|
|
91
|
+
'fixtures/lambdaFunction-fixture.contextSucceedWithContextDoneHandler',
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
description:
|
|
95
95
|
'should return result when handler calls callback and context.done',
|
|
96
96
|
expected: 'Hello Callback!',
|
|
97
97
|
handler:
|
|
98
|
-
'fixtures/lambdaFunction
|
|
98
|
+
'fixtures/lambdaFunction-fixture.callbackWithContextDoneHandler',
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
description:
|
|
102
102
|
'should return result when handler calls callback and returns Promise',
|
|
103
103
|
expected: 'Hello Callback!',
|
|
104
|
-
handler: 'fixtures/lambdaFunction
|
|
104
|
+
handler: 'fixtures/lambdaFunction-fixture.callbackWithPromiseHandler',
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
107
|
description:
|
|
108
108
|
'should return result when handler calls callback inside returned Promise',
|
|
109
109
|
expected: 'Hello Callback!',
|
|
110
|
-
handler: 'fixtures/lambdaFunction
|
|
110
|
+
handler: 'fixtures/lambdaFunction-fixture.callbackInsidePromiseHandler',
|
|
111
111
|
},
|
|
112
112
|
].forEach(({ description, expected, handler }) => {
|
|
113
113
|
it(description, async () => {
|
|
@@ -132,7 +132,7 @@ describe('LambdaFunction', () => {
|
|
|
132
132
|
|
|
133
133
|
it('should pass remaining time to LambdaContext', async () => {
|
|
134
134
|
const functionDefinition = {
|
|
135
|
-
handler: 'fixtures/lambdaFunction
|
|
135
|
+
handler: 'fixtures/lambdaFunction-fixture.remainingExecutionTimeHandler',
|
|
136
136
|
}
|
|
137
137
|
const options = {}
|
|
138
138
|
const lambdaFunction = new LambdaFunction(
|
|
@@ -152,7 +152,7 @@ describe('LambdaFunction', () => {
|
|
|
152
152
|
|
|
153
153
|
it.skip('should use default lambda timeout when timeout is not provided', async () => {
|
|
154
154
|
const functionDefinition = {
|
|
155
|
-
handler: 'fixtures/lambdaFunction
|
|
155
|
+
handler: 'fixtures/lambdaFunction-fixture.defaultTimeoutHandler',
|
|
156
156
|
}
|
|
157
157
|
const options = {}
|
|
158
158
|
const lambdaFunction = new LambdaFunction(
|
|
@@ -175,7 +175,7 @@ describe('LambdaFunction', () => {
|
|
|
175
175
|
// // might run flaky (unreliable)
|
|
176
176
|
// test('executionTimeInMillis should return execution time', async () => {
|
|
177
177
|
// const functionDefinition = {
|
|
178
|
-
// handler: 'fixtures/lambdaFunction
|
|
178
|
+
// handler: 'fixtures/lambdaFunction-fixture.executionTimeInMillisHandler',
|
|
179
179
|
// }
|
|
180
180
|
// const options = {}
|
|
181
181
|
// const lambdaFunction = new LambdaFunction(
|
|
@@ -12,7 +12,7 @@ export default class LambdaFunctionThatReturnsJSONObject {
|
|
|
12
12
|
serverless = {
|
|
13
13
|
config: {
|
|
14
14
|
serverlessPath: '',
|
|
15
|
-
servicePath: resolve(__dirname),
|
|
15
|
+
servicePath: resolve(__dirname, '../..'),
|
|
16
16
|
},
|
|
17
17
|
service: {
|
|
18
18
|
provider: {
|
|
@@ -27,8 +27,7 @@ export default class LambdaFunctionThatReturnsJSONObject {
|
|
|
27
27
|
|
|
28
28
|
getByFunctionName(functionName) {
|
|
29
29
|
const functionDefinition = {
|
|
30
|
-
handler:
|
|
31
|
-
'../../fixtures/lambdaFunction.fixture.asyncFunctionHandlerObject',
|
|
30
|
+
handler: 'fixtures/lambdaFunction-fixture.asyncFunctionHandlerObject',
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
this.#lambdaFunction = new LambdaFunction(
|
|
@@ -12,7 +12,7 @@ export default class LambdaFunctionThatReturnsNativeString {
|
|
|
12
12
|
serverless = {
|
|
13
13
|
config: {
|
|
14
14
|
serverlessPath: '',
|
|
15
|
-
servicePath: resolve(__dirname),
|
|
15
|
+
servicePath: resolve(__dirname, '../..'),
|
|
16
16
|
},
|
|
17
17
|
service: {
|
|
18
18
|
provider: {
|
|
@@ -27,7 +27,7 @@ export default class LambdaFunctionThatReturnsNativeString {
|
|
|
27
27
|
|
|
28
28
|
getByFunctionName(functionName) {
|
|
29
29
|
const functionDefinition = {
|
|
30
|
-
handler: '
|
|
30
|
+
handler: 'fixtures/lambdaFunction-fixture.asyncFunctionHandler',
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
this.#lambdaFunction = new LambdaFunction(
|
package/src/lambda/__tests__/fixtures/{lambdaFunction.fixture.js → lambdaFunction-fixture.js}
RENAMED
|
File without changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
2
|
import InvocationsController from '../../../routes/invocations/InvocationsController.js'
|
|
3
|
-
import LambdaFunctionThatReturnsJSONObject from '../../fixtures/Lambda/LambdaFunctionThatReturnsJSONObject
|
|
4
|
-
import LambdaFunctionThatReturnsNativeString from '../../fixtures/Lambda/LambdaFunctionThatReturnsNativeString
|
|
3
|
+
import LambdaFunctionThatReturnsJSONObject from '../../fixtures/Lambda/LambdaFunctionThatReturnsJSONObject-fixture.js'
|
|
4
|
+
import LambdaFunctionThatReturnsNativeString from '../../fixtures/Lambda/LambdaFunctionThatReturnsNativeString-fixture.js'
|
|
5
5
|
|
|
6
6
|
describe('InvocationController', () => {
|
|
7
7
|
const functionName = 'foo'
|
|
@@ -24,11 +24,9 @@ export default class HandlerRunner {
|
|
|
24
24
|
|
|
25
25
|
async #loadRunner() {
|
|
26
26
|
const { useChildProcesses, useDocker, useInProcess } = this.#options
|
|
27
|
+
const { handler, runtime } = this.#funOptions
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
this.#funOptions
|
|
30
|
-
|
|
31
|
-
log.debug(`Loading handler... (${handlerPath})`)
|
|
29
|
+
log.debug(`Loading handler... (${handler})`)
|
|
32
30
|
|
|
33
31
|
if (useDocker) {
|
|
34
32
|
// https://github.com/lambci/docker-lambda/issues/329
|
|
@@ -71,13 +69,7 @@ export default class HandlerRunner {
|
|
|
71
69
|
'./in-process-runner/index.js'
|
|
72
70
|
)
|
|
73
71
|
|
|
74
|
-
return new InProcessRunner(
|
|
75
|
-
functionKey,
|
|
76
|
-
handlerPath,
|
|
77
|
-
handlerName,
|
|
78
|
-
this.#env,
|
|
79
|
-
timeout,
|
|
80
|
-
)
|
|
72
|
+
return new InProcessRunner(this.#funOptions, this.#env)
|
|
81
73
|
}
|
|
82
74
|
|
|
83
75
|
const { default: WorkerThreadRunner } = await import(
|
|
@@ -7,23 +7,26 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
|
7
7
|
const childProcessHelperPath = resolve(__dirname, 'childProcessHelper.js')
|
|
8
8
|
|
|
9
9
|
export default class ChildProcessRunner {
|
|
10
|
+
#codeDir = null
|
|
11
|
+
|
|
10
12
|
#env = null
|
|
11
13
|
|
|
12
14
|
#functionKey = null
|
|
13
15
|
|
|
14
|
-
#
|
|
16
|
+
#handler = null
|
|
15
17
|
|
|
16
|
-
#
|
|
18
|
+
#servicePath = null
|
|
17
19
|
|
|
18
20
|
#timeout = null
|
|
19
21
|
|
|
20
22
|
constructor(funOptions, env) {
|
|
21
|
-
const { functionKey,
|
|
23
|
+
const { codeDir, functionKey, handler, servicePath, timeout } = funOptions
|
|
22
24
|
|
|
25
|
+
this.#codeDir = codeDir
|
|
23
26
|
this.#env = env
|
|
24
27
|
this.#functionKey = functionKey
|
|
25
|
-
this.#
|
|
26
|
-
this.#
|
|
28
|
+
this.#handler = handler
|
|
29
|
+
this.#servicePath = servicePath
|
|
27
30
|
this.#timeout = timeout
|
|
28
31
|
}
|
|
29
32
|
|
|
@@ -34,7 +37,7 @@ export default class ChildProcessRunner {
|
|
|
34
37
|
async run(event, context) {
|
|
35
38
|
const childProcess = execaNode(
|
|
36
39
|
childProcessHelperPath,
|
|
37
|
-
[this.#functionKey, this.#
|
|
40
|
+
[this.#functionKey, this.#handler, this.#servicePath, this.#codeDir],
|
|
38
41
|
{
|
|
39
42
|
env: this.#env,
|
|
40
43
|
stdio: 'inherit',
|
|
@@ -21,18 +21,21 @@ process.on('uncaughtException', (err) => {
|
|
|
21
21
|
})
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
-
const [, , functionKey,
|
|
24
|
+
const [, , functionKey, handler, servicePath, codeDir] = argv
|
|
25
25
|
|
|
26
26
|
process.on('message', async (messageData) => {
|
|
27
27
|
const { context, event, timeout } = messageData
|
|
28
28
|
|
|
29
29
|
// TODO we could probably cache this in the module scope?
|
|
30
30
|
const inProcessRunner = new InProcessRunner(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
{
|
|
32
|
+
codeDir,
|
|
33
|
+
functionKey,
|
|
34
|
+
handler,
|
|
35
|
+
servicePath,
|
|
36
|
+
timeout,
|
|
37
|
+
},
|
|
34
38
|
process.env,
|
|
35
|
-
timeout,
|
|
36
39
|
)
|
|
37
40
|
|
|
38
41
|
const result = await inProcessRunner.run(event, context)
|
|
@@ -4,6 +4,7 @@ import process, { chdir, cwd } from 'node:process'
|
|
|
4
4
|
import { parse as pathParse, resolve, sep } from 'node:path'
|
|
5
5
|
import { log } from '@serverless/utils/log.js'
|
|
6
6
|
import { execa } from 'execa'
|
|
7
|
+
import { splitHandlerPathAndName } from '../../../utils/index.js'
|
|
7
8
|
|
|
8
9
|
const { parse, stringify } = JSON
|
|
9
10
|
|
|
@@ -23,7 +24,8 @@ export default class GoRunner {
|
|
|
23
24
|
#tmpPath = null
|
|
24
25
|
|
|
25
26
|
constructor(funOptions, env) {
|
|
26
|
-
const {
|
|
27
|
+
const { handler, codeDir } = funOptions
|
|
28
|
+
const [handlerPath] = splitHandlerPathAndName(handler)
|
|
27
29
|
|
|
28
30
|
this.#codeDir = codeDir
|
|
29
31
|
this.#env = env
|
|
@@ -1,29 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { join } from 'node:path'
|
|
2
2
|
import { performance } from 'node:perf_hooks'
|
|
3
3
|
import process from 'node:process'
|
|
4
|
-
import {
|
|
4
|
+
import { load } from './aws-lambda-ric/UserFunction.js'
|
|
5
5
|
|
|
6
6
|
const { floor } = Math
|
|
7
7
|
const { assign } = Object
|
|
8
8
|
|
|
9
|
-
const require = createRequire(import.meta.url)
|
|
10
|
-
|
|
11
9
|
export default class InProcessRunner {
|
|
10
|
+
#codeDir = null
|
|
11
|
+
|
|
12
12
|
#env = null
|
|
13
13
|
|
|
14
14
|
#functionKey = null
|
|
15
15
|
|
|
16
|
-
#
|
|
16
|
+
#handler = null
|
|
17
17
|
|
|
18
|
-
#
|
|
18
|
+
#servicePath = null
|
|
19
19
|
|
|
20
20
|
#timeout = null
|
|
21
21
|
|
|
22
|
-
constructor(
|
|
22
|
+
constructor(funOptions, env) {
|
|
23
|
+
const { codeDir, functionKey, handler, servicePath, timeout } = funOptions
|
|
24
|
+
|
|
25
|
+
this.#codeDir = codeDir
|
|
23
26
|
this.#env = env
|
|
24
27
|
this.#functionKey = functionKey
|
|
25
|
-
this.#
|
|
26
|
-
this.#
|
|
28
|
+
this.#handler = handler
|
|
29
|
+
this.#servicePath = servicePath
|
|
27
30
|
this.#timeout = timeout
|
|
28
31
|
}
|
|
29
32
|
|
|
@@ -32,37 +35,16 @@ export default class InProcessRunner {
|
|
|
32
35
|
cleanup() {}
|
|
33
36
|
|
|
34
37
|
async run(event, context) {
|
|
35
|
-
// check if the handler module path exists
|
|
36
|
-
if (!require.resolve(this.#handlerPath)) {
|
|
37
|
-
throw new Error(
|
|
38
|
-
`Could not find handler module '${this.#handlerPath}' for function '${
|
|
39
|
-
this.#functionKey
|
|
40
|
-
}'.`,
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
38
|
// process.env should be available in the handler module scope as well as in the handler function scope
|
|
45
39
|
// NOTE: Don't use Object spread (...) here!
|
|
46
40
|
// otherwise the values of the attached props are not coerced to a string
|
|
47
41
|
// e.g. process.env.foo = 1 should be coerced to '1' (string)
|
|
48
42
|
assign(process.env, this.#env)
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
;({ [this.#handlerName]: handler } = require(this.#handlerPath))
|
|
55
|
-
} catch (err) {
|
|
56
|
-
log.error(err)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (typeof handler !== 'function') {
|
|
60
|
-
throw new Error(
|
|
61
|
-
`offline: handler '${this.#handlerName}' in ${
|
|
62
|
-
this.#handlerPath
|
|
63
|
-
} is not a function`,
|
|
64
|
-
)
|
|
65
|
-
}
|
|
44
|
+
const handler = await load(
|
|
45
|
+
this.#servicePath,
|
|
46
|
+
join(this.#codeDir, this.#handler),
|
|
47
|
+
)
|
|
66
48
|
|
|
67
49
|
let callback
|
|
68
50
|
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { pathToFileURL } = require('node:url')
|
|
4
|
+
|
|
5
|
+
// node_modules/lambda-runtime/dist/node16/UserFunction.js
|
|
6
|
+
;(function () {
|
|
7
|
+
const __getOwnPropNames = Object.getOwnPropertyNames
|
|
8
|
+
const __commonJS = (cb, mod) =>
|
|
9
|
+
function __require() {
|
|
10
|
+
return (
|
|
11
|
+
mod ||
|
|
12
|
+
(0, cb[__getOwnPropNames(cb)[0]])(
|
|
13
|
+
(mod = { exports: {} }).exports,
|
|
14
|
+
mod,
|
|
15
|
+
),
|
|
16
|
+
mod.exports
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
const require_Errors = __commonJS({
|
|
20
|
+
'Errors.js': function (exports2, module2) {
|
|
21
|
+
'use strict'
|
|
22
|
+
|
|
23
|
+
const util = require('util')
|
|
24
|
+
function _isError(obj) {
|
|
25
|
+
return (
|
|
26
|
+
obj &&
|
|
27
|
+
obj.name &&
|
|
28
|
+
obj.message &&
|
|
29
|
+
obj.stack &&
|
|
30
|
+
typeof obj.name === 'string' &&
|
|
31
|
+
typeof obj.message === 'string' &&
|
|
32
|
+
typeof obj.stack === 'string'
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
function intoError(err) {
|
|
36
|
+
if (err instanceof Error) {
|
|
37
|
+
return err
|
|
38
|
+
}
|
|
39
|
+
return new Error(err)
|
|
40
|
+
}
|
|
41
|
+
module2.exports.intoError = intoError
|
|
42
|
+
function toRapidResponse(error) {
|
|
43
|
+
try {
|
|
44
|
+
if (util.types.isNativeError(error) || _isError(error)) {
|
|
45
|
+
return {
|
|
46
|
+
errorType: error.name,
|
|
47
|
+
errorMessage: error.message,
|
|
48
|
+
trace: error.stack.split('\n'),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
errorType: typeof error,
|
|
53
|
+
errorMessage: error.toString(),
|
|
54
|
+
trace: [],
|
|
55
|
+
}
|
|
56
|
+
} catch (_err) {
|
|
57
|
+
return {
|
|
58
|
+
errorType: 'handled',
|
|
59
|
+
errorMessage:
|
|
60
|
+
'callback called with Error argument, but there was a problem while retrieving one or more of its message, name, and stack',
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
module2.exports.toRapidResponse = toRapidResponse
|
|
65
|
+
module2.exports.toFormatted = (error) => {
|
|
66
|
+
try {
|
|
67
|
+
return ` ${JSON.stringify(error, (_k, v) =>
|
|
68
|
+
_withEnumerableProperties(v),
|
|
69
|
+
)}`
|
|
70
|
+
} catch (err) {
|
|
71
|
+
return ` ${JSON.stringify(toRapidResponse(error))}`
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function _withEnumerableProperties(error) {
|
|
75
|
+
if (error instanceof Error) {
|
|
76
|
+
const ret = {
|
|
77
|
+
errorType: error.name,
|
|
78
|
+
errorMessage: error.message,
|
|
79
|
+
code: error.code,
|
|
80
|
+
...error,
|
|
81
|
+
}
|
|
82
|
+
if (typeof error.stack === 'string') {
|
|
83
|
+
ret.stack = error.stack.split('\n')
|
|
84
|
+
}
|
|
85
|
+
return ret
|
|
86
|
+
}
|
|
87
|
+
return error
|
|
88
|
+
}
|
|
89
|
+
const errorClasses = [
|
|
90
|
+
class ImportModuleError extends Error {},
|
|
91
|
+
class HandlerNotFound extends Error {},
|
|
92
|
+
class MalformedHandlerName extends Error {},
|
|
93
|
+
class UserCodeSyntaxError extends Error {},
|
|
94
|
+
class MalformedStreamingHandler extends Error {},
|
|
95
|
+
class InvalidStreamingOperation extends Error {},
|
|
96
|
+
class UnhandledPromiseRejection extends Error {
|
|
97
|
+
constructor(reason, promise) {
|
|
98
|
+
super(reason)
|
|
99
|
+
this.reason = reason
|
|
100
|
+
this.promise = promise
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
]
|
|
104
|
+
errorClasses.forEach((e) => {
|
|
105
|
+
module2.exports[e.name] = e
|
|
106
|
+
e.prototype.name = `Runtime.${e.name}`
|
|
107
|
+
})
|
|
108
|
+
},
|
|
109
|
+
})
|
|
110
|
+
const require_VerboseLog = __commonJS({
|
|
111
|
+
'VerboseLog.js': function (exports2) {
|
|
112
|
+
'use strict'
|
|
113
|
+
|
|
114
|
+
const EnvVarName = 'AWS_LAMBDA_RUNTIME_VERBOSE'
|
|
115
|
+
const Tag = 'RUNTIME'
|
|
116
|
+
const Verbosity = (() => {
|
|
117
|
+
if (!process.env[EnvVarName]) {
|
|
118
|
+
return 0
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const verbosity = parseInt(process.env[EnvVarName])
|
|
122
|
+
return verbosity < 0 ? 0 : verbosity > 3 ? 3 : verbosity
|
|
123
|
+
} catch (_) {
|
|
124
|
+
return 0
|
|
125
|
+
}
|
|
126
|
+
})()
|
|
127
|
+
exports2.logger = function (category) {
|
|
128
|
+
return {
|
|
129
|
+
verbose() {
|
|
130
|
+
if (Verbosity >= 1) {
|
|
131
|
+
console.log.apply(null, [Tag, category, ...arguments])
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
vverbose() {
|
|
135
|
+
if (Verbosity >= 2) {
|
|
136
|
+
console.log.apply(null, [Tag, category, ...arguments])
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
vvverbose() {
|
|
140
|
+
if (Verbosity >= 3) {
|
|
141
|
+
console.log.apply(null, [Tag, category, ...arguments])
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
const require_HttpResponseStream = __commonJS({
|
|
149
|
+
'HttpResponseStream.js': function (exports2, module2) {
|
|
150
|
+
'use strict'
|
|
151
|
+
|
|
152
|
+
const METADATA_PRELUDE_CONTENT_TYPE =
|
|
153
|
+
'application/vnd.awslambda.http-integration-response'
|
|
154
|
+
const DELIMITER_LEN = 8
|
|
155
|
+
const HttpResponseStream2 = class {
|
|
156
|
+
static from(underlyingStream, prelude) {
|
|
157
|
+
underlyingStream.setContentType(METADATA_PRELUDE_CONTENT_TYPE)
|
|
158
|
+
const metadataPrelude = JSON.stringify(prelude)
|
|
159
|
+
underlyingStream._onBeforeFirstWrite = (write) => {
|
|
160
|
+
write(metadataPrelude)
|
|
161
|
+
write(new Uint8Array(DELIMITER_LEN))
|
|
162
|
+
}
|
|
163
|
+
return underlyingStream
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
module2.exports.HttpResponseStream = HttpResponseStream2
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
const path = require('path')
|
|
170
|
+
const fs = require('fs')
|
|
171
|
+
const {
|
|
172
|
+
HandlerNotFound,
|
|
173
|
+
MalformedHandlerName,
|
|
174
|
+
ImportModuleError,
|
|
175
|
+
UserCodeSyntaxError,
|
|
176
|
+
} = require_Errors()
|
|
177
|
+
const { verbose } = require_VerboseLog().logger('LOADER')
|
|
178
|
+
const { HttpResponseStream } = require_HttpResponseStream()
|
|
179
|
+
const FUNCTION_EXPR = /^([^.]*)\.(.*)$/
|
|
180
|
+
const RELATIVE_PATH_SUBSTRING = '..'
|
|
181
|
+
const HANDLER_STREAMING = Symbol.for('aws.lambda.runtime.handler.streaming')
|
|
182
|
+
const STREAM_RESPONSE = 'response'
|
|
183
|
+
const NoGlobalAwsLambda =
|
|
184
|
+
process.env.AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA === '1' ||
|
|
185
|
+
process.env.AWS_LAMBDA_NODEJS_NO_GLOBAL_AWSLAMBDA === 'true'
|
|
186
|
+
function _moduleRootAndHandler(fullHandlerString) {
|
|
187
|
+
const handlerString = path.basename(fullHandlerString)
|
|
188
|
+
const moduleRoot = fullHandlerString.substring(
|
|
189
|
+
0,
|
|
190
|
+
fullHandlerString.indexOf(handlerString),
|
|
191
|
+
)
|
|
192
|
+
return [moduleRoot, handlerString]
|
|
193
|
+
}
|
|
194
|
+
function _splitHandlerString(handler) {
|
|
195
|
+
const match = handler.match(FUNCTION_EXPR)
|
|
196
|
+
if (!match || match.length != 3) {
|
|
197
|
+
throw new MalformedHandlerName('Bad handler')
|
|
198
|
+
}
|
|
199
|
+
return [match[1], match[2]]
|
|
200
|
+
}
|
|
201
|
+
function _resolveHandler(object, nestedProperty) {
|
|
202
|
+
return nestedProperty.split('.').reduce((nested, key) => {
|
|
203
|
+
return nested && nested[key]
|
|
204
|
+
}, object)
|
|
205
|
+
}
|
|
206
|
+
function _tryRequireFile(file, extension) {
|
|
207
|
+
const path2 = file + (extension || '')
|
|
208
|
+
verbose('Try loading as commonjs:', path2)
|
|
209
|
+
return fs.existsSync(path2) ? require(path2) : void 0
|
|
210
|
+
}
|
|
211
|
+
async function _tryAwaitImport(file, extension) {
|
|
212
|
+
const path2 = file + (extension || '')
|
|
213
|
+
verbose('Try loading as esmodule:', path2)
|
|
214
|
+
if (fs.existsSync(path2)) {
|
|
215
|
+
return await import(pathToFileURL(path2).href)
|
|
216
|
+
}
|
|
217
|
+
return void 0
|
|
218
|
+
}
|
|
219
|
+
function _hasFolderPackageJsonTypeModule(folder) {
|
|
220
|
+
if (folder.endsWith('/node_modules')) {
|
|
221
|
+
return false
|
|
222
|
+
}
|
|
223
|
+
const pj = path.join(folder, '/package.json')
|
|
224
|
+
if (fs.existsSync(pj)) {
|
|
225
|
+
try {
|
|
226
|
+
const pkg = JSON.parse(fs.readFileSync(pj))
|
|
227
|
+
if (pkg) {
|
|
228
|
+
if (pkg.type === 'module') {
|
|
229
|
+
verbose(`'type: module' detected in ${pj}`)
|
|
230
|
+
return true
|
|
231
|
+
}
|
|
232
|
+
verbose(`'type: module' not detected in ${pj}`)
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
235
|
+
} catch (e) {
|
|
236
|
+
console.warn(
|
|
237
|
+
`${pj} cannot be read, it will be ignored for ES module detection purposes.`,
|
|
238
|
+
e,
|
|
239
|
+
)
|
|
240
|
+
return false
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (folder === '/') {
|
|
244
|
+
return false
|
|
245
|
+
}
|
|
246
|
+
return _hasFolderPackageJsonTypeModule(path.resolve(folder, '..'))
|
|
247
|
+
}
|
|
248
|
+
function _hasPackageJsonTypeModule(file) {
|
|
249
|
+
const jsPath = `${file}.js`
|
|
250
|
+
return fs.existsSync(jsPath)
|
|
251
|
+
? _hasFolderPackageJsonTypeModule(path.resolve(path.dirname(jsPath)))
|
|
252
|
+
: false
|
|
253
|
+
}
|
|
254
|
+
async function _tryRequire(appRoot, moduleRoot, module2) {
|
|
255
|
+
verbose(
|
|
256
|
+
'Try loading as commonjs: ',
|
|
257
|
+
module2,
|
|
258
|
+
' with paths: ,',
|
|
259
|
+
appRoot,
|
|
260
|
+
moduleRoot,
|
|
261
|
+
)
|
|
262
|
+
const lambdaStylePath = path.resolve(appRoot, moduleRoot, module2)
|
|
263
|
+
const extensionless = _tryRequireFile(lambdaStylePath)
|
|
264
|
+
if (extensionless) {
|
|
265
|
+
return extensionless
|
|
266
|
+
}
|
|
267
|
+
const pjHasModule = _hasPackageJsonTypeModule(lambdaStylePath)
|
|
268
|
+
if (!pjHasModule) {
|
|
269
|
+
const loaded2 = _tryRequireFile(lambdaStylePath, '.js')
|
|
270
|
+
if (loaded2) {
|
|
271
|
+
return loaded2
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const loaded =
|
|
275
|
+
(pjHasModule && (await _tryAwaitImport(lambdaStylePath, '.js'))) ||
|
|
276
|
+
(await _tryAwaitImport(lambdaStylePath, '.mjs')) ||
|
|
277
|
+
_tryRequireFile(lambdaStylePath, '.cjs')
|
|
278
|
+
if (loaded) {
|
|
279
|
+
return loaded
|
|
280
|
+
}
|
|
281
|
+
verbose(
|
|
282
|
+
'Try loading as commonjs: ',
|
|
283
|
+
module2,
|
|
284
|
+
' with path(s): ',
|
|
285
|
+
appRoot,
|
|
286
|
+
moduleRoot,
|
|
287
|
+
)
|
|
288
|
+
const nodeStylePath = require.resolve(module2, {
|
|
289
|
+
paths: [appRoot, moduleRoot],
|
|
290
|
+
})
|
|
291
|
+
return require(nodeStylePath)
|
|
292
|
+
}
|
|
293
|
+
async function _loadUserApp(appRoot, moduleRoot, module2) {
|
|
294
|
+
if (!NoGlobalAwsLambda) {
|
|
295
|
+
globalThis.awslambda = {
|
|
296
|
+
streamifyResponse: (handler) => {
|
|
297
|
+
handler[HANDLER_STREAMING] = STREAM_RESPONSE
|
|
298
|
+
return handler
|
|
299
|
+
},
|
|
300
|
+
HttpResponseStream,
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
return await _tryRequire(appRoot, moduleRoot, module2)
|
|
305
|
+
} catch (e) {
|
|
306
|
+
if (e instanceof SyntaxError) {
|
|
307
|
+
throw new UserCodeSyntaxError(e)
|
|
308
|
+
} else if (e.code !== void 0 && e.code === 'MODULE_NOT_FOUND') {
|
|
309
|
+
verbose('globalPaths', JSON.stringify(require('module').globalPaths))
|
|
310
|
+
throw new ImportModuleError(e)
|
|
311
|
+
} else {
|
|
312
|
+
throw e
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function _throwIfInvalidHandler(fullHandlerString) {
|
|
317
|
+
if (fullHandlerString.includes(RELATIVE_PATH_SUBSTRING)) {
|
|
318
|
+
throw new MalformedHandlerName(
|
|
319
|
+
`'${fullHandlerString}' is not a valid handler name. Use absolute paths when specifying root directories in handler names.`,
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function _isHandlerStreaming(handler) {
|
|
324
|
+
if (
|
|
325
|
+
typeof handler[HANDLER_STREAMING] === 'undefined' ||
|
|
326
|
+
handler[HANDLER_STREAMING] === null ||
|
|
327
|
+
handler[HANDLER_STREAMING] === false
|
|
328
|
+
) {
|
|
329
|
+
return false
|
|
330
|
+
}
|
|
331
|
+
if (handler[HANDLER_STREAMING] === STREAM_RESPONSE) {
|
|
332
|
+
return STREAM_RESPONSE
|
|
333
|
+
}
|
|
334
|
+
throw new MalformedStreamingHandler('Only response streaming is supported.')
|
|
335
|
+
}
|
|
336
|
+
module.exports.load = async function (appRoot, fullHandlerString) {
|
|
337
|
+
_throwIfInvalidHandler(fullHandlerString)
|
|
338
|
+
const [moduleRoot, moduleAndHandler] =
|
|
339
|
+
_moduleRootAndHandler(fullHandlerString)
|
|
340
|
+
const [module2, handlerPath] = _splitHandlerString(moduleAndHandler)
|
|
341
|
+
const userApp = await _loadUserApp(appRoot, moduleRoot, module2)
|
|
342
|
+
const handlerFunc = _resolveHandler(userApp, handlerPath)
|
|
343
|
+
if (!handlerFunc) {
|
|
344
|
+
throw new HandlerNotFound(
|
|
345
|
+
`${fullHandlerString} is undefined or not exported`,
|
|
346
|
+
)
|
|
347
|
+
}
|
|
348
|
+
if (typeof handlerFunc !== 'function') {
|
|
349
|
+
throw new HandlerNotFound(`${fullHandlerString} is not a function`)
|
|
350
|
+
}
|
|
351
|
+
return handlerFunc
|
|
352
|
+
}
|
|
353
|
+
module.exports.getHandlerMetadata = function (handlerFunc) {
|
|
354
|
+
return {
|
|
355
|
+
streaming: _isHandlerStreaming(handlerFunc),
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
module.exports.STREAM_RESPONSE = STREAM_RESPONSE
|
|
359
|
+
})()
|
|
@@ -2,9 +2,10 @@ import { spawn } from 'node:child_process'
|
|
|
2
2
|
import { EOL, platform } from 'node:os'
|
|
3
3
|
import { delimiter, dirname, join, relative, resolve } from 'node:path'
|
|
4
4
|
import process, { cwd } from 'node:process'
|
|
5
|
-
import
|
|
5
|
+
import { createInterface } from 'node:readline'
|
|
6
6
|
import { fileURLToPath } from 'node:url'
|
|
7
7
|
import { log } from '@serverless/utils/log.js'
|
|
8
|
+
import { splitHandlerPathAndName } from '../../../utils/index.js'
|
|
8
9
|
|
|
9
10
|
const { parse, stringify } = JSON
|
|
10
11
|
const { assign, hasOwn } = Object
|
|
@@ -16,18 +17,15 @@ export default class PythonRunner {
|
|
|
16
17
|
|
|
17
18
|
#env = null
|
|
18
19
|
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
#handlerPath = null
|
|
20
|
+
#handlerProcess = null
|
|
22
21
|
|
|
23
22
|
#runtime = null
|
|
24
23
|
|
|
25
24
|
constructor(funOptions, env) {
|
|
26
|
-
const {
|
|
25
|
+
const { handler, runtime } = funOptions
|
|
26
|
+
const [handlerPath, handlerName] = splitHandlerPathAndName(handler)
|
|
27
27
|
|
|
28
28
|
this.#env = env
|
|
29
|
-
this.#handlerName = handlerName
|
|
30
|
-
this.#handlerPath = handlerPath
|
|
31
29
|
this.#runtime = platform() === 'win32' ? 'python.exe' : runtime
|
|
32
30
|
|
|
33
31
|
if (process.env.VIRTUAL_ENV) {
|
|
@@ -42,13 +40,13 @@ export default class PythonRunner {
|
|
|
42
40
|
|
|
43
41
|
const [pythonExecutable] = this.#runtime.split('.')
|
|
44
42
|
|
|
45
|
-
this
|
|
43
|
+
this.#handlerProcess = spawn(
|
|
46
44
|
pythonExecutable,
|
|
47
45
|
[
|
|
48
46
|
'-u',
|
|
49
47
|
resolve(__dirname, 'invoke.py'),
|
|
50
|
-
relative(cwd(),
|
|
51
|
-
|
|
48
|
+
relative(cwd(), handlerPath),
|
|
49
|
+
handlerName,
|
|
52
50
|
],
|
|
53
51
|
{
|
|
54
52
|
env: assign(process.env, this.#env),
|
|
@@ -56,14 +54,14 @@ export default class PythonRunner {
|
|
|
56
54
|
},
|
|
57
55
|
)
|
|
58
56
|
|
|
59
|
-
this
|
|
60
|
-
input: this
|
|
57
|
+
this.#handlerProcess.stdout.readline = createInterface({
|
|
58
|
+
input: this.#handlerProcess.stdout,
|
|
61
59
|
})
|
|
62
60
|
}
|
|
63
61
|
|
|
64
62
|
// () => void
|
|
65
63
|
cleanup() {
|
|
66
|
-
this
|
|
64
|
+
this.#handlerProcess.kill()
|
|
67
65
|
}
|
|
68
66
|
|
|
69
67
|
#parsePayload(value) {
|
|
@@ -117,8 +115,8 @@ export default class PythonRunner {
|
|
|
117
115
|
try {
|
|
118
116
|
const parsed = this.#parsePayload(line.toString())
|
|
119
117
|
if (parsed) {
|
|
120
|
-
this
|
|
121
|
-
this
|
|
118
|
+
this.#handlerProcess.stdout.readline.removeListener('line', onLine)
|
|
119
|
+
this.#handlerProcess.stderr.removeListener('data', onErr)
|
|
122
120
|
return accept(parsed)
|
|
123
121
|
}
|
|
124
122
|
return null
|
|
@@ -127,12 +125,12 @@ export default class PythonRunner {
|
|
|
127
125
|
}
|
|
128
126
|
}
|
|
129
127
|
|
|
130
|
-
this
|
|
131
|
-
this
|
|
128
|
+
this.#handlerProcess.stdout.readline.on('line', onLine)
|
|
129
|
+
this.#handlerProcess.stderr.on('data', onErr)
|
|
132
130
|
|
|
133
131
|
process.nextTick(() => {
|
|
134
|
-
this
|
|
135
|
-
this
|
|
132
|
+
this.#handlerProcess.stdin.write(input)
|
|
133
|
+
this.#handlerProcess.stdin.write('\n')
|
|
136
134
|
})
|
|
137
135
|
})
|
|
138
136
|
}
|
|
@@ -4,6 +4,7 @@ import { cwd } from 'node:process'
|
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
5
|
import { log } from '@serverless/utils/log.js'
|
|
6
6
|
import { execa } from 'execa'
|
|
7
|
+
import { splitHandlerPathAndName } from '../../../utils/index.js'
|
|
7
8
|
|
|
8
9
|
const { parse, stringify } = JSON
|
|
9
10
|
const { hasOwn } = Object
|
|
@@ -20,7 +21,9 @@ export default class RubyRunner {
|
|
|
20
21
|
#handlerPath = null
|
|
21
22
|
|
|
22
23
|
constructor(funOptions, env) {
|
|
23
|
-
const
|
|
24
|
+
const [handlerPath, handlerName] = splitHandlerPathAndName(
|
|
25
|
+
funOptions.handler,
|
|
26
|
+
)
|
|
24
27
|
|
|
25
28
|
this.#env = env
|
|
26
29
|
this.#handlerName = handlerName
|
|
@@ -8,18 +8,17 @@ const workerThreadHelperPath = resolve(__dirname, './workerThreadHelper.js')
|
|
|
8
8
|
export default class WorkerThreadRunner {
|
|
9
9
|
#workerThread = null
|
|
10
10
|
|
|
11
|
-
constructor(funOptions
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const { functionKey, handlerName, handlerPath, timeout } = funOptions
|
|
11
|
+
constructor(funOptions, env) {
|
|
12
|
+
const { codeDir, functionKey, handler, servicePath, timeout } = funOptions
|
|
15
13
|
|
|
16
14
|
this.#workerThread = new Worker(workerThreadHelperPath, {
|
|
17
15
|
// don't pass process.env from the main process!
|
|
18
16
|
env,
|
|
19
17
|
workerData: {
|
|
18
|
+
codeDir,
|
|
20
19
|
functionKey,
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
handler,
|
|
21
|
+
servicePath,
|
|
23
22
|
timeout,
|
|
24
23
|
},
|
|
25
24
|
})
|
|
@@ -2,18 +2,21 @@ import { env } from 'node:process'
|
|
|
2
2
|
import { parentPort, workerData } from 'node:worker_threads'
|
|
3
3
|
import InProcessRunner from '../in-process-runner/index.js'
|
|
4
4
|
|
|
5
|
-
const { functionKey,
|
|
5
|
+
const { functionKey, handler, servicePath, timeout, codeDir } = workerData
|
|
6
6
|
|
|
7
7
|
parentPort.on('message', async (messageData) => {
|
|
8
8
|
const { context, event, port } = messageData
|
|
9
9
|
|
|
10
10
|
// TODO we could probably cache this in the module scope?
|
|
11
11
|
const inProcessRunner = new InProcessRunner(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
{
|
|
13
|
+
codeDir,
|
|
14
|
+
functionKey,
|
|
15
|
+
handler,
|
|
16
|
+
servicePath,
|
|
17
|
+
timeout,
|
|
18
|
+
},
|
|
15
19
|
env,
|
|
16
|
-
timeout,
|
|
17
20
|
)
|
|
18
21
|
|
|
19
22
|
let result
|