serverless-offline 9.1.1 → 9.1.4
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/README.md +0 -1
- package/package.json +6 -6
- package/src/ServerlessOffline.js +2 -6
- package/src/config/defaultOptions.js +1 -1
- package/src/events/http/Http.js +4 -4
- package/src/events/http/HttpServer.js +181 -159
- package/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js +2 -12
- package/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +2 -11
- package/src/events/schedule/Schedule.js +3 -3
- package/src/lambda/handler-runner/go-runner/GoRunner.js +6 -6
- package/src/lambda/handler-runner/in-process-runner/InProcessRunner.js +9 -4
- package/src/lambda/handler-runner/java-runner/JavaRunner.js +5 -3
- package/src/lambda/handler-runner/python-runner/PythonRunner.js +5 -4
- package/src/lambda/handler-runner/ruby-runner/RubyRunner.js +5 -3
package/README.md
CHANGED
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.1.4",
|
|
5
5
|
"description": "Emulate AWS λ and API Gateway locally when developing your Serverless project",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "./src/index.js",
|
|
@@ -194,7 +194,8 @@
|
|
|
194
194
|
"@hapi/boom": "^10.0.0",
|
|
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.1187.0",
|
|
198
199
|
"boxen": "^7.0.0",
|
|
199
200
|
"chalk": "^5.0.1",
|
|
200
201
|
"execa": "^6.1.0",
|
|
@@ -204,9 +205,9 @@
|
|
|
204
205
|
"jsonpath-plus": "^7.0.0",
|
|
205
206
|
"jsonschema": "^1.4.1",
|
|
206
207
|
"jsonwebtoken": "^8.5.1",
|
|
207
|
-
"jszip": "^3.10.
|
|
208
|
+
"jszip": "^3.10.1",
|
|
208
209
|
"luxon": "^3.0.1",
|
|
209
|
-
"node-fetch": "^3.2.
|
|
210
|
+
"node-fetch": "^3.2.10",
|
|
210
211
|
"node-schedule": "^2.1.0",
|
|
211
212
|
"object.hasown": "^1.1.1",
|
|
212
213
|
"p-memoize": "^7.1.0",
|
|
@@ -216,7 +217,7 @@
|
|
|
216
217
|
},
|
|
217
218
|
"devDependencies": {
|
|
218
219
|
"archiver": "^5.3.1",
|
|
219
|
-
"eslint": "^8.
|
|
220
|
+
"eslint": "^8.21.0",
|
|
220
221
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
221
222
|
"eslint-config-prettier": "^8.5.0",
|
|
222
223
|
"eslint-plugin-import": "^2.25.4",
|
|
@@ -230,7 +231,6 @@
|
|
|
230
231
|
"standard-version": "^9.5.0"
|
|
231
232
|
},
|
|
232
233
|
"peerDependencies": {
|
|
233
|
-
"@serverless/utils": "^6.7.0",
|
|
234
234
|
"serverless": "^3.2.0"
|
|
235
235
|
}
|
|
236
236
|
}
|
package/src/ServerlessOffline.js
CHANGED
|
@@ -168,7 +168,7 @@ export default class ServerlessOffline {
|
|
|
168
168
|
|
|
169
169
|
this.#http = new Http(this.#serverless, this.#options, this.#lambda)
|
|
170
170
|
|
|
171
|
-
await this.#http.
|
|
171
|
+
await this.#http.createServer()
|
|
172
172
|
|
|
173
173
|
this.#http.create(events)
|
|
174
174
|
|
|
@@ -236,12 +236,8 @@ export default class ServerlessOffline {
|
|
|
236
236
|
.replace(/\s/g, '')
|
|
237
237
|
.split(',')
|
|
238
238
|
|
|
239
|
-
if (this.#options.corsDisallowCredentials) {
|
|
240
|
-
this.#options.corsAllowCredentials = false
|
|
241
|
-
}
|
|
242
|
-
|
|
243
239
|
this.#options.corsConfig = {
|
|
244
|
-
credentials: this.#options.
|
|
240
|
+
credentials: !this.#options.corsDisallowCredentials,
|
|
245
241
|
exposedHeaders: this.#options.corsExposedHeaders,
|
|
246
242
|
headers: this.#options.corsAllowHeaders,
|
|
247
243
|
origin: this.#options.corsAllowOrigin,
|
|
@@ -2,9 +2,9 @@ import { createApiKey } from '../utils/index.js'
|
|
|
2
2
|
|
|
3
3
|
export default {
|
|
4
4
|
apiKey: createApiKey(),
|
|
5
|
-
corsAllowCredentials: true, // TODO no CLI option
|
|
6
5
|
corsAllowHeaders: 'accept,content-type,x-api-key,authorization',
|
|
7
6
|
corsAllowOrigin: '*',
|
|
7
|
+
corsDisallowCredentials: true,
|
|
8
8
|
corsExposedHeaders: 'WWW-Authenticate,Server-Authorization',
|
|
9
9
|
disableCookieValidation: false,
|
|
10
10
|
disableScheduledEvents: false,
|
package/src/events/http/Http.js
CHANGED
|
@@ -17,6 +17,10 @@ export default class Http {
|
|
|
17
17
|
return this.#httpServer.stop(timeout)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
async createServer() {
|
|
21
|
+
await this.#httpServer.createServer()
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
#createEvent(functionKey, rawHttpEventDefinition, handler) {
|
|
21
25
|
const httpEvent = new HttpEventDefinition(rawHttpEventDefinition)
|
|
22
26
|
|
|
@@ -39,10 +43,6 @@ export default class Http {
|
|
|
39
43
|
this.#httpServer.create404Route()
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
registerPlugins() {
|
|
43
|
-
return this.#httpServer.registerPlugins()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
46
|
// TEMP FIXME quick fix to expose gateway server for testing, look for better solution
|
|
47
47
|
getServer() {
|
|
48
48
|
return this.#httpServer.getServer()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer'
|
|
2
|
-
import {
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
3
|
import { createRequire } from 'node:module'
|
|
4
4
|
import { join, resolve } from 'node:path'
|
|
5
5
|
import { exit } from 'node:process'
|
|
@@ -47,7 +47,9 @@ export default class HttpServer {
|
|
|
47
47
|
this.#lambda = lambda
|
|
48
48
|
this.#options = options
|
|
49
49
|
this.#serverless = serverless
|
|
50
|
+
}
|
|
50
51
|
|
|
52
|
+
async createServer() {
|
|
51
53
|
const {
|
|
52
54
|
enforceSecureCookies,
|
|
53
55
|
host,
|
|
@@ -80,14 +82,20 @@ export default class HttpServer {
|
|
|
80
82
|
// HTTPS support
|
|
81
83
|
if (typeof httpsProtocol === 'string' && httpsProtocol.length > 0) {
|
|
82
84
|
serverOptions.tls = {
|
|
83
|
-
cert:
|
|
84
|
-
key:
|
|
85
|
+
cert: readFile(resolve(httpsProtocol, 'cert.pem'), 'ascii'),
|
|
86
|
+
key: readFile(resolve(httpsProtocol, 'key.pem'), 'ascii'),
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
// Hapijs server creation
|
|
89
91
|
this.#server = new Server(serverOptions)
|
|
90
92
|
|
|
93
|
+
try {
|
|
94
|
+
await this.#server.register([h2o2])
|
|
95
|
+
} catch (err) {
|
|
96
|
+
log.error(err)
|
|
97
|
+
}
|
|
98
|
+
|
|
91
99
|
// Enable CORS preflight response
|
|
92
100
|
this.#server.ext('onPreResponse', (request, h) => {
|
|
93
101
|
if (request.headers.origin) {
|
|
@@ -205,14 +213,6 @@ export default class HttpServer {
|
|
|
205
213
|
})
|
|
206
214
|
}
|
|
207
215
|
|
|
208
|
-
async registerPlugins() {
|
|
209
|
-
try {
|
|
210
|
-
await this.#server.register([h2o2])
|
|
211
|
-
} catch (err) {
|
|
212
|
-
log.error(err)
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
216
|
#logPluginIssue() {
|
|
217
217
|
log.notice(
|
|
218
218
|
'If you think this is an issue with the plugin please submit it, thanks!\nhttps://github.com/dherault/serverless-offline/issues',
|
|
@@ -260,7 +260,7 @@ export default class HttpServer {
|
|
|
260
260
|
log.debug(`Creating Authorization scheme for ${authKey}`)
|
|
261
261
|
|
|
262
262
|
// Create the Auth Scheme for the endpoint
|
|
263
|
-
const scheme = createJWTAuthScheme(jwtSettings
|
|
263
|
+
const scheme = createJWTAuthScheme(jwtSettings)
|
|
264
264
|
|
|
265
265
|
// Set the auth scheme and strategy on the server
|
|
266
266
|
this.#server.auth.scheme(authSchemeName, scheme)
|
|
@@ -338,6 +338,7 @@ export default class HttpServer {
|
|
|
338
338
|
* /tests/integration/custom-authentication
|
|
339
339
|
*/
|
|
340
340
|
const customizations = this.#serverless.service.custom
|
|
341
|
+
|
|
341
342
|
if (
|
|
342
343
|
customizations &&
|
|
343
344
|
customizations.offline?.customAuthenticationProvider
|
|
@@ -350,11 +351,13 @@ export default class HttpServer {
|
|
|
350
351
|
)
|
|
351
352
|
|
|
352
353
|
const strategy = provider(endpoint, functionKey, method, path)
|
|
354
|
+
|
|
353
355
|
this.#server.auth.scheme(
|
|
354
356
|
strategy.scheme,
|
|
355
357
|
strategy.getAuthenticateFunction,
|
|
356
358
|
)
|
|
357
359
|
this.#server.auth.strategy(strategy.name, strategy.scheme)
|
|
360
|
+
|
|
358
361
|
return strategy.name
|
|
359
362
|
}
|
|
360
363
|
|
|
@@ -363,164 +366,44 @@ export default class HttpServer {
|
|
|
363
366
|
? null
|
|
364
367
|
: this.#configureJWTAuthorization(endpoint, functionKey, method, path) ||
|
|
365
368
|
this.#configureAuthorization(endpoint, functionKey, method, path)
|
|
369
|
+
|
|
366
370
|
return authStrategyName
|
|
367
371
|
}
|
|
368
372
|
|
|
369
|
-
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
let method
|
|
373
|
-
let path
|
|
374
|
-
let hapiPath
|
|
375
|
-
|
|
376
|
-
if (httpEvent.isHttpApi) {
|
|
377
|
-
if (httpEvent.routeKey === '$default') {
|
|
378
|
-
method = 'ANY'
|
|
379
|
-
path = httpEvent.routeKey
|
|
380
|
-
hapiPath = '/{default*}'
|
|
381
|
-
} else {
|
|
382
|
-
;[method, path] = httpEvent.routeKey.split(' ')
|
|
383
|
-
hapiPath = generateHapiPath(
|
|
384
|
-
path,
|
|
385
|
-
{
|
|
386
|
-
...this.#options,
|
|
387
|
-
noPrependStageInUrl: true, // Serverless always uses the $default stage
|
|
388
|
-
},
|
|
389
|
-
this.#serverless,
|
|
390
|
-
)
|
|
391
|
-
}
|
|
392
|
-
} else {
|
|
393
|
-
method = httpEvent.method.toUpperCase()
|
|
394
|
-
;({ path } = httpEvent)
|
|
395
|
-
hapiPath = generateHapiPath(path, this.#options, this.#serverless)
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const endpoint = new Endpoint(
|
|
399
|
-
join(this.#serverless.config.servicePath, handlerPath),
|
|
400
|
-
httpEvent,
|
|
401
|
-
).generate()
|
|
402
|
-
|
|
403
|
-
const stage = endpoint.isHttpApi
|
|
404
|
-
? '$default'
|
|
405
|
-
: this.#options.stage || this.#serverless.service.provider.stage
|
|
406
|
-
const protectedRoutes = []
|
|
407
|
-
|
|
408
|
-
if (httpEvent.private) {
|
|
409
|
-
protectedRoutes.push(`${method}#${hapiPath}`)
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const { host, httpPort, httpsProtocol } = this.#options
|
|
413
|
-
const server = `${httpsProtocol ? 'https' : 'http'}://${host}:${httpPort}`
|
|
414
|
-
|
|
415
|
-
this.#terminalInfo.push({
|
|
416
|
-
invokePath: `/2015-03-31/functions/${functionKey}/invocations`,
|
|
417
|
-
method,
|
|
418
|
-
path: hapiPath,
|
|
419
|
-
server,
|
|
420
|
-
stage:
|
|
421
|
-
endpoint.isHttpApi || this.#options.noPrependStageInUrl ? null : stage,
|
|
422
|
-
})
|
|
423
|
-
|
|
424
|
-
const authStrategyName = this.#setAuthorizationStrategy(
|
|
373
|
+
#createHapiHandler(params) {
|
|
374
|
+
const {
|
|
375
|
+
additionalRequestContext,
|
|
425
376
|
endpoint,
|
|
426
377
|
functionKey,
|
|
378
|
+
hapiMethod,
|
|
379
|
+
hapiPath,
|
|
427
380
|
method,
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
let cors = null
|
|
432
|
-
if (endpoint.cors) {
|
|
433
|
-
cors = {
|
|
434
|
-
credentials:
|
|
435
|
-
endpoint.cors.credentials || this.#options.corsConfig.credentials,
|
|
436
|
-
exposedHeaders: this.#options.corsConfig.exposedHeaders,
|
|
437
|
-
headers: endpoint.cors.headers || this.#options.corsConfig.headers,
|
|
438
|
-
origin: endpoint.cors.origins || this.#options.corsConfig.origin,
|
|
439
|
-
}
|
|
440
|
-
} else if (
|
|
441
|
-
this.#serverless.service.provider.httpApi &&
|
|
442
|
-
this.#serverless.service.provider.httpApi.cors
|
|
443
|
-
) {
|
|
444
|
-
const httpApiCors = getHttpApiCorsConfig(
|
|
445
|
-
this.#serverless.service.provider.httpApi.cors,
|
|
446
|
-
this,
|
|
447
|
-
)
|
|
448
|
-
cors = {
|
|
449
|
-
credentials: httpApiCors.allowCredentials,
|
|
450
|
-
exposedHeaders: httpApiCors.exposedResponseHeaders || [],
|
|
451
|
-
headers: httpApiCors.allowedHeaders || [],
|
|
452
|
-
maxAge: httpApiCors.maxAge,
|
|
453
|
-
origin: httpApiCors.allowedOrigins || [],
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const hapiMethod = method === 'ANY' ? '*' : method
|
|
458
|
-
|
|
459
|
-
const state = this.#options.disableCookieValidation
|
|
460
|
-
? {
|
|
461
|
-
failAction: 'ignore',
|
|
462
|
-
parse: false,
|
|
463
|
-
}
|
|
464
|
-
: {
|
|
465
|
-
failAction: 'error',
|
|
466
|
-
parse: true,
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
const hapiOptions = {
|
|
470
|
-
auth: authStrategyName,
|
|
471
|
-
cors,
|
|
472
|
-
state,
|
|
473
|
-
timeout: { socket: false },
|
|
474
|
-
}
|
|
381
|
+
protectedRoute,
|
|
382
|
+
stage,
|
|
383
|
+
} = params
|
|
475
384
|
|
|
476
|
-
|
|
477
|
-
// for more details, check https://github.com/dherault/serverless-offline/issues/204
|
|
478
|
-
if (hapiMethod === 'HEAD') {
|
|
479
|
-
log.notice(
|
|
480
|
-
'HEAD method event detected. Skipping HAPI server route mapping',
|
|
481
|
-
)
|
|
482
|
-
|
|
483
|
-
return
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
if (hapiMethod !== 'HEAD' && hapiMethod !== 'GET') {
|
|
487
|
-
// maxBytes: Increase request size from 1MB default limit to 10MB.
|
|
488
|
-
// Cf AWS API GW payload limits.
|
|
489
|
-
hapiOptions.payload = {
|
|
490
|
-
maxBytes: 1024 * 1024 * 10,
|
|
491
|
-
parse: false,
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
const additionalRequestContext = {}
|
|
496
|
-
if (httpEvent.operationId) {
|
|
497
|
-
additionalRequestContext.operationName = httpEvent.operationId
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
hapiOptions.tags = ['api']
|
|
501
|
-
|
|
502
|
-
const hapiHandler = async (request, h) => {
|
|
385
|
+
return async (request, h) => {
|
|
503
386
|
const requestPath =
|
|
504
387
|
endpoint.isHttpApi || this.#options.noPrependStageInUrl
|
|
505
388
|
? request.path
|
|
506
389
|
: request.path.substr(`/${stage}`.length)
|
|
507
390
|
|
|
508
|
-
//
|
|
391
|
+
// payload processing
|
|
509
392
|
const encoding = detectEncoding(request)
|
|
510
393
|
|
|
511
394
|
request.payload = request.payload && request.payload.toString(encoding)
|
|
512
395
|
request.rawPayload = request.payload
|
|
513
396
|
|
|
514
|
-
//
|
|
397
|
+
// incomming request message
|
|
515
398
|
log.notice()
|
|
516
399
|
|
|
517
400
|
log.notice()
|
|
518
401
|
log.notice(`${method} ${request.path} (λ: ${functionKey})`)
|
|
519
402
|
|
|
520
|
-
//
|
|
403
|
+
// check for APIKey
|
|
521
404
|
if (
|
|
522
|
-
(
|
|
523
|
-
|
|
405
|
+
(protectedRoute === `${hapiMethod}#${hapiPath}` ||
|
|
406
|
+
protectedRoute === `ANY#${hapiPath}`) &&
|
|
524
407
|
!this.#options.noAuth
|
|
525
408
|
) {
|
|
526
409
|
const errorResponse = () =>
|
|
@@ -641,24 +524,18 @@ export default class HttpServer {
|
|
|
641
524
|
event = request.payload || {}
|
|
642
525
|
}
|
|
643
526
|
} else if (integration === 'AWS_PROXY') {
|
|
644
|
-
const stageVariables = this.#serverless.service.custom
|
|
645
|
-
? this.#serverless.service.custom.stageVariables
|
|
646
|
-
: null
|
|
647
|
-
|
|
648
527
|
const lambdaProxyIntegrationEvent =
|
|
649
528
|
endpoint.isHttpApi && endpoint.payload === '2.0'
|
|
650
529
|
? new LambdaProxyIntegrationEventV2(
|
|
651
530
|
request,
|
|
652
531
|
stage,
|
|
653
532
|
endpoint.routeKey,
|
|
654
|
-
stageVariables,
|
|
655
533
|
additionalRequestContext,
|
|
656
534
|
)
|
|
657
535
|
: new LambdaProxyIntegrationEvent(
|
|
658
536
|
request,
|
|
659
537
|
stage,
|
|
660
538
|
requestPath,
|
|
661
|
-
stageVariables,
|
|
662
539
|
endpoint.isHttpApi ? endpoint.routeKey : null,
|
|
663
540
|
additionalRequestContext,
|
|
664
541
|
)
|
|
@@ -710,8 +587,7 @@ export default class HttpServer {
|
|
|
710
587
|
|
|
711
588
|
const errorMessage = (err.message || err).toString()
|
|
712
589
|
|
|
713
|
-
const
|
|
714
|
-
const found = errorMessage.match(re)
|
|
590
|
+
const found = errorMessage.match(/\[(\d{3})]/)
|
|
715
591
|
|
|
716
592
|
if (found && found.length > 1) {
|
|
717
593
|
;[, errorStatusCode] = found
|
|
@@ -862,7 +738,9 @@ export default class HttpServer {
|
|
|
862
738
|
).getContext()
|
|
863
739
|
|
|
864
740
|
result = renderVelocityTemplateObject(
|
|
865
|
-
{
|
|
741
|
+
{
|
|
742
|
+
root: responseTemplate,
|
|
743
|
+
},
|
|
866
744
|
reponseContext,
|
|
867
745
|
).root
|
|
868
746
|
} catch (error) {
|
|
@@ -971,7 +849,9 @@ export default class HttpServer {
|
|
|
971
849
|
headerValue.forEach((value) => {
|
|
972
850
|
// it looks like Hapi doesn't support multiple headers with the same name,
|
|
973
851
|
// appending values is the closest we can come to the AWS behavior.
|
|
974
|
-
response.header(headerKey, value, {
|
|
852
|
+
response.header(headerKey, value, {
|
|
853
|
+
append: true,
|
|
854
|
+
})
|
|
975
855
|
})
|
|
976
856
|
}
|
|
977
857
|
})
|
|
@@ -1009,7 +889,6 @@ export default class HttpServer {
|
|
|
1009
889
|
}
|
|
1010
890
|
}
|
|
1011
891
|
|
|
1012
|
-
// Log response
|
|
1013
892
|
let whatToLog = result
|
|
1014
893
|
|
|
1015
894
|
try {
|
|
@@ -1024,9 +903,152 @@ export default class HttpServer {
|
|
|
1024
903
|
}
|
|
1025
904
|
}
|
|
1026
905
|
|
|
1027
|
-
// Bon voyage!
|
|
1028
906
|
return response
|
|
1029
907
|
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
createRoutes(functionKey, httpEvent, handler) {
|
|
911
|
+
const [handlerPath] = splitHandlerPathAndName(handler)
|
|
912
|
+
|
|
913
|
+
let method
|
|
914
|
+
let path
|
|
915
|
+
let hapiPath
|
|
916
|
+
|
|
917
|
+
if (httpEvent.isHttpApi) {
|
|
918
|
+
if (httpEvent.routeKey === '$default') {
|
|
919
|
+
method = 'ANY'
|
|
920
|
+
path = httpEvent.routeKey
|
|
921
|
+
hapiPath = '/{default*}'
|
|
922
|
+
} else {
|
|
923
|
+
;[method, path] = httpEvent.routeKey.split(' ')
|
|
924
|
+
hapiPath = generateHapiPath(
|
|
925
|
+
path,
|
|
926
|
+
{
|
|
927
|
+
...this.#options,
|
|
928
|
+
noPrependStageInUrl: true, // Serverless always uses the $default stage
|
|
929
|
+
},
|
|
930
|
+
this.#serverless,
|
|
931
|
+
)
|
|
932
|
+
}
|
|
933
|
+
} else {
|
|
934
|
+
method = httpEvent.method.toUpperCase()
|
|
935
|
+
;({ path } = httpEvent)
|
|
936
|
+
hapiPath = generateHapiPath(path, this.#options, this.#serverless)
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
const endpoint = new Endpoint(
|
|
940
|
+
join(this.#serverless.config.servicePath, handlerPath),
|
|
941
|
+
httpEvent,
|
|
942
|
+
).generate()
|
|
943
|
+
|
|
944
|
+
const stage = endpoint.isHttpApi
|
|
945
|
+
? '$default'
|
|
946
|
+
: this.#options.stage || this.#serverless.service.provider.stage
|
|
947
|
+
|
|
948
|
+
const protectedRoute = httpEvent.private
|
|
949
|
+
? `${method}#${hapiPath}`
|
|
950
|
+
: undefined
|
|
951
|
+
|
|
952
|
+
const { host, httpPort, httpsProtocol } = this.#options
|
|
953
|
+
const server = `${httpsProtocol ? 'https' : 'http'}://${host}:${httpPort}`
|
|
954
|
+
|
|
955
|
+
this.#terminalInfo.push({
|
|
956
|
+
invokePath: `/2015-03-31/functions/${functionKey}/invocations`,
|
|
957
|
+
method,
|
|
958
|
+
path: hapiPath,
|
|
959
|
+
server,
|
|
960
|
+
stage:
|
|
961
|
+
endpoint.isHttpApi || this.#options.noPrependStageInUrl ? null : stage,
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
const authStrategyName = this.#setAuthorizationStrategy(
|
|
965
|
+
endpoint,
|
|
966
|
+
functionKey,
|
|
967
|
+
method,
|
|
968
|
+
path,
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
let cors = null
|
|
972
|
+
if (endpoint.cors) {
|
|
973
|
+
cors = {
|
|
974
|
+
credentials:
|
|
975
|
+
endpoint.cors.credentials || this.#options.corsConfig.credentials,
|
|
976
|
+
exposedHeaders: this.#options.corsConfig.exposedHeaders,
|
|
977
|
+
headers: endpoint.cors.headers || this.#options.corsConfig.headers,
|
|
978
|
+
origin: endpoint.cors.origins || this.#options.corsConfig.origin,
|
|
979
|
+
}
|
|
980
|
+
} else if (
|
|
981
|
+
this.#serverless.service.provider.httpApi &&
|
|
982
|
+
this.#serverless.service.provider.httpApi.cors
|
|
983
|
+
) {
|
|
984
|
+
const httpApiCors = getHttpApiCorsConfig(
|
|
985
|
+
this.#serverless.service.provider.httpApi.cors,
|
|
986
|
+
this,
|
|
987
|
+
)
|
|
988
|
+
cors = {
|
|
989
|
+
credentials: httpApiCors.allowCredentials,
|
|
990
|
+
exposedHeaders: httpApiCors.exposedResponseHeaders || [],
|
|
991
|
+
headers: httpApiCors.allowedHeaders || [],
|
|
992
|
+
maxAge: httpApiCors.maxAge,
|
|
993
|
+
origin: httpApiCors.allowedOrigins || [],
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const hapiMethod = method === 'ANY' ? '*' : method
|
|
998
|
+
|
|
999
|
+
const state = this.#options.disableCookieValidation
|
|
1000
|
+
? {
|
|
1001
|
+
failAction: 'ignore',
|
|
1002
|
+
parse: false,
|
|
1003
|
+
}
|
|
1004
|
+
: {
|
|
1005
|
+
failAction: 'error',
|
|
1006
|
+
parse: true,
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const hapiOptions = {
|
|
1010
|
+
auth: authStrategyName,
|
|
1011
|
+
cors,
|
|
1012
|
+
state,
|
|
1013
|
+
timeout: { socket: false },
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// skip HEAD routes as hapi will fail with 'Method name not allowed: HEAD ...'
|
|
1017
|
+
// for more details, check https://github.com/dherault/serverless-offline/issues/204
|
|
1018
|
+
if (hapiMethod === 'HEAD') {
|
|
1019
|
+
log.notice(
|
|
1020
|
+
'HEAD method event detected. Skipping HAPI server route mapping',
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
return
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (hapiMethod !== 'HEAD' && hapiMethod !== 'GET') {
|
|
1027
|
+
// maxBytes: Increase request size from 1MB default limit to 10MB.
|
|
1028
|
+
// Cf AWS API GW payload limits.
|
|
1029
|
+
hapiOptions.payload = {
|
|
1030
|
+
maxBytes: 1024 * 1024 * 10,
|
|
1031
|
+
parse: false,
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const additionalRequestContext = {}
|
|
1036
|
+
if (httpEvent.operationId) {
|
|
1037
|
+
additionalRequestContext.operationName = httpEvent.operationId
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
hapiOptions.tags = ['api']
|
|
1041
|
+
|
|
1042
|
+
const hapiHandler = this.#createHapiHandler({
|
|
1043
|
+
additionalRequestContext,
|
|
1044
|
+
endpoint,
|
|
1045
|
+
functionKey,
|
|
1046
|
+
hapiMethod,
|
|
1047
|
+
hapiPath,
|
|
1048
|
+
method,
|
|
1049
|
+
protectedRoute,
|
|
1050
|
+
stage,
|
|
1051
|
+
})
|
|
1030
1052
|
|
|
1031
1053
|
this.#server.route({
|
|
1032
1054
|
handler: hapiHandler,
|
|
@@ -30,22 +30,12 @@ export default class LambdaProxyIntegrationEvent {
|
|
|
30
30
|
|
|
31
31
|
#stage = null
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
constructor(
|
|
36
|
-
request,
|
|
37
|
-
stage,
|
|
38
|
-
path,
|
|
39
|
-
stageVariables,
|
|
40
|
-
routeKey,
|
|
41
|
-
additionalRequestContext,
|
|
42
|
-
) {
|
|
33
|
+
constructor(request, stage, path, routeKey, additionalRequestContext) {
|
|
43
34
|
this.#additionalRequestContext = additionalRequestContext || {}
|
|
44
35
|
this.#path = path
|
|
45
36
|
this.#routeKey = routeKey
|
|
46
37
|
this.#request = request
|
|
47
38
|
this.#stage = stage
|
|
48
|
-
this.#stageVariables = stageVariables
|
|
49
39
|
}
|
|
50
40
|
|
|
51
41
|
create() {
|
|
@@ -227,7 +217,7 @@ export default class LambdaProxyIntegrationEvent {
|
|
|
227
217
|
stage: this.#stage,
|
|
228
218
|
},
|
|
229
219
|
resource,
|
|
230
|
-
stageVariables:
|
|
220
|
+
stageVariables: null,
|
|
231
221
|
}
|
|
232
222
|
}
|
|
233
223
|
}
|
|
@@ -24,20 +24,11 @@ export default class LambdaProxyIntegrationEventV2 {
|
|
|
24
24
|
|
|
25
25
|
#stage = null
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
constructor(
|
|
30
|
-
request,
|
|
31
|
-
stage,
|
|
32
|
-
routeKey,
|
|
33
|
-
stageVariables,
|
|
34
|
-
additionalRequestContext,
|
|
35
|
-
) {
|
|
27
|
+
constructor(request, stage, routeKey, additionalRequestContext) {
|
|
36
28
|
this.#additionalRequestContext = additionalRequestContext || {}
|
|
37
29
|
this.#routeKey = routeKey
|
|
38
30
|
this.#request = request
|
|
39
31
|
this.#stage = stage
|
|
40
|
-
this.#stageVariables = stageVariables
|
|
41
32
|
}
|
|
42
33
|
|
|
43
34
|
create() {
|
|
@@ -183,7 +174,7 @@ export default class LambdaProxyIntegrationEventV2 {
|
|
|
183
174
|
timeEpoch: requestTimeEpoch,
|
|
184
175
|
},
|
|
185
176
|
routeKey: this.#routeKey,
|
|
186
|
-
stageVariables:
|
|
177
|
+
stageVariables: null,
|
|
187
178
|
version: '2.0',
|
|
188
179
|
}
|
|
189
180
|
}
|
|
@@ -39,9 +39,9 @@ export default class Schedule {
|
|
|
39
39
|
const cron = this.#convertExpressionToCron(entry)
|
|
40
40
|
|
|
41
41
|
log.notice(
|
|
42
|
-
`Scheduling [${functionKey}] cron: [${cron}]
|
|
43
|
-
input
|
|
44
|
-
|
|
42
|
+
`Scheduling [${functionKey}] cron: [${cron}]${
|
|
43
|
+
input ? ` input: ${stringify(input)}` : ''
|
|
44
|
+
}`,
|
|
45
45
|
)
|
|
46
46
|
|
|
47
47
|
nodeSchedule.scheduleJob(cron, async () => {
|
|
@@ -7,9 +7,9 @@ import { execa } from 'execa'
|
|
|
7
7
|
|
|
8
8
|
const { parse, stringify } = JSON
|
|
9
9
|
|
|
10
|
-
const PAYLOAD_IDENTIFIER = 'offline_payload'
|
|
11
|
-
|
|
12
10
|
export default class GoRunner {
|
|
11
|
+
static #payloadIdentifier = 'offline_payload'
|
|
12
|
+
|
|
13
13
|
#codeDir = null
|
|
14
14
|
|
|
15
15
|
#env = null
|
|
@@ -51,12 +51,10 @@ export default class GoRunner {
|
|
|
51
51
|
let payload
|
|
52
52
|
|
|
53
53
|
for (const item of value.split(EOL)) {
|
|
54
|
-
if (item.
|
|
55
|
-
logs.push(item)
|
|
56
|
-
} else if (item.indexOf(PAYLOAD_IDENTIFIER) !== -1) {
|
|
54
|
+
if (item.includes(GoRunner.#payloadIdentifier)) {
|
|
57
55
|
try {
|
|
58
56
|
const {
|
|
59
|
-
|
|
57
|
+
[GoRunner.#payloadIdentifier]: { error, success },
|
|
60
58
|
} = parse(item)
|
|
61
59
|
|
|
62
60
|
if (success) {
|
|
@@ -67,6 +65,8 @@ export default class GoRunner {
|
|
|
67
65
|
} catch {
|
|
68
66
|
// @ignore
|
|
69
67
|
}
|
|
68
|
+
} else {
|
|
69
|
+
logs.push(item)
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -50,7 +50,6 @@ export default class InProcessRunner {
|
|
|
50
50
|
let handler
|
|
51
51
|
|
|
52
52
|
try {
|
|
53
|
-
// const { [this.#handlerName]: handler } = await import(this.#handlerPath)
|
|
54
53
|
// eslint-disable-next-line import/no-dynamic-require
|
|
55
54
|
;({ [this.#handlerName]: handler } = require(this.#handlerPath))
|
|
56
55
|
} catch (err) {
|
|
@@ -87,15 +86,21 @@ export default class InProcessRunner {
|
|
|
87
86
|
// create new immutable object
|
|
88
87
|
const lambdaContext = {
|
|
89
88
|
...context,
|
|
90
|
-
done
|
|
91
|
-
|
|
89
|
+
done(err, data) {
|
|
90
|
+
callback(err, data)
|
|
91
|
+
},
|
|
92
|
+
fail(err) {
|
|
93
|
+
callback(err)
|
|
94
|
+
},
|
|
92
95
|
getRemainingTimeInMillis() {
|
|
93
96
|
const timeLeft = executionTimeout - performance.now()
|
|
94
97
|
|
|
95
98
|
// just return 0 for now if we are beyond alotted time (timeout)
|
|
96
99
|
return timeLeft > 0 ? floor(timeLeft) : 0
|
|
97
100
|
},
|
|
98
|
-
succeed
|
|
101
|
+
succeed(res) {
|
|
102
|
+
callback(null, res)
|
|
103
|
+
},
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
let result
|
|
@@ -4,9 +4,11 @@ import { log } from '@serverless/utils/log.js'
|
|
|
4
4
|
import { invokeJavaLocal } from 'java-invoke-local'
|
|
5
5
|
|
|
6
6
|
const { parse, stringify } = JSON
|
|
7
|
-
const {
|
|
7
|
+
const { hasOwn } = Object
|
|
8
8
|
|
|
9
9
|
export default class JavaRunner {
|
|
10
|
+
static #payloadIdentifier = '__offline_payload__'
|
|
11
|
+
|
|
10
12
|
#deployPackage = null
|
|
11
13
|
|
|
12
14
|
#env = null
|
|
@@ -45,9 +47,9 @@ export default class JavaRunner {
|
|
|
45
47
|
if (
|
|
46
48
|
json &&
|
|
47
49
|
typeof json === 'object' &&
|
|
48
|
-
|
|
50
|
+
hasOwn(json, JavaRunner.#payloadIdentifier)
|
|
49
51
|
) {
|
|
50
|
-
return json
|
|
52
|
+
return json[JavaRunner.#payloadIdentifier]
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
|
|
@@ -7,12 +7,13 @@ import { fileURLToPath } from 'node:url'
|
|
|
7
7
|
import { log } from '@serverless/utils/log.js'
|
|
8
8
|
|
|
9
9
|
const { parse, stringify } = JSON
|
|
10
|
-
const { assign } = Object
|
|
11
|
-
const { has } = Reflect
|
|
10
|
+
const { assign, hasOwn } = Object
|
|
12
11
|
|
|
13
12
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
14
13
|
|
|
15
14
|
export default class PythonRunner {
|
|
15
|
+
static #payloadIdentifier = '__offline_payload__'
|
|
16
|
+
|
|
16
17
|
#env = null
|
|
17
18
|
|
|
18
19
|
#handlerName = null
|
|
@@ -83,9 +84,9 @@ export default class PythonRunner {
|
|
|
83
84
|
if (
|
|
84
85
|
json &&
|
|
85
86
|
typeof json === 'object' &&
|
|
86
|
-
|
|
87
|
+
hasOwn(json, PythonRunner.#payloadIdentifier)
|
|
87
88
|
) {
|
|
88
|
-
payload = json
|
|
89
|
+
payload = json[PythonRunner.#payloadIdentifier]
|
|
89
90
|
// everything else is print(), logging, ...
|
|
90
91
|
} else {
|
|
91
92
|
log.notice(item)
|
|
@@ -6,11 +6,13 @@ import { log } from '@serverless/utils/log.js'
|
|
|
6
6
|
import { execa } from 'execa'
|
|
7
7
|
|
|
8
8
|
const { parse, stringify } = JSON
|
|
9
|
-
const {
|
|
9
|
+
const { hasOwn } = Object
|
|
10
10
|
|
|
11
11
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
12
12
|
|
|
13
13
|
export default class RubyRunner {
|
|
14
|
+
static #payloadIdentifier = '__offline_payload__'
|
|
15
|
+
|
|
14
16
|
#env = null
|
|
15
17
|
|
|
16
18
|
#handlerName = null
|
|
@@ -47,9 +49,9 @@ export default class RubyRunner {
|
|
|
47
49
|
if (
|
|
48
50
|
json &&
|
|
49
51
|
typeof json === 'object' &&
|
|
50
|
-
|
|
52
|
+
hasOwn(json, RubyRunner.#payloadIdentifier)
|
|
51
53
|
) {
|
|
52
|
-
payload = json
|
|
54
|
+
payload = json[RubyRunner.#payloadIdentifier]
|
|
53
55
|
} else {
|
|
54
56
|
log.notice(item)
|
|
55
57
|
}
|