serverless-offline 13.3.1 → 13.3.3
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 +17 -17
- package/package.json +14 -13
- package/src/ServerlessOffline.js +42 -42
- package/src/config/colors.js +9 -9
- package/src/config/commandOptions.js +61 -61
- package/src/config/constants.js +5 -5
- package/src/config/defaultOptions.js +6 -6
- package/src/config/index.js +4 -4
- package/src/config/supportedRuntimes.js +18 -18
- package/src/errors/index.js +1 -1
- package/src/events/alb/Alb.js +2 -2
- package/src/events/alb/AlbEventDefinition.js +2 -2
- package/src/events/alb/HttpServer.js +54 -54
- package/src/events/alb/index.js +1 -1
- package/src/events/alb/lambda-events/LambdaAlbRequestEvent.js +2 -2
- package/src/events/alb/lambda-events/index.js +1 -1
- package/src/events/authCanExecuteResource.js +3 -3
- package/src/events/authFunctionNameExtractor.js +9 -9
- package/src/events/authMatchPolicyResource.js +8 -8
- package/src/events/authValidateContext.js +11 -11
- package/src/events/http/Endpoint.js +26 -26
- package/src/events/http/Http.js +2 -2
- package/src/events/http/HttpEventDefinition.js +2 -2
- package/src/events/http/HttpServer.js +156 -156
- package/src/events/http/OfflineEndpoint.js +7 -7
- package/src/events/http/authJWTSettingsExtractor.js +2 -2
- package/src/events/http/createAuthScheme.js +29 -27
- package/src/events/http/createJWTAuthScheme.js +12 -12
- package/src/events/http/index.js +1 -1
- package/src/events/http/javaHelpers.js +2 -2
- package/src/events/http/lambda-events/LambdaIntegrationEvent.js +5 -5
- package/src/events/http/lambda-events/LambdaProxyIntegrationEvent.js +47 -47
- package/src/events/http/lambda-events/LambdaProxyIntegrationEventV2.js +27 -27
- package/src/events/http/lambda-events/VelocityContext.js +28 -32
- package/src/events/http/lambda-events/index.js +4 -4
- package/src/events/http/lambda-events/renderVelocityTemplateObject.js +13 -13
- package/src/events/http/parseResources.js +6 -6
- package/src/events/http/payloadSchemaValidator.js +2 -2
- package/src/events/schedule/Schedule.js +21 -21
- package/src/events/schedule/ScheduleEvent.js +7 -7
- package/src/events/schedule/ScheduleEventDefinition.js +1 -1
- package/src/events/schedule/index.js +1 -1
- package/src/events/websocket/HttpServer.js +9 -9
- package/src/events/websocket/WebSocket.js +4 -4
- package/src/events/websocket/WebSocketClients.js +29 -29
- package/src/events/websocket/WebSocketEventDefinition.js +1 -1
- package/src/events/websocket/WebSocketServer.js +9 -9
- package/src/events/websocket/http-routes/_catchAll/catchAllRoute.js +3 -3
- package/src/events/websocket/http-routes/_catchAll/index.js +1 -1
- package/src/events/websocket/http-routes/connections/ConnectionsController.js +1 -1
- package/src/events/websocket/http-routes/connections/connectionsRoutes.js +6 -6
- package/src/events/websocket/http-routes/connections/index.js +1 -1
- package/src/events/websocket/http-routes/index.js +2 -2
- package/src/events/websocket/index.js +1 -1
- package/src/events/websocket/lambda-events/WebSocketAuthorizerEvent.js +5 -5
- package/src/events/websocket/lambda-events/WebSocketConnectEvent.js +7 -6
- package/src/events/websocket/lambda-events/WebSocketDisconnectEvent.js +5 -5
- package/src/events/websocket/lambda-events/WebSocketEvent.js +2 -2
- package/src/events/websocket/lambda-events/WebSocketRequestContext.js +12 -11
- package/src/events/websocket/lambda-events/index.js +4 -4
- package/src/index.js +1 -1
- package/src/lambda/HttpServer.js +11 -11
- package/src/lambda/Lambda.js +2 -2
- package/src/lambda/LambdaContext.js +1 -1
- package/src/lambda/LambdaFunction.js +34 -34
- package/src/lambda/LambdaFunctionPool.js +3 -3
- package/src/lambda/handler-runner/HandlerRunner.js +12 -12
- package/src/lambda/handler-runner/docker-runner/DockerContainer.js +59 -59
- package/src/lambda/handler-runner/docker-runner/DockerImage.js +6 -6
- package/src/lambda/handler-runner/docker-runner/DockerRunner.js +2 -2
- package/src/lambda/handler-runner/docker-runner/index.js +1 -1
- package/src/lambda/handler-runner/go-runner/GoRunner.js +34 -34
- package/src/lambda/handler-runner/go-runner/index.js +1 -1
- package/src/lambda/handler-runner/in-process-runner/InProcessRunner.js +7 -7
- package/src/lambda/handler-runner/in-process-runner/aws-lambda-ric/UserFunction.js +52 -52
- package/src/lambda/handler-runner/in-process-runner/index.js +1 -1
- package/src/lambda/handler-runner/index.js +1 -1
- package/src/lambda/handler-runner/java-runner/JavaRunner.js +13 -13
- package/src/lambda/handler-runner/java-runner/index.js +1 -1
- package/src/lambda/handler-runner/python-runner/PythonRunner.js +21 -21
- package/src/lambda/handler-runner/python-runner/index.js +1 -1
- package/src/lambda/handler-runner/ruby-runner/RubyRunner.js +11 -11
- package/src/lambda/handler-runner/ruby-runner/index.js +1 -1
- package/src/lambda/handler-runner/worker-thread-runner/WorkerThreadRunner.js +6 -6
- package/src/lambda/handler-runner/worker-thread-runner/index.js +1 -1
- package/src/lambda/handler-runner/worker-thread-runner/workerThreadHelper.js +4 -4
- package/src/lambda/index.js +1 -1
- package/src/lambda/routes/index.js +2 -2
- package/src/lambda/routes/invocations/InvocationsController.js +11 -11
- package/src/lambda/routes/invocations/index.js +1 -1
- package/src/lambda/routes/invocations/invocationsRoute.js +15 -15
- package/src/lambda/routes/invoke-async/index.js +1 -1
- package/src/lambda/routes/invoke-async/invokeAsyncRoute.js +6 -6
- package/src/utils/checkDockerDaemon.js +8 -8
- package/src/utils/checkGoVersion.js +3 -3
- package/src/utils/createApiKey.js +2 -2
- package/src/utils/detectExecutable.js +2 -2
- package/src/utils/formatToClfTime.js +2 -2
- package/src/utils/generateHapiPath.js +8 -8
- package/src/utils/getApiKeysValues.js +10 -2
- package/src/utils/getHttpApiCorsConfig.js +11 -11
- package/src/utils/getRawQueryParams.js +2 -2
- package/src/utils/index.js +25 -26
- package/src/utils/jsonPath.js +1 -1
- package/src/utils/logRoutes.js +13 -13
- package/src/utils/parseHeaders.js +1 -1
- package/src/utils/parseMultiValueHeaders.js +1 -1
- package/src/utils/parseMultiValueQueryStringParameters.js +1 -1
- package/src/utils/parseQueryStringParameters.js +1 -1
- package/src/utils/parseQueryStringParametersForPayloadV2.js +1 -1
- package/src/utils/splitHandlerPathAndName.js +3 -3
- package/src/utils/createUniqueId.js +0 -5
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import { Buffer } from
|
|
2
|
-
import { readFile } from
|
|
3
|
-
import { createRequire } from
|
|
4
|
-
import { join, resolve } from
|
|
5
|
-
import { exit } from
|
|
6
|
-
import h2o2 from
|
|
7
|
-
import { Server } from
|
|
8
|
-
import { log } from
|
|
9
|
-
import authFunctionNameExtractor from
|
|
10
|
-
import authJWTSettingsExtractor from
|
|
11
|
-
import createAuthScheme from
|
|
12
|
-
import createJWTAuthScheme from
|
|
13
|
-
import Endpoint from
|
|
1
|
+
import { Buffer } from "node:buffer"
|
|
2
|
+
import { readFile } from "node:fs/promises"
|
|
3
|
+
import { createRequire } from "node:module"
|
|
4
|
+
import { join, resolve } from "node:path"
|
|
5
|
+
import { exit } from "node:process"
|
|
6
|
+
import h2o2 from "@hapi/h2o2"
|
|
7
|
+
import { Server } from "@hapi/hapi"
|
|
8
|
+
import { log } from "@serverless/utils/log.js"
|
|
9
|
+
import authFunctionNameExtractor from "../authFunctionNameExtractor.js"
|
|
10
|
+
import authJWTSettingsExtractor from "./authJWTSettingsExtractor.js"
|
|
11
|
+
import createAuthScheme from "./createAuthScheme.js"
|
|
12
|
+
import createJWTAuthScheme from "./createJWTAuthScheme.js"
|
|
13
|
+
import Endpoint from "./Endpoint.js"
|
|
14
14
|
import {
|
|
15
15
|
LambdaIntegrationEvent,
|
|
16
16
|
LambdaProxyIntegrationEvent,
|
|
17
17
|
renderVelocityTemplateObject,
|
|
18
18
|
VelocityContext,
|
|
19
|
-
} from
|
|
20
|
-
import LambdaProxyIntegrationEventV2 from
|
|
21
|
-
import parseResources from
|
|
22
|
-
import payloadSchemaValidator from
|
|
23
|
-
import logRoutes from
|
|
19
|
+
} from "./lambda-events/index.js"
|
|
20
|
+
import LambdaProxyIntegrationEventV2 from "./lambda-events/LambdaProxyIntegrationEventV2.js"
|
|
21
|
+
import parseResources from "./parseResources.js"
|
|
22
|
+
import payloadSchemaValidator from "./payloadSchemaValidator.js"
|
|
23
|
+
import logRoutes from "../../utils/logRoutes.js"
|
|
24
24
|
import {
|
|
25
25
|
createApiKey,
|
|
26
26
|
detectEncoding,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
getHttpApiCorsConfig,
|
|
30
30
|
jsonPath,
|
|
31
31
|
splitHandlerPathAndName,
|
|
32
|
-
} from
|
|
32
|
+
} from "../../utils/index.js"
|
|
33
33
|
|
|
34
34
|
const { parse, stringify } = JSON
|
|
35
35
|
const { assign, entries, keys } = Object
|
|
@@ -57,8 +57,8 @@ export default class HttpServer {
|
|
|
57
57
|
|
|
58
58
|
async #loadCerts(httpsProtocol) {
|
|
59
59
|
const [cert, key] = await Promise.all([
|
|
60
|
-
readFile(resolve(httpsProtocol,
|
|
61
|
-
readFile(resolve(httpsProtocol,
|
|
60
|
+
readFile(resolve(httpsProtocol, "cert.pem"), "utf8"),
|
|
61
|
+
readFile(resolve(httpsProtocol, "key.pem"), "utf8"),
|
|
62
62
|
])
|
|
63
63
|
|
|
64
64
|
return {
|
|
@@ -104,7 +104,7 @@ export default class HttpServer {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
// Enable CORS preflight response
|
|
107
|
-
this.#server.ext(
|
|
107
|
+
this.#server.ext("onPreResponse", (request, h) => {
|
|
108
108
|
if (request.headers.origin) {
|
|
109
109
|
const response = request.response.isBoom
|
|
110
110
|
? request.response.output
|
|
@@ -123,11 +123,11 @@ export default class HttpServer {
|
|
|
123
123
|
this,
|
|
124
124
|
)
|
|
125
125
|
|
|
126
|
-
if (request.method ===
|
|
126
|
+
if (request.method === "options") {
|
|
127
127
|
response.statusCode = 204
|
|
128
128
|
const allowAllOrigins =
|
|
129
129
|
httpApiCors.allowedOrigins.length === 1 &&
|
|
130
|
-
httpApiCors.allowedOrigins[0] ===
|
|
130
|
+
httpApiCors.allowedOrigins[0] === "*"
|
|
131
131
|
if (
|
|
132
132
|
!allowAllOrigins &&
|
|
133
133
|
!httpApiCors.allowedOrigins.includes(request.headers.origin)
|
|
@@ -136,47 +136,47 @@ export default class HttpServer {
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
response.headers[
|
|
139
|
+
response.headers["access-control-allow-origin"] =
|
|
140
140
|
request.headers.origin
|
|
141
141
|
if (httpApiCors.allowCredentials) {
|
|
142
|
-
response.headers[
|
|
142
|
+
response.headers["access-control-allow-credentials"] = "true"
|
|
143
143
|
}
|
|
144
144
|
if (httpApiCors.maxAge) {
|
|
145
|
-
response.headers[
|
|
145
|
+
response.headers["access-control-max-age"] = httpApiCors.maxAge
|
|
146
146
|
}
|
|
147
147
|
if (httpApiCors.exposedResponseHeaders) {
|
|
148
|
-
response.headers[
|
|
149
|
-
httpApiCors.exposedResponseHeaders.join(
|
|
148
|
+
response.headers["access-control-expose-headers"] =
|
|
149
|
+
httpApiCors.exposedResponseHeaders.join(",")
|
|
150
150
|
}
|
|
151
151
|
if (httpApiCors.allowedMethods) {
|
|
152
|
-
response.headers[
|
|
153
|
-
httpApiCors.allowedMethods.join(
|
|
152
|
+
response.headers["access-control-allow-methods"] =
|
|
153
|
+
httpApiCors.allowedMethods.join(",")
|
|
154
154
|
}
|
|
155
155
|
if (httpApiCors.allowedHeaders) {
|
|
156
|
-
response.headers[
|
|
157
|
-
httpApiCors.allowedHeaders.join(
|
|
156
|
+
response.headers["access-control-allow-headers"] =
|
|
157
|
+
httpApiCors.allowedHeaders.join(",")
|
|
158
158
|
}
|
|
159
159
|
} else {
|
|
160
|
-
response.headers[
|
|
160
|
+
response.headers["access-control-allow-origin"] =
|
|
161
161
|
request.headers.origin
|
|
162
|
-
response.headers[
|
|
162
|
+
response.headers["access-control-allow-credentials"] = "true"
|
|
163
163
|
|
|
164
|
-
if (request.method ===
|
|
164
|
+
if (request.method === "options") {
|
|
165
165
|
response.statusCode = 200
|
|
166
166
|
|
|
167
|
-
response.headers[
|
|
168
|
-
request.headers[
|
|
169
|
-
|
|
170
|
-
response.headers[
|
|
167
|
+
response.headers["access-control-expose-headers"] =
|
|
168
|
+
request.headers["access-control-expose-headers"] ||
|
|
169
|
+
"content-type, content-length, etag"
|
|
170
|
+
response.headers["access-control-max-age"] = 60 * 10
|
|
171
171
|
|
|
172
|
-
if (request.headers[
|
|
173
|
-
response.headers[
|
|
174
|
-
request.headers[
|
|
172
|
+
if (request.headers["access-control-request-headers"]) {
|
|
173
|
+
response.headers["access-control-allow-headers"] =
|
|
174
|
+
request.headers["access-control-request-headers"]
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
if (request.headers[
|
|
178
|
-
response.headers[
|
|
179
|
-
request.headers[
|
|
177
|
+
if (request.headers["access-control-request-method"]) {
|
|
178
|
+
response.headers["access-control-allow-methods"] =
|
|
179
|
+
request.headers["access-control-request-method"]
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
|
|
@@ -206,7 +206,7 @@ export default class HttpServer {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
// TODO move the following block
|
|
209
|
-
const server = `${httpsProtocol ?
|
|
209
|
+
const server = `${httpsProtocol ? "https" : "http"}://${host}:${httpPort}`
|
|
210
210
|
|
|
211
211
|
log.notice(`Server ready: ${server} 🚀`)
|
|
212
212
|
}
|
|
@@ -220,7 +220,7 @@ export default class HttpServer {
|
|
|
220
220
|
|
|
221
221
|
#logPluginIssue() {
|
|
222
222
|
log.notice(
|
|
223
|
-
|
|
223
|
+
"If you think this is an issue with the plugin please submit it, thanks!\nhttps://github.com/dherault/serverless-offline/issues",
|
|
224
224
|
)
|
|
225
225
|
log.notice()
|
|
226
226
|
}
|
|
@@ -243,7 +243,7 @@ export default class HttpServer {
|
|
|
243
243
|
// right now _configureJWTAuthorization only handles AWS HttpAPI Gateway JWT
|
|
244
244
|
// authorizers that are defined in the serverless file
|
|
245
245
|
if (
|
|
246
|
-
this.#serverless.service.provider.name !==
|
|
246
|
+
this.#serverless.service.provider.name !== "aws" ||
|
|
247
247
|
!endpoint.isHttpApi
|
|
248
248
|
) {
|
|
249
249
|
return null
|
|
@@ -253,8 +253,8 @@ export default class HttpServer {
|
|
|
253
253
|
(endpoint.authorizer.name &&
|
|
254
254
|
this.#serverless.service.provider?.httpApi?.authorizers?.[
|
|
255
255
|
endpoint.authorizer.name
|
|
256
|
-
]?.type ===
|
|
257
|
-
endpoint.authorizer.type ===
|
|
256
|
+
]?.type === "request") ||
|
|
257
|
+
endpoint.authorizer.type === "request"
|
|
258
258
|
) {
|
|
259
259
|
return null
|
|
260
260
|
}
|
|
@@ -321,17 +321,17 @@ export default class HttpServer {
|
|
|
321
321
|
false,
|
|
322
322
|
identitySource: serverlessAuthorizerOptions?.identitySource,
|
|
323
323
|
identityValidationExpression:
|
|
324
|
-
serverlessAuthorizerOptions?.identityValidationExpression ||
|
|
324
|
+
serverlessAuthorizerOptions?.identityValidationExpression || "(.*)",
|
|
325
325
|
payloadVersion: endpoint.isHttpApi
|
|
326
|
-
? serverlessAuthorizerOptions?.payloadVersion ||
|
|
327
|
-
:
|
|
326
|
+
? serverlessAuthorizerOptions?.payloadVersion || "2.0"
|
|
327
|
+
: "1.0",
|
|
328
328
|
resultTtlInSeconds:
|
|
329
|
-
serverlessAuthorizerOptions?.resultTtlInSeconds ||
|
|
329
|
+
serverlessAuthorizerOptions?.resultTtlInSeconds || "300",
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
if (
|
|
333
333
|
authorizerOptions.enableSimpleResponses &&
|
|
334
|
-
authorizerOptions.payloadVersion ===
|
|
334
|
+
authorizerOptions.payloadVersion === "1.0"
|
|
335
335
|
) {
|
|
336
336
|
log.error(
|
|
337
337
|
`Cannot create Authorization function '${authFunctionName}' if payloadVersion is '1.0' and enableSimpleResponses is true`,
|
|
@@ -339,7 +339,7 @@ export default class HttpServer {
|
|
|
339
339
|
return null
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
if (typeof endpoint.authorizer ===
|
|
342
|
+
if (typeof endpoint.authorizer === "string") {
|
|
343
343
|
authorizerOptions.name = authFunctionName
|
|
344
344
|
} else {
|
|
345
345
|
assign(authorizerOptions, endpoint.authorizer)
|
|
@@ -348,11 +348,11 @@ export default class HttpServer {
|
|
|
348
348
|
if (
|
|
349
349
|
!authorizerOptions.identitySource &&
|
|
350
350
|
!(
|
|
351
|
-
authorizerOptions.type ===
|
|
351
|
+
authorizerOptions.type === "request" &&
|
|
352
352
|
authorizerOptions.resultTtlInSeconds === 0
|
|
353
353
|
)
|
|
354
354
|
) {
|
|
355
|
-
authorizerOptions.identitySource =
|
|
355
|
+
authorizerOptions.identitySource = "method.request.header.Authorization"
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
// Create a unique scheme per endpoint
|
|
@@ -391,7 +391,7 @@ export default class HttpServer {
|
|
|
391
391
|
customizations &&
|
|
392
392
|
customizations.offline?.customAuthenticationProvider
|
|
393
393
|
) {
|
|
394
|
-
const root = resolve(this.#serverless.serviceDir,
|
|
394
|
+
const root = resolve(this.#serverless.serviceDir, "require-resolver")
|
|
395
395
|
const customRequire = createRequire(root)
|
|
396
396
|
|
|
397
397
|
const provider = customRequire(
|
|
@@ -457,13 +457,13 @@ export default class HttpServer {
|
|
|
457
457
|
const errorResponse = () =>
|
|
458
458
|
h
|
|
459
459
|
.response({
|
|
460
|
-
message:
|
|
460
|
+
message: "Forbidden",
|
|
461
461
|
})
|
|
462
462
|
.code(403)
|
|
463
|
-
.header(
|
|
464
|
-
.type(
|
|
463
|
+
.header("x-amzn-ErrorType", "ForbiddenException")
|
|
464
|
+
.type("application/json")
|
|
465
465
|
|
|
466
|
-
const apiKey = request.headers[
|
|
466
|
+
const apiKey = request.headers["x-api-key"]
|
|
467
467
|
|
|
468
468
|
if (apiKey) {
|
|
469
469
|
if (!this.#apiKeysValues.has(apiKey)) {
|
|
@@ -495,26 +495,26 @@ export default class HttpServer {
|
|
|
495
495
|
}
|
|
496
496
|
|
|
497
497
|
const response = h.response()
|
|
498
|
-
const contentType = request.mime ||
|
|
498
|
+
const contentType = request.mime || "application/json" // default content type
|
|
499
499
|
|
|
500
500
|
const { integration, requestTemplates } = endpoint
|
|
501
501
|
|
|
502
502
|
// default request template to '' if we don't have a definition pushed in from serverless or endpoint
|
|
503
503
|
const requestTemplate =
|
|
504
|
-
requestTemplates !== undefined && integration ===
|
|
504
|
+
requestTemplates !== undefined && integration === "AWS"
|
|
505
505
|
? requestTemplates[contentType]
|
|
506
|
-
:
|
|
506
|
+
: ""
|
|
507
507
|
|
|
508
508
|
const schemas =
|
|
509
509
|
endpoint?.request?.schemas === undefined
|
|
510
|
-
?
|
|
510
|
+
? ""
|
|
511
511
|
: endpoint.request.schemas[contentType]
|
|
512
512
|
|
|
513
513
|
// https://hapijs.com/api#route-configuration doesn't seem to support selectively parsing
|
|
514
514
|
// so we have to do it ourselves
|
|
515
515
|
const contentTypesThatRequirePayloadParsing = [
|
|
516
|
-
|
|
517
|
-
|
|
516
|
+
"application/json",
|
|
517
|
+
"application/vnd.api+json",
|
|
518
518
|
]
|
|
519
519
|
|
|
520
520
|
if (
|
|
@@ -524,22 +524,22 @@ export default class HttpServer {
|
|
|
524
524
|
) {
|
|
525
525
|
try {
|
|
526
526
|
if (!request.payload || request.payload.length === 0) {
|
|
527
|
-
request.payload =
|
|
527
|
+
request.payload = "{}"
|
|
528
528
|
}
|
|
529
529
|
|
|
530
530
|
request.payload = parse(request.payload)
|
|
531
531
|
} catch (err) {
|
|
532
|
-
log.debug(
|
|
532
|
+
log.debug("error in converting request.payload to JSON:", err)
|
|
533
533
|
}
|
|
534
534
|
}
|
|
535
535
|
|
|
536
|
-
log.debug(
|
|
537
|
-
log.debug(
|
|
538
|
-
log.debug(
|
|
536
|
+
log.debug("contentType:", contentType)
|
|
537
|
+
log.debug("requestTemplate:", requestTemplate)
|
|
538
|
+
log.debug("payload:", request.payload)
|
|
539
539
|
|
|
540
540
|
/* REQUEST PAYLOAD SCHEMA VALIDATION */
|
|
541
541
|
if (schemas) {
|
|
542
|
-
log.debug(
|
|
542
|
+
log.debug("schemas:", schemas)
|
|
543
543
|
|
|
544
544
|
try {
|
|
545
545
|
payloadSchemaValidator(schemas, request.payload)
|
|
@@ -552,10 +552,10 @@ export default class HttpServer {
|
|
|
552
552
|
|
|
553
553
|
let event = {}
|
|
554
554
|
|
|
555
|
-
if (integration ===
|
|
555
|
+
if (integration === "AWS") {
|
|
556
556
|
if (requestTemplate) {
|
|
557
557
|
try {
|
|
558
|
-
log.debug(
|
|
558
|
+
log.debug("_____ REQUEST TEMPLATE PROCESSING _____")
|
|
559
559
|
|
|
560
560
|
event = new LambdaIntegrationEvent(
|
|
561
561
|
request,
|
|
@@ -570,12 +570,12 @@ export default class HttpServer {
|
|
|
570
570
|
err,
|
|
571
571
|
)
|
|
572
572
|
}
|
|
573
|
-
} else if (typeof request.payload ===
|
|
573
|
+
} else if (typeof request.payload === "object") {
|
|
574
574
|
event = request.payload || {}
|
|
575
575
|
}
|
|
576
|
-
} else if (integration ===
|
|
576
|
+
} else if (integration === "AWS_PROXY") {
|
|
577
577
|
const lambdaProxyIntegrationEvent =
|
|
578
|
-
endpoint.isHttpApi && endpoint.payload ===
|
|
578
|
+
endpoint.isHttpApi && endpoint.payload === "2.0"
|
|
579
579
|
? new LambdaProxyIntegrationEventV2(
|
|
580
580
|
request,
|
|
581
581
|
stage,
|
|
@@ -593,7 +593,7 @@ export default class HttpServer {
|
|
|
593
593
|
event = lambdaProxyIntegrationEvent.create()
|
|
594
594
|
}
|
|
595
595
|
|
|
596
|
-
log.debug(
|
|
596
|
+
log.debug("event:", event)
|
|
597
597
|
|
|
598
598
|
const lambdaFunction = this.#lambda.get(functionKey)
|
|
599
599
|
|
|
@@ -611,15 +611,15 @@ export default class HttpServer {
|
|
|
611
611
|
// const processResponse = (err, data) => {
|
|
612
612
|
// Everything in this block happens once the lambda function has resolved
|
|
613
613
|
|
|
614
|
-
log.debug(
|
|
614
|
+
log.debug("_____ HANDLER RESOLVED _____")
|
|
615
615
|
|
|
616
|
-
let responseName =
|
|
616
|
+
let responseName = "default"
|
|
617
617
|
const { contentHandling, responseContentType } = endpoint
|
|
618
618
|
|
|
619
619
|
/* RESPONSE SELECTION (among endpoint's possible responses) */
|
|
620
620
|
|
|
621
621
|
// Failure handling
|
|
622
|
-
let errorStatusCode =
|
|
622
|
+
let errorStatusCode = "502"
|
|
623
623
|
|
|
624
624
|
if (err) {
|
|
625
625
|
const errorMessage = (err.message || err).toString()
|
|
@@ -629,7 +629,7 @@ export default class HttpServer {
|
|
|
629
629
|
if (found && found.length > 1) {
|
|
630
630
|
;[, errorStatusCode] = found
|
|
631
631
|
} else {
|
|
632
|
-
errorStatusCode =
|
|
632
|
+
errorStatusCode = "502"
|
|
633
633
|
}
|
|
634
634
|
|
|
635
635
|
// Mocks Lambda errors
|
|
@@ -643,7 +643,7 @@ export default class HttpServer {
|
|
|
643
643
|
|
|
644
644
|
for (const [key, value] of entries(endpoint.responses)) {
|
|
645
645
|
if (
|
|
646
|
-
key !==
|
|
646
|
+
key !== "default" &&
|
|
647
647
|
`^${value.selectionPattern || key}$`.test(errorMessage)
|
|
648
648
|
) {
|
|
649
649
|
responseName = key
|
|
@@ -661,7 +661,7 @@ export default class HttpServer {
|
|
|
661
661
|
const { responseParameters } = chosenResponse
|
|
662
662
|
|
|
663
663
|
if (responseParameters) {
|
|
664
|
-
log.debug(
|
|
664
|
+
log.debug("_____ RESPONSE PARAMETERS PROCCESSING _____")
|
|
665
665
|
log.debug(
|
|
666
666
|
`Found ${
|
|
667
667
|
keys(responseParameters).length
|
|
@@ -670,27 +670,27 @@ export default class HttpServer {
|
|
|
670
670
|
|
|
671
671
|
// responseParameters use the following shape: "key": "value"
|
|
672
672
|
entries(responseParameters).forEach(([key, value]) => {
|
|
673
|
-
const keyArray = key.split(
|
|
674
|
-
const valueArray = value.split(
|
|
673
|
+
const keyArray = key.split(".") // eg: "method.response.header.location"
|
|
674
|
+
const valueArray = value.split(".") // eg: "integration.response.body.redirect.url"
|
|
675
675
|
|
|
676
676
|
log.debug(`Processing responseParameter "${key}": "${value}"`)
|
|
677
677
|
|
|
678
678
|
// For now the plugin only supports modifying headers
|
|
679
|
-
if (key.startsWith(
|
|
680
|
-
const headerName = keyArray.slice(3).join(
|
|
679
|
+
if (key.startsWith("method.response.header") && keyArray[3]) {
|
|
680
|
+
const headerName = keyArray.slice(3).join(".")
|
|
681
681
|
let headerValue
|
|
682
682
|
|
|
683
|
-
log.debug(
|
|
683
|
+
log.debug("Found header in left-hand:", headerName)
|
|
684
684
|
|
|
685
|
-
if (value.startsWith(
|
|
686
|
-
if (valueArray[2] ===
|
|
687
|
-
log.debug(
|
|
685
|
+
if (value.startsWith("integration.response")) {
|
|
686
|
+
if (valueArray[2] === "body") {
|
|
687
|
+
log.debug("Found body in right-hand")
|
|
688
688
|
|
|
689
689
|
headerValue = valueArray[3]
|
|
690
|
-
? jsonPath(result, valueArray.slice(3).join(
|
|
690
|
+
? jsonPath(result, valueArray.slice(3).join("."))
|
|
691
691
|
: result
|
|
692
692
|
|
|
693
|
-
headerValue = headerValue == null ?
|
|
693
|
+
headerValue = headerValue == null ? "" : String(headerValue)
|
|
694
694
|
} else {
|
|
695
695
|
log.notice()
|
|
696
696
|
|
|
@@ -706,7 +706,7 @@ export default class HttpServer {
|
|
|
706
706
|
headerValue = /^'.*'$/.test(value) ? value.slice(1, -1) : value // See #34
|
|
707
707
|
}
|
|
708
708
|
// Applies the header;
|
|
709
|
-
if (headerValue ===
|
|
709
|
+
if (headerValue === "") {
|
|
710
710
|
log.warning(
|
|
711
711
|
`Empty value for responseParameter "${key}": "${value}", it won't be set`,
|
|
712
712
|
)
|
|
@@ -733,13 +733,13 @@ export default class HttpServer {
|
|
|
733
733
|
|
|
734
734
|
let statusCode = 200
|
|
735
735
|
|
|
736
|
-
if (integration ===
|
|
736
|
+
if (integration === "AWS") {
|
|
737
737
|
const endpointResponseHeaders =
|
|
738
738
|
(endpoint.response && endpoint.response.headers) || {}
|
|
739
739
|
|
|
740
740
|
entries(endpointResponseHeaders)
|
|
741
741
|
.filter(
|
|
742
|
-
([, value]) => typeof value ===
|
|
742
|
+
([, value]) => typeof value === "string" && /^'.*?'$/.test(value),
|
|
743
743
|
)
|
|
744
744
|
.forEach(([key, value]) => response.header(key, value.slice(1, -1)))
|
|
745
745
|
|
|
@@ -749,14 +749,14 @@ export default class HttpServer {
|
|
|
749
749
|
const { responseTemplates } = chosenResponse
|
|
750
750
|
|
|
751
751
|
if (
|
|
752
|
-
typeof responseTemplates ===
|
|
752
|
+
typeof responseTemplates === "object" &&
|
|
753
753
|
keys(responseTemplates).length > 0
|
|
754
754
|
) {
|
|
755
755
|
// BAD IMPLEMENTATION: first key in responseTemplates
|
|
756
756
|
const responseTemplate = responseTemplates[responseContentType]
|
|
757
757
|
|
|
758
|
-
if (responseTemplate && responseTemplate !==
|
|
759
|
-
log.debug(
|
|
758
|
+
if (responseTemplate && responseTemplate !== "\n") {
|
|
759
|
+
log.debug("_____ RESPONSE TEMPLATE PROCCESSING _____")
|
|
760
760
|
log.debug(`Using responseTemplate '${responseContentType}'`)
|
|
761
761
|
|
|
762
762
|
try {
|
|
@@ -794,34 +794,34 @@ export default class HttpServer {
|
|
|
794
794
|
log.warning(`No statusCode found for response "${responseName}".`)
|
|
795
795
|
}
|
|
796
796
|
|
|
797
|
-
response.header(
|
|
797
|
+
response.header("Content-Type", responseContentType, {
|
|
798
798
|
override: false, // Maybe a responseParameter set it already. See #34
|
|
799
799
|
})
|
|
800
800
|
|
|
801
801
|
response.statusCode = statusCode
|
|
802
802
|
|
|
803
|
-
if (contentHandling ===
|
|
804
|
-
response.encoding =
|
|
805
|
-
response.source = Buffer.from(result,
|
|
806
|
-
response.variety =
|
|
807
|
-
} else if (typeof result ===
|
|
803
|
+
if (contentHandling === "CONVERT_TO_BINARY") {
|
|
804
|
+
response.encoding = "binary"
|
|
805
|
+
response.source = Buffer.from(result, "base64")
|
|
806
|
+
response.variety = "buffer"
|
|
807
|
+
} else if (typeof result === "string") {
|
|
808
808
|
response.source = stringify(result)
|
|
809
809
|
} else {
|
|
810
810
|
response.source = result
|
|
811
811
|
}
|
|
812
|
-
} else if (integration ===
|
|
812
|
+
} else if (integration === "AWS_PROXY") {
|
|
813
813
|
/* LAMBDA PROXY INTEGRATION HAPIJS RESPONSE CONFIGURATION */
|
|
814
814
|
|
|
815
815
|
if (
|
|
816
816
|
endpoint.isHttpApi &&
|
|
817
|
-
endpoint.payload ===
|
|
818
|
-
(typeof result ===
|
|
817
|
+
endpoint.payload === "2.0" &&
|
|
818
|
+
(typeof result === "string" || !result.statusCode)
|
|
819
819
|
) {
|
|
820
|
-
const body = typeof result ===
|
|
820
|
+
const body = typeof result === "string" ? result : stringify(result)
|
|
821
821
|
result = {
|
|
822
822
|
body,
|
|
823
823
|
headers: {
|
|
824
|
-
|
|
824
|
+
"Content-Type": "application/json",
|
|
825
825
|
},
|
|
826
826
|
isBase64Encoded: false,
|
|
827
827
|
statusCode: 200,
|
|
@@ -855,20 +855,20 @@ export default class HttpServer {
|
|
|
855
855
|
)
|
|
856
856
|
}
|
|
857
857
|
|
|
858
|
-
log.debug(
|
|
858
|
+
log.debug("headers", headers)
|
|
859
859
|
|
|
860
860
|
const parseCookies = (headerValue) => {
|
|
861
|
-
const cookieName = headerValue.slice(0, headerValue.indexOf(
|
|
862
|
-
const cookieValue = headerValue.slice(headerValue.indexOf(
|
|
861
|
+
const cookieName = headerValue.slice(0, headerValue.indexOf("="))
|
|
862
|
+
const cookieValue = headerValue.slice(headerValue.indexOf("=") + 1)
|
|
863
863
|
|
|
864
864
|
h.state(cookieName, cookieValue, {
|
|
865
|
-
encoding:
|
|
865
|
+
encoding: "none",
|
|
866
866
|
strictHeader: false,
|
|
867
867
|
})
|
|
868
868
|
}
|
|
869
869
|
|
|
870
870
|
entries(headers).forEach(([headerKey, headerValue]) => {
|
|
871
|
-
if (headerKey.toLowerCase() ===
|
|
871
|
+
if (headerKey.toLowerCase() === "set-cookie") {
|
|
872
872
|
headerValue.forEach(parseCookies)
|
|
873
873
|
} else {
|
|
874
874
|
headerValue.forEach((value) => {
|
|
@@ -883,30 +883,30 @@ export default class HttpServer {
|
|
|
883
883
|
|
|
884
884
|
if (
|
|
885
885
|
endpoint.isHttpApi &&
|
|
886
|
-
endpoint.payload ===
|
|
886
|
+
endpoint.payload === "2.0" &&
|
|
887
887
|
result.cookies
|
|
888
888
|
) {
|
|
889
889
|
result.cookies.forEach(parseCookies)
|
|
890
890
|
}
|
|
891
891
|
|
|
892
|
-
response.header(
|
|
892
|
+
response.header("Content-Type", "application/json", {
|
|
893
893
|
duplicate: false,
|
|
894
894
|
override: false,
|
|
895
895
|
})
|
|
896
896
|
|
|
897
|
-
if (typeof result ===
|
|
897
|
+
if (typeof result === "string") {
|
|
898
898
|
response.source = stringify(result)
|
|
899
899
|
} else if (result && result.body !== undefined) {
|
|
900
900
|
if (result.isBase64Encoded) {
|
|
901
|
-
response.encoding =
|
|
902
|
-
response.source = Buffer.from(result.body,
|
|
903
|
-
response.variety =
|
|
901
|
+
response.encoding = "binary"
|
|
902
|
+
response.source = Buffer.from(result.body, "base64")
|
|
903
|
+
response.variety = "buffer"
|
|
904
904
|
} else {
|
|
905
|
-
if (result && result.body && typeof result.body !==
|
|
905
|
+
if (result && result.body && typeof result.body !== "string") {
|
|
906
906
|
// FIXME TODO we should probably just write to console instead of returning a payload
|
|
907
907
|
return this.#reply502(
|
|
908
908
|
response,
|
|
909
|
-
|
|
909
|
+
"According to the API Gateway specs, the body content must be stringified. Check your Lambda response and make sure you are invoking JSON.stringify(YOUR_CONTENT) on your body object",
|
|
910
910
|
{},
|
|
911
911
|
)
|
|
912
912
|
}
|
|
@@ -951,12 +951,12 @@ export default class HttpServer {
|
|
|
951
951
|
let hapiPath
|
|
952
952
|
|
|
953
953
|
if (httpEvent.isHttpApi) {
|
|
954
|
-
if (httpEvent.routeKey ===
|
|
955
|
-
method =
|
|
954
|
+
if (httpEvent.routeKey === "$default") {
|
|
955
|
+
method = "ANY"
|
|
956
956
|
path = httpEvent.routeKey
|
|
957
|
-
hapiPath =
|
|
957
|
+
hapiPath = "/{default*}"
|
|
958
958
|
} else {
|
|
959
|
-
;[method, path] = httpEvent.routeKey.split(
|
|
959
|
+
;[method, path] = httpEvent.routeKey.split(" ")
|
|
960
960
|
hapiPath = generateHapiPath(
|
|
961
961
|
path,
|
|
962
962
|
{
|
|
@@ -980,7 +980,7 @@ export default class HttpServer {
|
|
|
980
980
|
).generate()
|
|
981
981
|
|
|
982
982
|
const stage = endpoint.isHttpApi
|
|
983
|
-
?
|
|
983
|
+
? "$default"
|
|
984
984
|
: this.#options.stage || this.#serverless.service.provider.stage
|
|
985
985
|
|
|
986
986
|
const protectedRoute = httpEvent.private
|
|
@@ -988,7 +988,7 @@ export default class HttpServer {
|
|
|
988
988
|
: undefined
|
|
989
989
|
|
|
990
990
|
const { host, httpPort, httpsProtocol } = this.#options
|
|
991
|
-
const server = `${httpsProtocol ?
|
|
991
|
+
const server = `${httpsProtocol ? "https" : "http"}://${host}:${httpPort}`
|
|
992
992
|
|
|
993
993
|
this.#terminalInfo.push({
|
|
994
994
|
invokePath: `/2015-03-31/functions/${functionKey}/invocations`,
|
|
@@ -1032,15 +1032,15 @@ export default class HttpServer {
|
|
|
1032
1032
|
}
|
|
1033
1033
|
}
|
|
1034
1034
|
|
|
1035
|
-
const hapiMethod = method ===
|
|
1035
|
+
const hapiMethod = method === "ANY" ? "*" : method
|
|
1036
1036
|
|
|
1037
1037
|
const state = this.#options.disableCookieValidation
|
|
1038
1038
|
? {
|
|
1039
|
-
failAction:
|
|
1039
|
+
failAction: "ignore",
|
|
1040
1040
|
parse: false,
|
|
1041
1041
|
}
|
|
1042
1042
|
: {
|
|
1043
|
-
failAction:
|
|
1043
|
+
failAction: "error",
|
|
1044
1044
|
parse: true,
|
|
1045
1045
|
}
|
|
1046
1046
|
|
|
@@ -1058,15 +1058,15 @@ export default class HttpServer {
|
|
|
1058
1058
|
|
|
1059
1059
|
// skip HEAD routes as hapi will fail with 'Method name not allowed: HEAD ...'
|
|
1060
1060
|
// for more details, check https://github.com/dherault/serverless-offline/issues/204
|
|
1061
|
-
if (hapiMethod ===
|
|
1061
|
+
if (hapiMethod === "HEAD") {
|
|
1062
1062
|
log.notice(
|
|
1063
|
-
|
|
1063
|
+
"HEAD method event detected. Skipping HAPI server route mapping",
|
|
1064
1064
|
)
|
|
1065
1065
|
|
|
1066
1066
|
return
|
|
1067
1067
|
}
|
|
1068
1068
|
|
|
1069
|
-
if (hapiMethod !==
|
|
1069
|
+
if (hapiMethod !== "HEAD" && hapiMethod !== "GET") {
|
|
1070
1070
|
// maxBytes: Increase request size from 1MB default limit to 10MB.
|
|
1071
1071
|
// Cf AWS API GW payload limits.
|
|
1072
1072
|
hapiOptions.payload = {
|
|
@@ -1080,7 +1080,7 @@ export default class HttpServer {
|
|
|
1080
1080
|
additionalRequestContext.operationName = httpEvent.operationId
|
|
1081
1081
|
}
|
|
1082
1082
|
|
|
1083
|
-
hapiOptions.tags = [
|
|
1083
|
+
hapiOptions.tags = ["api"]
|
|
1084
1084
|
|
|
1085
1085
|
const hapiHandler = this.#createHapiHandler({
|
|
1086
1086
|
additionalRequestContext,
|
|
@@ -1106,14 +1106,14 @@ export default class HttpServer {
|
|
|
1106
1106
|
|
|
1107
1107
|
log.error(error)
|
|
1108
1108
|
|
|
1109
|
-
response.header(
|
|
1109
|
+
response.header("Content-Type", "application/json")
|
|
1110
1110
|
|
|
1111
1111
|
response.statusCode = statusCode
|
|
1112
1112
|
response.source = {
|
|
1113
1113
|
errorMessage: message,
|
|
1114
1114
|
errorType: error.constructor.name,
|
|
1115
1115
|
offlineInfo:
|
|
1116
|
-
|
|
1116
|
+
"If you believe this is an issue with serverless-offline please submit it, thanks. https://github.com/dherault/serverless-offline/issues",
|
|
1117
1117
|
stackTrace: this.#getArrayStackTrace(error.stack),
|
|
1118
1118
|
}
|
|
1119
1119
|
|
|
@@ -1146,7 +1146,7 @@ export default class HttpServer {
|
|
|
1146
1146
|
log.notice()
|
|
1147
1147
|
|
|
1148
1148
|
log.notice()
|
|
1149
|
-
log.notice(
|
|
1149
|
+
log.notice("Routes defined in resources:")
|
|
1150
1150
|
|
|
1151
1151
|
entries(resourceRoutes).forEach(([methodId, resourceRoutesObj]) => {
|
|
1152
1152
|
const { isProxy, method, pathResource, proxyUri } = resourceRoutesObj
|
|
@@ -1178,15 +1178,15 @@ export default class HttpServer {
|
|
|
1178
1178
|
return
|
|
1179
1179
|
}
|
|
1180
1180
|
|
|
1181
|
-
const hapiMethod = method ===
|
|
1181
|
+
const hapiMethod = method === "ANY" ? "*" : method
|
|
1182
1182
|
|
|
1183
1183
|
const state = this.#options.disableCookieValidation
|
|
1184
1184
|
? {
|
|
1185
|
-
failAction:
|
|
1185
|
+
failAction: "ignore",
|
|
1186
1186
|
parse: false,
|
|
1187
1187
|
}
|
|
1188
1188
|
: {
|
|
1189
|
-
failAction:
|
|
1189
|
+
failAction: "error",
|
|
1190
1190
|
parse: true,
|
|
1191
1191
|
}
|
|
1192
1192
|
|
|
@@ -1197,15 +1197,15 @@ export default class HttpServer {
|
|
|
1197
1197
|
|
|
1198
1198
|
// skip HEAD routes as hapi will fail with 'Method name not allowed: HEAD ...'
|
|
1199
1199
|
// for more details, check https://github.com/dherault/serverless-offline/issues/204
|
|
1200
|
-
if (hapiMethod ===
|
|
1200
|
+
if (hapiMethod === "HEAD") {
|
|
1201
1201
|
log.notice(
|
|
1202
|
-
|
|
1202
|
+
"HEAD method event detected. Skipping HAPI server route mapping",
|
|
1203
1203
|
)
|
|
1204
1204
|
|
|
1205
1205
|
return
|
|
1206
1206
|
}
|
|
1207
1207
|
|
|
1208
|
-
if (hapiMethod !==
|
|
1208
|
+
if (hapiMethod !== "GET" && hapiMethod !== "HEAD") {
|
|
1209
1209
|
hapiOptions.payload = {
|
|
1210
1210
|
parse: false,
|
|
1211
1211
|
}
|
|
@@ -1248,14 +1248,14 @@ export default class HttpServer {
|
|
|
1248
1248
|
|
|
1249
1249
|
create404Route() {
|
|
1250
1250
|
// If a {proxy+} or $default route exists, don't conflict with it
|
|
1251
|
-
if (this.#server.match(
|
|
1251
|
+
if (this.#server.match("*", "/{p*}")) {
|
|
1252
1252
|
return
|
|
1253
1253
|
}
|
|
1254
1254
|
|
|
1255
1255
|
const existingRoutes = this.#server
|
|
1256
1256
|
.table()
|
|
1257
1257
|
// Exclude this (404) route
|
|
1258
|
-
.filter((route) => route.path !==
|
|
1258
|
+
.filter((route) => route.path !== "/{p*}")
|
|
1259
1259
|
// Sort by path
|
|
1260
1260
|
.sort((a, b) => (a.path <= b.path ? -1 : 1))
|
|
1261
1261
|
// Human-friendly result
|
|
@@ -1265,7 +1265,7 @@ export default class HttpServer {
|
|
|
1265
1265
|
handler(request, h) {
|
|
1266
1266
|
const response = h.response({
|
|
1267
1267
|
currentRoute: `${request.method} - ${request.path}`,
|
|
1268
|
-
error:
|
|
1268
|
+
error: "Serverless-offline: route not found.",
|
|
1269
1269
|
existingRoutes,
|
|
1270
1270
|
statusCode: 404,
|
|
1271
1271
|
})
|
|
@@ -1273,11 +1273,11 @@ export default class HttpServer {
|
|
|
1273
1273
|
|
|
1274
1274
|
return response
|
|
1275
1275
|
},
|
|
1276
|
-
method:
|
|
1276
|
+
method: "*",
|
|
1277
1277
|
options: {
|
|
1278
1278
|
cors: this.#options.corsConfig,
|
|
1279
1279
|
},
|
|
1280
|
-
path:
|
|
1280
|
+
path: "/{p*}",
|
|
1281
1281
|
}
|
|
1282
1282
|
|
|
1283
1283
|
this.#server.route(route)
|
|
@@ -1286,7 +1286,7 @@ export default class HttpServer {
|
|
|
1286
1286
|
#getArrayStackTrace(stack) {
|
|
1287
1287
|
if (!stack) return null
|
|
1288
1288
|
|
|
1289
|
-
const splittedStack = stack.split(
|
|
1289
|
+
const splittedStack = stack.split("\n")
|
|
1290
1290
|
|
|
1291
1291
|
return splittedStack
|
|
1292
1292
|
.slice(
|