@kensio/yulin 1.21.10 → 1.21.11
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/dist/serve/payload-2/sim-payload-2-endpoint.d.ts +9 -0
- package/dist/serve/payload-2/sim-payload-2-request-context.js +1 -1
- package/dist/service/apigatewayv2/api/sim-http-api-lambda-proxy.factory.d.ts +6 -0
- package/dist/service/apigatewayv2/api/sim-http-api-lambda-proxy.factory.js +2 -0
- package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-format.d.ts +8 -0
- package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-format.js +36 -0
- package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-request.d.ts +53 -0
- package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-request.js +1 -0
- package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-settings.d.ts +26 -0
- package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-settings.js +44 -0
- package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-settings.type.d.ts +21 -0
- package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-settings.type.js +1 -0
- package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-stream-name.d.ts +13 -0
- package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-stream-name.js +23 -0
- package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-variables.d.ts +17 -0
- package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-variables.js +86 -0
- package/dist/service/apigatewayv2/api/stage/sim-http-api-stage.d.ts +9 -0
- package/dist/service/apigatewayv2/api/stage/sim-http-api-stage.js +9 -0
- package/dist/service/apigatewayv2/cfn/stage/sim-cfn-http-api-access-log-settings-properties.d.ts +24 -0
- package/dist/service/apigatewayv2/cfn/stage/sim-cfn-http-api-access-log-settings-properties.js +30 -0
- package/dist/service/apigatewayv2/cfn/stage/sim-cfn-http-api-stage-properties.d.ts +1 -0
- package/dist/service/apigatewayv2/cfn/stage/sim-cfn-http-api-stage-properties.js +10 -4
- package/dist/service/apigatewayv2/command/stage/sim-http-api-access-log-settings-input.d.ts +15 -0
- package/dist/service/apigatewayv2/command/stage/sim-http-api-access-log-settings-input.js +33 -0
- package/dist/service/apigatewayv2/command/stage/sim-http-api-stage-commands.js +4 -0
- package/dist/service/apigatewayv2/command/stage/stage.command.d.ts +2 -0
- package/dist/service/apigatewayv2/serve/auth/sim-http-api-route-authorize-input.d.ts +6 -0
- package/dist/service/apigatewayv2/serve/sim-api-gateway-v2-controller.d.ts +13 -15
- package/dist/service/apigatewayv2/serve/sim-api-gateway-v2-controller.js +26 -60
- package/dist/service/apigatewayv2/serve/sim-http-api-access-log-record.d.ts +31 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-access-log-record.js +88 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-access-log.d.ts +43 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-access-log.js +72 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-endpoint.js +1 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-integration-input.d.ts +22 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-integration-input.js +1 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-integration-invocation.d.ts +5 -22
- package/dist/service/apigatewayv2/serve/sim-http-api-integration-invocation.js +17 -20
- package/dist/service/apigatewayv2/serve/sim-http-api-integration-outcome.d.ts +27 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-integration-outcome.js +13 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-may-invoke.d.ts +19 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-may-invoke.js +19 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-serve-pipeline.d.ts +27 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-serve-pipeline.js +75 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-served.d.ts +22 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-served.js +1 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-serving.d.ts +12 -0
- package/dist/service/apigatewayv2/serve/sim-http-api-serving.js +1 -0
- package/dist/service/logs/group/sim-logs-arn.d.ts +15 -0
- package/dist/service/logs/group/sim-logs-arn.js +33 -0
- package/docs/services/apigatewayv2/README.md +162 -6
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SimHttpApiStage } from "../../api/stage/sim-http-api-stage.js";
|
|
2
2
|
import { SimApiGatewayV2NotFound } from "../../error/sim-api-gateway-v2.error.js";
|
|
3
3
|
import { SimApiGatewayV2UnsimulatedInput } from "../sim-api-gateway-v2-unsimulated-input.js";
|
|
4
|
+
import { simHttpApiAccessLogSettings } from "./sim-http-api-access-log-settings-input.js";
|
|
4
5
|
import { simHttpApiStageRouteSettings } from "./sim-http-api-stage-route-settings-input.js";
|
|
5
6
|
import { SimHttpApiStageRules } from "./sim-http-api-stage-rules.js";
|
|
6
7
|
const stagesPath = "/stages";
|
|
@@ -12,6 +13,7 @@ const acceptedCreateStageOptions = [
|
|
|
12
13
|
"Description",
|
|
13
14
|
"DefaultRouteSettings",
|
|
14
15
|
"RouteSettings",
|
|
16
|
+
"AccessLogSettings",
|
|
15
17
|
];
|
|
16
18
|
/**
|
|
17
19
|
* The commands addressing the stages of an API.
|
|
@@ -38,6 +40,7 @@ export class SimHttpApiStageCommands {
|
|
|
38
40
|
unsimulated,
|
|
39
41
|
clock: this.clock,
|
|
40
42
|
});
|
|
43
|
+
const accessLogSettings = simHttpApiAccessLogSettings(input, unsimulated);
|
|
41
44
|
const httpApi = this.access.api({
|
|
42
45
|
method: "POST",
|
|
43
46
|
apiId,
|
|
@@ -52,6 +55,7 @@ export class SimHttpApiStageCommands {
|
|
|
52
55
|
description: input.Description,
|
|
53
56
|
createdDate: this.clock.now(),
|
|
54
57
|
routeSettings,
|
|
58
|
+
accessLogSettings,
|
|
55
59
|
});
|
|
56
60
|
httpApi.stages.add(stage);
|
|
57
61
|
return { ...stage.view(), $metadata: {} };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SimResponseMetadata } from "../../../aws/metadata/response-metadata.type.js";
|
|
2
2
|
import type { SimHttpApiRouteSettings, SimHttpApiRouteSettingsMap } from "../../api/stage/settings/sim-http-api-route-settings.type.js";
|
|
3
|
+
import type { SimHttpApiAccessLogSettingsInput } from "../../api/stage/access-log/sim-http-api-access-log-settings.type.js";
|
|
3
4
|
import type { SimHttpApiStageView } from "../../api/stage/sim-http-api-stage.js";
|
|
4
5
|
/**
|
|
5
6
|
* Minimal structural sim API Gateway v2 CreateStage command.
|
|
@@ -17,6 +18,7 @@ export interface SimCreateStageCommandInput {
|
|
|
17
18
|
readonly Description?: string | undefined;
|
|
18
19
|
readonly DefaultRouteSettings?: SimHttpApiRouteSettings | undefined;
|
|
19
20
|
readonly RouteSettings?: SimHttpApiRouteSettingsMap | undefined;
|
|
21
|
+
readonly AccessLogSettings?: SimHttpApiAccessLogSettingsInput | undefined;
|
|
20
22
|
}
|
|
21
23
|
export interface SimCreateStageCommandOutput extends SimHttpApiStageView {
|
|
22
24
|
readonly $metadata: SimResponseMetadata;
|
|
@@ -35,4 +35,10 @@ export interface SimHttpApiRouteAuthorizeInput {
|
|
|
35
35
|
* integrated function's.
|
|
36
36
|
*/
|
|
37
37
|
readonly iam: SimIamInterServiceAuthZ;
|
|
38
|
+
/**
|
|
39
|
+
* The id the endpoint stamped this request with, which a `REQUEST`
|
|
40
|
+
* authorizer's event reports as its own. The authorizer and the integration
|
|
41
|
+
* behind the same request name one id, as they do on real AWS.
|
|
42
|
+
*/
|
|
43
|
+
readonly requestId?: string | undefined;
|
|
38
44
|
}
|
|
@@ -10,31 +10,29 @@ interface SimApiGatewayV2ServiceControllerProperties {
|
|
|
10
10
|
*
|
|
11
11
|
* A request reaching the generated endpoint, or a custom domain mapped to the
|
|
12
12
|
* API, is matched to a route, and the route's integration invokes a simulated
|
|
13
|
-
* Lambda function with a payload format 2.0 event. The function runs as its
|
|
14
|
-
* any other invocation.
|
|
13
|
+
* Lambda function with a payload format 2.0 event. The function runs as its
|
|
14
|
+
* execution Role, as it does for any other invocation. The throttle and the
|
|
15
|
+
* authorization standing in front of that are the pipeline's.
|
|
15
16
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* A route that authorizes anybody is checked next: a JWT route's
|
|
20
|
-
* token has to verify and meet the route's scopes, an `AWS_IAM` route's caller
|
|
21
|
-
* has to be allowed `execute-api:Invoke` on the route, and a `CUSTOM` route's
|
|
22
|
-
* Lambda authorizer has to allow the request, or the request is refused and the
|
|
23
|
-
* integration is never invoked. Whether the API may invoke a function is a
|
|
24
|
-
* separate question, and the API's own rather than the client's.
|
|
17
|
+
* Every request the resolver matches is stamped with an id before anything
|
|
18
|
+
* runs, so the stage's access log line, the authorizer event and the
|
|
19
|
+
* integration event all name the same request, as they do on real AWS.
|
|
25
20
|
*/
|
|
26
21
|
export declare class SimApiGatewayV2ServiceController implements SimAwsServiceController {
|
|
27
22
|
private readonly router;
|
|
28
|
-
private readonly
|
|
29
|
-
private readonly integration;
|
|
23
|
+
private readonly pipeline;
|
|
30
24
|
private readonly serving;
|
|
25
|
+
private readonly accessLog;
|
|
31
26
|
private readonly errorResponse;
|
|
32
|
-
private readonly refusalResponse;
|
|
33
27
|
constructor(properties?: SimApiGatewayV2ServiceControllerProperties);
|
|
34
28
|
/**
|
|
35
29
|
* Handle an HTTP request routed to a simulated HTTP API.
|
|
36
30
|
*/
|
|
37
31
|
handleRequest(serviceRequest: SimAwsServiceRequest): Promise<Response>;
|
|
38
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Serve one request the resolver matched to a stage, and record it in that
|
|
34
|
+
* stage's access log.
|
|
35
|
+
*/
|
|
36
|
+
private serve;
|
|
39
37
|
}
|
|
40
38
|
export {};
|
|
@@ -1,50 +1,38 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { SimAws } from "../../aws/sim-aws.js";
|
|
2
|
-
import {
|
|
3
|
+
import { SimHttpApiAccessLog } from "./sim-http-api-access-log.js";
|
|
3
4
|
import { SimApiGatewayV2ErrorResponse } from "./sim-api-gateway-v2-error-response.js";
|
|
4
5
|
import { SimApiGatewayV2Router } from "./sim-api-gateway-v2-router.js";
|
|
5
|
-
import {
|
|
6
|
-
import { SimHttpApiRefusalResponse } from "./sim-http-api-refusal-response.js";
|
|
6
|
+
import { SimHttpApiServePipeline } from "./sim-http-api-serve-pipeline.js";
|
|
7
7
|
import { SimHttpApiServingResolver, } from "./sim-http-api-serving.js";
|
|
8
8
|
/**
|
|
9
9
|
* Localhost HTTP controller for simulated API Gateway HTTP APIs.
|
|
10
10
|
*
|
|
11
11
|
* A request reaching the generated endpoint, or a custom domain mapped to the
|
|
12
12
|
* API, is matched to a route, and the route's integration invokes a simulated
|
|
13
|
-
* Lambda function with a payload format 2.0 event. The function runs as its
|
|
14
|
-
* any other invocation.
|
|
13
|
+
* Lambda function with a payload format 2.0 event. The function runs as its
|
|
14
|
+
* execution Role, as it does for any other invocation. The throttle and the
|
|
15
|
+
* authorization standing in front of that are the pipeline's.
|
|
15
16
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* A route that authorizes anybody is checked next: a JWT route's
|
|
20
|
-
* token has to verify and meet the route's scopes, an `AWS_IAM` route's caller
|
|
21
|
-
* has to be allowed `execute-api:Invoke` on the route, and a `CUSTOM` route's
|
|
22
|
-
* Lambda authorizer has to allow the request, or the request is refused and the
|
|
23
|
-
* integration is never invoked. Whether the API may invoke a function is a
|
|
24
|
-
* separate question, and the API's own rather than the client's.
|
|
17
|
+
* Every request the resolver matches is stamped with an id before anything
|
|
18
|
+
* runs, so the stage's access log line, the authorizer event and the
|
|
19
|
+
* integration event all name the same request, as they do on real AWS.
|
|
25
20
|
*/
|
|
26
21
|
export class SimApiGatewayV2ServiceController {
|
|
27
22
|
router;
|
|
28
|
-
|
|
29
|
-
integration;
|
|
23
|
+
pipeline;
|
|
30
24
|
serving;
|
|
25
|
+
accessLog;
|
|
31
26
|
errorResponse = new SimApiGatewayV2ErrorResponse();
|
|
32
|
-
refusalResponse = new SimHttpApiRefusalResponse();
|
|
33
27
|
constructor(properties = {}) {
|
|
34
28
|
const { simAws = new SimAws() } = properties;
|
|
35
29
|
this.router = properties.router ?? new SimApiGatewayV2Router({ simAws });
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
clock: this.router.simAws,
|
|
41
|
-
functions: this.router,
|
|
42
|
-
});
|
|
43
|
-
this.integration = new SimHttpApiIntegrationInvocation({
|
|
44
|
-
router: this.router,
|
|
30
|
+
this.pipeline = new SimHttpApiServePipeline({ router: this.router });
|
|
31
|
+
this.serving = new SimHttpApiServingResolver({ router: this.router });
|
|
32
|
+
this.accessLog = new SimHttpApiAccessLog({
|
|
33
|
+
simAws: this.router.simAws,
|
|
45
34
|
clock: this.router.simAws,
|
|
46
35
|
});
|
|
47
|
-
this.serving = new SimHttpApiServingResolver({ router: this.router });
|
|
48
36
|
}
|
|
49
37
|
/**
|
|
50
38
|
* Handle an HTTP request routed to a simulated HTTP API.
|
|
@@ -59,41 +47,19 @@ export class SimApiGatewayV2ServiceController {
|
|
|
59
47
|
return this.errorResponse.forbiddenHost();
|
|
60
48
|
}
|
|
61
49
|
case "served": {
|
|
62
|
-
return await this.
|
|
50
|
+
return await this.serve(resolution.serving, serviceRequest);
|
|
63
51
|
}
|
|
64
52
|
}
|
|
65
53
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// credentials is refused whether or not the integration behind the route
|
|
77
|
-
// would work.
|
|
78
|
-
const authorization = await this.routeAuthorizer.authorize({
|
|
79
|
-
api,
|
|
80
|
-
match,
|
|
81
|
-
request,
|
|
82
|
-
caller: serviceRequest.caller,
|
|
83
|
-
iam: this.router.iamFor(api),
|
|
84
|
-
domainName: serving.domainName,
|
|
85
|
-
rawPath: serving.rawPath,
|
|
86
|
-
});
|
|
87
|
-
if (!authorization.admitted) {
|
|
88
|
-
return this.refusalResponse.build(authorization);
|
|
89
|
-
}
|
|
90
|
-
return await this.integration.invoke({
|
|
91
|
-
api,
|
|
92
|
-
match,
|
|
93
|
-
request,
|
|
94
|
-
authorization,
|
|
95
|
-
domainName: serving.domainName,
|
|
96
|
-
rawPath: serving.rawPath,
|
|
97
|
-
});
|
|
54
|
+
/**
|
|
55
|
+
* Serve one request the resolver matched to a stage, and record it in that
|
|
56
|
+
* stage's access log.
|
|
57
|
+
*/
|
|
58
|
+
async serve(resolved, serviceRequest) {
|
|
59
|
+
const serving = { ...resolved, requestId: randomUUID() };
|
|
60
|
+
const at = this.router.simAws.now();
|
|
61
|
+
const served = await this.pipeline.run(serving, serviceRequest);
|
|
62
|
+
await this.accessLog.served(serving, serviceRequest.request, served, at);
|
|
63
|
+
return served.response;
|
|
98
64
|
}
|
|
99
65
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SimHttpApiAccessLogRequest } from "../api/stage/access-log/sim-http-api-access-log-request.js";
|
|
2
|
+
import type { SimHttpApiAuthorization } from "../api/authorizer/sim-http-api-authorization.js";
|
|
3
|
+
import type { SimHttpApiServing } from "./sim-http-api-serving.js";
|
|
4
|
+
import type { SimHttpApiIntegrationOutcome } from "./sim-http-api-integration-outcome.js";
|
|
5
|
+
interface SimHttpApiAccessLogRecordInput {
|
|
6
|
+
readonly serving: SimHttpApiServing;
|
|
7
|
+
readonly requestId: string;
|
|
8
|
+
readonly request: Request;
|
|
9
|
+
readonly response: Response;
|
|
10
|
+
/** Simulated milliseconds the whole request took. */
|
|
11
|
+
readonly responseLatency: number;
|
|
12
|
+
/** How long the response body is, measured from the response itself. */
|
|
13
|
+
readonly responseLength: number;
|
|
14
|
+
/** The simulated instant the request arrived. */
|
|
15
|
+
readonly at: Date;
|
|
16
|
+
/** Absent for a request refused before the route's authorizer ran. */
|
|
17
|
+
readonly authorization?: SimHttpApiAuthorization | undefined;
|
|
18
|
+
/** Absent for a request no integration was invoked for. */
|
|
19
|
+
readonly integration?: SimHttpApiIntegrationOutcome | undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Describe one served request as its stage's access log records it.
|
|
23
|
+
*
|
|
24
|
+
* Every path through the endpoint arrives here, including the ones that
|
|
25
|
+
* refused the request. A throttled request has no authorization and no
|
|
26
|
+
* integration, and an authorizer that said no has an authorization and no
|
|
27
|
+
* integration. What each one knows is what its line carries, and the rest of
|
|
28
|
+
* the format's variables render as dashes.
|
|
29
|
+
*/
|
|
30
|
+
export declare function simHttpApiAccessLogRecord(input: SimHttpApiAccessLogRecordInput): SimHttpApiAccessLogRequest;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { simAwsProxiedSourceIp } from "../../../serve/http/sim-aws-proxied-connection.js";
|
|
2
|
+
/**
|
|
3
|
+
* The `$context.error.message` an endpoint's own refusal carries.
|
|
4
|
+
*
|
|
5
|
+
* These are the bodies `SimApiGatewayV2ErrorResponse` answers with, keyed by
|
|
6
|
+
* the status they answer with. A response an integration produced has no entry
|
|
7
|
+
* here, and leaves the variable empty as real API Gateway does.
|
|
8
|
+
*/
|
|
9
|
+
const refusalMessages = new Map([
|
|
10
|
+
[401, "Unauthorized"],
|
|
11
|
+
[403, "Forbidden"],
|
|
12
|
+
[429, "Too Many Requests"],
|
|
13
|
+
[500, "Internal Server Error"],
|
|
14
|
+
]);
|
|
15
|
+
/**
|
|
16
|
+
* Describe one served request as its stage's access log records it.
|
|
17
|
+
*
|
|
18
|
+
* Every path through the endpoint arrives here, including the ones that
|
|
19
|
+
* refused the request. A throttled request has no authorization and no
|
|
20
|
+
* integration, and an authorizer that said no has an authorization and no
|
|
21
|
+
* integration. What each one knows is what its line carries, and the rest of
|
|
22
|
+
* the format's variables render as dashes.
|
|
23
|
+
*/
|
|
24
|
+
export function simHttpApiAccessLogRecord(input) {
|
|
25
|
+
const { serving, request, response, authorization, integration } = input;
|
|
26
|
+
const { api, match, domainName } = serving;
|
|
27
|
+
const admitted = authorization?.admitted === true ? authorization : undefined;
|
|
28
|
+
return {
|
|
29
|
+
requestId: input.requestId,
|
|
30
|
+
at: input.at,
|
|
31
|
+
accountId: api.accountRegionScope.accountId,
|
|
32
|
+
apiId: api.apiId,
|
|
33
|
+
domainName,
|
|
34
|
+
domainPrefix: domainName.split(".", 1).join(""),
|
|
35
|
+
stage: match.stage.stageName,
|
|
36
|
+
routeKey: match.route.routeKey,
|
|
37
|
+
method: request.method,
|
|
38
|
+
path: serving.rawPath,
|
|
39
|
+
protocol: "HTTP/1.1",
|
|
40
|
+
sourceIp: simAwsProxiedSourceIp,
|
|
41
|
+
userAgent: request.headers.get("user-agent") ?? "",
|
|
42
|
+
status: response.status,
|
|
43
|
+
responseLength: input.responseLength,
|
|
44
|
+
responseLatency: input.responseLatency,
|
|
45
|
+
...(refusalMessage(response, integration) !== undefined && {
|
|
46
|
+
errorMessage: refusalMessage(response, integration),
|
|
47
|
+
}),
|
|
48
|
+
...(authorization?.admitted === false &&
|
|
49
|
+
authorization.errorDescription !== undefined && {
|
|
50
|
+
authorizerError: authorization.errorDescription,
|
|
51
|
+
}),
|
|
52
|
+
...integrationFields(integration),
|
|
53
|
+
...(admitted?.jwt !== undefined && { jwt: admitted.jwt }),
|
|
54
|
+
...(admitted?.lambda !== undefined && { lambda: admitted.lambda }),
|
|
55
|
+
...(serving.basePathMatched !== undefined && {
|
|
56
|
+
basePathMatched: serving.basePathMatched,
|
|
57
|
+
}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* What the integration contributed, with the members it has no value for left
|
|
62
|
+
* out so that they render as dashes.
|
|
63
|
+
*
|
|
64
|
+
* A request no integration ran for contributes nothing at all, which is what a
|
|
65
|
+
* throttled or refused request does.
|
|
66
|
+
*/
|
|
67
|
+
function integrationFields(integration) {
|
|
68
|
+
if (integration === undefined) {
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
const { integrationStatus, lambdaInvokeStatus, integrationErrorMessage, integrationLatency, } = integration;
|
|
72
|
+
return {
|
|
73
|
+
...(integrationStatus !== undefined && { integrationStatus }),
|
|
74
|
+
...(lambdaInvokeStatus !== undefined && { lambdaInvokeStatus }),
|
|
75
|
+
...(integrationErrorMessage !== undefined && { integrationErrorMessage }),
|
|
76
|
+
...(integrationLatency !== undefined && { integrationLatency }),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The message the endpoint answered with, where the endpoint answered rather
|
|
81
|
+
* than the integration.
|
|
82
|
+
*/
|
|
83
|
+
function refusalMessage(response, integration) {
|
|
84
|
+
if (integration?.integrationStatus !== undefined) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
return refusalMessages.get(response.status);
|
|
88
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { SimAws } from "../../aws/sim-aws.js";
|
|
2
|
+
import type { SimClock } from "../../../util/clock/sim-clock.js";
|
|
3
|
+
import type { SimHttpApiAccessLogRequest } from "../api/stage/access-log/sim-http-api-access-log-request.js";
|
|
4
|
+
import type { SimHttpApiStage } from "../api/stage/sim-http-api-stage.js";
|
|
5
|
+
import type { SimHttpApiServed } from "./sim-http-api-served.js";
|
|
6
|
+
import type { SimHttpApiServing } from "./sim-http-api-serving.js";
|
|
7
|
+
interface SimHttpApiAccessLogProperties {
|
|
8
|
+
readonly simAws: SimAws;
|
|
9
|
+
readonly clock: SimClock;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Writes a stage's access log line for one request.
|
|
13
|
+
*
|
|
14
|
+
* The line goes to the log group alone. Sim Lambda forwards a handler's output
|
|
15
|
+
* to the host console as well, and an API answering a few hundred requests in
|
|
16
|
+
* one test file would bury the suite's own output the same way. Writing
|
|
17
|
+
* through `SimLogsServiceWriter` keeps it out of the console, and a test reads
|
|
18
|
+
* the lines back with `FilterLogEvents`.
|
|
19
|
+
*
|
|
20
|
+
* Nothing here fails. A destination log group that was never declared is
|
|
21
|
+
* created by the write, and a stage with no access log settings writes
|
|
22
|
+
* nothing, which is how a request is served whether or not anybody is logging
|
|
23
|
+
* it.
|
|
24
|
+
*/
|
|
25
|
+
export declare class SimHttpApiAccessLog {
|
|
26
|
+
#private;
|
|
27
|
+
constructor(properties: SimHttpApiAccessLogProperties);
|
|
28
|
+
/**
|
|
29
|
+
* Record one finished request against the stage that served it.
|
|
30
|
+
*
|
|
31
|
+
* A stage with no access log settings does nothing here, including none of
|
|
32
|
+
* the measuring a line needs. The response body is measured from a clone,
|
|
33
|
+
* since the one the client gets still has to be readable, and the latency is
|
|
34
|
+
* simulated milliseconds, so a test holding the clock logs a latency of zero
|
|
35
|
+
* rather than however long the process happened to take.
|
|
36
|
+
*/
|
|
37
|
+
served(serving: SimHttpApiServing, request: Request, served: SimHttpApiServed, at: Date): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Record one described request against the stage that served it.
|
|
40
|
+
*/
|
|
41
|
+
record(stage: SimHttpApiStage, request: SimHttpApiAccessLogRequest): void;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { simHttpApiAccessLogLine } from "../api/stage/access-log/sim-http-api-access-log-format.js";
|
|
2
|
+
import { simHttpApiAccessLogStreamName } from "../api/stage/access-log/sim-http-api-access-log-stream-name.js";
|
|
3
|
+
import { simHttpApiAccessLogVariables } from "../api/stage/access-log/sim-http-api-access-log-variables.js";
|
|
4
|
+
import { simHttpApiAccessLogRecord } from "./sim-http-api-access-log-record.js";
|
|
5
|
+
/**
|
|
6
|
+
* Writes a stage's access log line for one request.
|
|
7
|
+
*
|
|
8
|
+
* The line goes to the log group alone. Sim Lambda forwards a handler's output
|
|
9
|
+
* to the host console as well, and an API answering a few hundred requests in
|
|
10
|
+
* one test file would bury the suite's own output the same way. Writing
|
|
11
|
+
* through `SimLogsServiceWriter` keeps it out of the console, and a test reads
|
|
12
|
+
* the lines back with `FilterLogEvents`.
|
|
13
|
+
*
|
|
14
|
+
* Nothing here fails. A destination log group that was never declared is
|
|
15
|
+
* created by the write, and a stage with no access log settings writes
|
|
16
|
+
* nothing, which is how a request is served whether or not anybody is logging
|
|
17
|
+
* it.
|
|
18
|
+
*/
|
|
19
|
+
export class SimHttpApiAccessLog {
|
|
20
|
+
#simAws;
|
|
21
|
+
#clock;
|
|
22
|
+
constructor(properties) {
|
|
23
|
+
this.#simAws = properties.simAws;
|
|
24
|
+
this.#clock = properties.clock;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Record one finished request against the stage that served it.
|
|
28
|
+
*
|
|
29
|
+
* A stage with no access log settings does nothing here, including none of
|
|
30
|
+
* the measuring a line needs. The response body is measured from a clone,
|
|
31
|
+
* since the one the client gets still has to be readable, and the latency is
|
|
32
|
+
* simulated milliseconds, so a test holding the clock logs a latency of zero
|
|
33
|
+
* rather than however long the process happened to take.
|
|
34
|
+
*/
|
|
35
|
+
async served(serving, request, served, at) {
|
|
36
|
+
const { match, requestId = "" } = serving;
|
|
37
|
+
if (match.stage.accessLogSettings === undefined) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const body = await served.response.clone().arrayBuffer();
|
|
41
|
+
this.record(match.stage, simHttpApiAccessLogRecord({
|
|
42
|
+
serving,
|
|
43
|
+
requestId,
|
|
44
|
+
request,
|
|
45
|
+
response: served.response,
|
|
46
|
+
responseLength: body.byteLength,
|
|
47
|
+
responseLatency: this.#clock.now().getTime() - at.getTime(),
|
|
48
|
+
at,
|
|
49
|
+
...(served.authorization !== undefined && {
|
|
50
|
+
authorization: served.authorization,
|
|
51
|
+
}),
|
|
52
|
+
...(served.integration !== undefined && {
|
|
53
|
+
integration: served.integration,
|
|
54
|
+
}),
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Record one described request against the stage that served it.
|
|
59
|
+
*/
|
|
60
|
+
record(stage, request) {
|
|
61
|
+
const settings = stage.accessLogSettings;
|
|
62
|
+
if (settings === undefined) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const line = simHttpApiAccessLogLine(settings.format, simHttpApiAccessLogVariables(request));
|
|
66
|
+
this.#simAws
|
|
67
|
+
.accountRegionScope(settings.accountId, settings.regionName)
|
|
68
|
+
.logs()
|
|
69
|
+
.serviceWriter()
|
|
70
|
+
.write(settings.logGroupName, simHttpApiAccessLogStreamName(this.#clock), [line]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
export function simHttpApiEndpoint(serving) {
|
|
19
19
|
const { api, match, domainName } = serving;
|
|
20
20
|
return {
|
|
21
|
+
...(serving.requestId !== undefined && { requestId: serving.requestId }),
|
|
21
22
|
apiId: api.apiId,
|
|
22
23
|
domainName,
|
|
23
24
|
// The first label, which is the API id for a generated endpoint and the
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SimHttpApiAdmitted } from "../api/authorizer/sim-http-api-authorization.js";
|
|
2
|
+
import type { SimHttpApiMatch } from "../api/sim-http-api-match.js";
|
|
3
|
+
import type { SimHttpApi } from "../api/sim-http-api.js";
|
|
4
|
+
/**
|
|
5
|
+
* One request an integration is invoked for, as the endpoint resolved it.
|
|
6
|
+
*/
|
|
7
|
+
export interface SimHttpApiIntegrationInvocationInput {
|
|
8
|
+
readonly api: SimHttpApi;
|
|
9
|
+
readonly match: SimHttpApiMatch;
|
|
10
|
+
readonly request: Request;
|
|
11
|
+
/** What the route's authorization knows about the caller. */
|
|
12
|
+
readonly authorization: SimHttpApiAdmitted;
|
|
13
|
+
/** The AWS-shaped hostname the request arrived on. */
|
|
14
|
+
readonly domainName: string;
|
|
15
|
+
/** The path the invocation event reports, which the resolver settled. */
|
|
16
|
+
readonly rawPath: string;
|
|
17
|
+
/**
|
|
18
|
+
* The id the endpoint stamped this request with, which the invocation event
|
|
19
|
+
* reports as its own.
|
|
20
|
+
*/
|
|
21
|
+
readonly requestId?: string | undefined;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
import type { SimClock } from "../../../util/clock/sim-clock.js";
|
|
2
|
-
import type { SimHttpApiAdmitted } from "../api/authorizer/sim-http-api-authorization.js";
|
|
3
|
-
import type { SimHttpApiMatch } from "../api/sim-http-api-match.js";
|
|
4
|
-
import type { SimHttpApi } from "../api/sim-http-api.js";
|
|
5
2
|
import type { SimApiGatewayV2Router } from "./sim-api-gateway-v2-router.js";
|
|
3
|
+
import type { SimHttpApiIntegrationInvocationInput } from "./sim-http-api-integration-input.js";
|
|
4
|
+
import { type SimHttpApiIntegrationOutcome } from "./sim-http-api-integration-outcome.js";
|
|
6
5
|
interface SimHttpApiIntegrationInvocationProperties {
|
|
7
6
|
readonly router: SimApiGatewayV2Router;
|
|
8
7
|
/** Clock the invocation event is stamped with. */
|
|
9
8
|
readonly clock: SimClock;
|
|
10
9
|
}
|
|
11
|
-
interface SimHttpApiIntegrationInvocationInput {
|
|
12
|
-
readonly api: SimHttpApi;
|
|
13
|
-
readonly match: SimHttpApiMatch;
|
|
14
|
-
readonly request: Request;
|
|
15
|
-
/** What the route's authorization knows about the caller. */
|
|
16
|
-
readonly authorization: SimHttpApiAdmitted;
|
|
17
|
-
/** The AWS-shaped hostname the request arrived on. */
|
|
18
|
-
readonly domainName: string;
|
|
19
|
-
/** The path the invocation event reports, which the resolver settled. */
|
|
20
|
-
readonly rawPath: string;
|
|
21
|
-
}
|
|
22
10
|
/**
|
|
23
11
|
* Invokes the Lambda function behind the matched route's integration, and
|
|
24
12
|
* turns what it returns into the HTTP response.
|
|
@@ -37,13 +25,8 @@ export declare class SimHttpApiIntegrationInvocation {
|
|
|
37
25
|
/**
|
|
38
26
|
* Invoke the integration for one authorized request.
|
|
39
27
|
*/
|
|
40
|
-
invoke(input: SimHttpApiIntegrationInvocationInput): Promise<
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
*
|
|
44
|
-
* The route the request matched is supplied as the source ARN, so a
|
|
45
|
-
* permission granted for one route does not open another.
|
|
46
|
-
*/
|
|
47
|
-
private mayNotInvoke;
|
|
28
|
+
invoke(input: SimHttpApiIntegrationInvocationInput): Promise<SimHttpApiIntegrationOutcome>;
|
|
29
|
+
/** The 500 answered whenever a handler was never reached. */
|
|
30
|
+
private failed;
|
|
48
31
|
}
|
|
49
32
|
export {};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { SimPayload2EventBuilder } from "../../../serve/payload-2/sim-payload-2-event-builder.js";
|
|
2
2
|
import { SimPayload2ResponseBuilder } from "../../../serve/payload-2/sim-payload-2-response-builder.js";
|
|
3
|
-
import { SimHttpApiExecuteApiArn } from "../api/sim-http-api-execute-api-arn.js";
|
|
4
|
-
import { SimHttpApiInvokeAuthorizer } from "./auth/sim-http-api-invoke-authorizer.js";
|
|
5
3
|
import { SimApiGatewayV2ErrorResponse } from "./sim-api-gateway-v2-error-response.js";
|
|
4
|
+
import { simHttpApiMayNotInvoke } from "./sim-http-api-may-invoke.js";
|
|
6
5
|
import { simHttpApiEndpoint } from "./sim-http-api-endpoint.js";
|
|
6
|
+
import { simHttpApiIntegrationFailure, } from "./sim-http-api-integration-outcome.js";
|
|
7
7
|
/**
|
|
8
8
|
* Invokes the Lambda function behind the matched route's integration, and
|
|
9
9
|
* turns what it returns into the HTTP response.
|
|
@@ -30,11 +30,11 @@ export class SimHttpApiIntegrationInvocation {
|
|
|
30
30
|
async invoke(input) {
|
|
31
31
|
const { match, request, authorization } = input;
|
|
32
32
|
const target = this.router.targetFor(match.integration);
|
|
33
|
-
if (target === undefined ||
|
|
33
|
+
if (target === undefined || simHttpApiMayNotInvoke(input, target)) {
|
|
34
34
|
// The integration names a function that is not there, or one that
|
|
35
35
|
// granted the API nothing. Real API Gateway discovers both only when it
|
|
36
36
|
// tries to invoke, answers 500, and leaves the reason in its own logs.
|
|
37
|
-
return this.
|
|
37
|
+
return this.failed("The integration could not be invoked");
|
|
38
38
|
}
|
|
39
39
|
try {
|
|
40
40
|
const event = await this.eventBuilder.build(request, simHttpApiEndpoint(input), {
|
|
@@ -42,28 +42,25 @@ export class SimHttpApiIntegrationInvocation {
|
|
|
42
42
|
caller: authorization.caller,
|
|
43
43
|
lambda: authorization.lambda,
|
|
44
44
|
});
|
|
45
|
-
|
|
45
|
+
const response = this.responseBuilder.build(await target.simFunction.invoke(event));
|
|
46
|
+
// Lambda answered the invocation, whatever the handler then returned.
|
|
47
|
+
// That is the distinction between the two statuses AWS logs.
|
|
48
|
+
return {
|
|
49
|
+
response,
|
|
50
|
+
integrationStatus: response.status,
|
|
51
|
+
lambdaInvokeStatus: 200,
|
|
52
|
+
};
|
|
46
53
|
}
|
|
47
|
-
catch {
|
|
54
|
+
catch (error) {
|
|
48
55
|
// Real API Gateway reports an unhandled integration error as a 500, with
|
|
49
56
|
// the error itself only visible in the function's logs. Reading the
|
|
50
57
|
// request body can fail the same way, so building the event is inside
|
|
51
58
|
// this too.
|
|
52
|
-
return this.
|
|
59
|
+
return this.failed(error);
|
|
53
60
|
}
|
|
54
61
|
}
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
* The route the request matched is supplied as the source ARN, so a
|
|
59
|
-
* permission granted for one route does not open another.
|
|
60
|
-
*/
|
|
61
|
-
mayNotInvoke(input, target) {
|
|
62
|
-
const { api, match, request } = input;
|
|
63
|
-
return new SimHttpApiInvokeAuthorizer({ iam: target.iam }).authorize({
|
|
64
|
-
api,
|
|
65
|
-
target,
|
|
66
|
-
sourceArn: SimHttpApiExecuteApiArn.forMatchedRoute(api, match, request.method),
|
|
67
|
-
}).isDenied;
|
|
62
|
+
/** The 500 answered whenever a handler was never reached. */
|
|
63
|
+
failed(error) {
|
|
64
|
+
return simHttpApiIntegrationFailure(this.errorResponse.internalServerError(), error);
|
|
68
65
|
}
|
|
69
66
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What invoking an integration produced, beyond the response the client gets.
|
|
3
|
+
*
|
|
4
|
+
* The two statuses are the pair AWS distinguishes in its access log variables.
|
|
5
|
+
* `lambdaInvokeStatus` is Lambda's own answer to the invocation, and
|
|
6
|
+
* `integrationStatus` the status the handler's code returned. An invocation
|
|
7
|
+
* that never reached a handler has neither.
|
|
8
|
+
*/
|
|
9
|
+
export interface SimHttpApiIntegrationOutcome {
|
|
10
|
+
readonly response: Response;
|
|
11
|
+
readonly integrationStatus?: number | undefined;
|
|
12
|
+
readonly lambdaInvokeStatus?: number | undefined;
|
|
13
|
+
readonly integrationErrorMessage?: string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Simulated milliseconds the invocation took. A test holding the clock still
|
|
16
|
+
* measures zero, the way the whole request's latency does.
|
|
17
|
+
*/
|
|
18
|
+
readonly integrationLatency?: number | undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The 500 every way of failing to reach a handler produces, carrying the
|
|
22
|
+
* reason the access log reports as `$context.integrationErrorMessage`.
|
|
23
|
+
*
|
|
24
|
+
* The message never reaches the client. Real API Gateway answers the same
|
|
25
|
+
* body whatever went wrong, and leaves the reason in its own logs.
|
|
26
|
+
*/
|
|
27
|
+
export declare function simHttpApiIntegrationFailure(response: Response, error: unknown): SimHttpApiIntegrationOutcome;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 500 every way of failing to reach a handler produces, carrying the
|
|
3
|
+
* reason the access log reports as `$context.integrationErrorMessage`.
|
|
4
|
+
*
|
|
5
|
+
* The message never reaches the client. Real API Gateway answers the same
|
|
6
|
+
* body whatever went wrong, and leaves the reason in its own logs.
|
|
7
|
+
*/
|
|
8
|
+
export function simHttpApiIntegrationFailure(response, error) {
|
|
9
|
+
return {
|
|
10
|
+
response,
|
|
11
|
+
integrationErrorMessage: error instanceof Error ? error.message : String(error),
|
|
12
|
+
};
|
|
13
|
+
}
|