Skip to content

Commit af68821

Browse files
committed
refactoring
1 parent 0cb6305 commit af68821

File tree

7 files changed

+124
-74
lines changed

7 files changed

+124
-74
lines changed

cli/bootstrap.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { log } from "../src/internal/log";
22
import packageJson from "../package.json";
3-
import { bootstrap } from "../src/internal/bootstrap";
3+
import { run } from "../src";
44

55
(async () => {
66
if (process.argv.includes("--version")) {
@@ -11,7 +11,7 @@ import { bootstrap } from "../src/internal/bootstrap";
1111
log("Starting bootstrap", { env: JSON.stringify(process.env) });
1212

1313
try {
14-
await bootstrap();
14+
await run();
1515
} catch (e) {
1616
if (e instanceof Error) {
1717
console.error(e.message);
@@ -20,5 +20,6 @@ import { bootstrap } from "../src/internal/bootstrap";
2020
}
2121
process.exit(1);
2222
}
23+
2324
log("Bootstrap complete");
2425
})();

src/index.ts

+38-6
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,50 @@
1-
import { endpointSpawn, endpointProxy } from "./internal/proxy";
2-
import { nextEvent, respondToEvent } from "./internal/runtime";
1+
import { pollForEvents } from "./internal/events";
2+
import { endpointSpawn, endpointProxy } from "./internal/endpoints";
3+
import { log } from "./internal/log";
4+
import { getRuntimeEvent, postRuntimeEventResponse } from "./internal/runtime";
35
import {
4-
LambdaEvent,
6+
RuntimeEvent,
57
EndpointExecRequest,
68
EndpointProxyRequest,
79
EndpointResponse,
810
} from "./internal/types";
911

12+
const { _HANDLER, AWS_LAMBDA_RUNTIME_API } = process.env;
13+
14+
export const run = async (): Promise<void> => {
15+
if (!AWS_LAMBDA_RUNTIME_API) {
16+
throw new Error("No AWS_LAMBDA_RUNTIME_API specified");
17+
}
18+
19+
if (!_HANDLER) {
20+
throw new Error("No handler specified");
21+
}
22+
23+
log("Bootstraping", { _HANDLER, AWS_LAMBDA_RUNTIME_API });
24+
25+
const { childProcess, bin, endpoint } = await endpointSpawn(
26+
_HANDLER,
27+
process.env
28+
);
29+
30+
try {
31+
log("Polling for events", { bin, endpoint });
32+
await pollForEvents(AWS_LAMBDA_RUNTIME_API, bin, endpoint);
33+
} catch (e) {
34+
if (childProcess) {
35+
log("Killing child process", { pid: childProcess.pid });
36+
childProcess.kill();
37+
}
38+
throw e;
39+
}
40+
};
41+
1042
export {
1143
endpointSpawn,
1244
endpointProxy,
13-
nextEvent,
14-
respondToEvent,
15-
LambdaEvent,
45+
getRuntimeEvent,
46+
postRuntimeEventResponse,
47+
RuntimeEvent,
1648
EndpointExecRequest,
1749
EndpointProxyRequest,
1850
EndpointResponse,

src/internal/bootstrap.ts

-33
This file was deleted.

src/internal/proxy.ts renamed to src/internal/endpoints.ts

+55-17
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,24 @@ import {
99
EndpointExecRequest,
1010
EndpointProxyRequest,
1111
EndpointResponse,
12+
SpawnResult,
1213
} from "./types";
1314
import { info, log } from "./log";
1415
import { ChildProcess, spawn } from "child_process";
16+
import { APIGatewayProxyEventV2 } from "aws-lambda";
17+
18+
function wsify(url?: URL): URL | undefined {
19+
if (!url) return undefined;
20+
21+
const wsUrl = new URL(url.toString());
22+
wsUrl.protocol = url.protocol.replace("http", "ws");
23+
24+
if (!wsUrl.protocol.startsWith("ws")) {
25+
return undefined;
26+
}
27+
28+
return wsUrl;
29+
}
1530

1631
function convertHeaders(
1732
headers: RawAxiosResponseHeaders | AxiosResponseHeaders
@@ -78,12 +93,9 @@ const waitForEndpoint = async (
7893

7994
export const endpointSpawn = async (
8095
handler: string,
81-
env?: NodeJS.ProcessEnv
82-
): Promise<{
83-
childProcess?: ChildProcess;
84-
bin?: string;
85-
endpoint?: URL;
86-
}> => {
96+
env?: NodeJS.ProcessEnv,
97+
detached: boolean = true
98+
): Promise<SpawnResult> => {
8799
// handler is in the format of
88100
// - `{some-bin}@http://localhost:{the-bins-port} (will start some-bin, and forward requests to the http server)
89101
// - `http://localhost:{some-port}` (will forward the request to the http server)
@@ -109,20 +121,26 @@ export const endpointSpawn = async (
109121

110122
const cmds = bin.split(" ");
111123
childProcess = spawn(cmds[0], cmds.slice(1), {
112-
detached: true,
124+
detached,
113125
stdio: "inherit",
114126
env: env,
115127
});
116128

117-
// TODO Decide if we should do this...
118-
childProcess.unref();
129+
if (detached) {
130+
childProcess.unref();
131+
}
119132

120133
log("Started child process", { cmds, pid: childProcess.pid });
121134
}
122135

123136
endpoint = endpoint ? new URL(endpoint) : undefined;
124137

125-
return { childProcess, bin, endpoint };
138+
return {
139+
childProcess,
140+
bin,
141+
endpoint,
142+
wsEndpoint: wsify(endpoint),
143+
};
126144
};
127145

128146
export const endpointExec = async ({
@@ -133,12 +151,21 @@ export const endpointExec = async ({
133151
}: EndpointExecRequest): Promise<EndpointResponse> => {
134152
const { execa } = await import("execa");
135153

136-
const { stdout } = await execa({
154+
const timeout = deadline - Date.now();
155+
156+
info(`Running: \`${bin}\``);
157+
158+
const subprocess = execa({
137159
stderr: ["inherit"],
138160
})`${bin} ${event}`;
139161

140-
// TODO: handle deadline
141-
info("TODO: need to handle deadline", { deadline });
162+
setTimeout(() => {
163+
subprocess.kill(
164+
Error(`${bin} took longer than ${timeout} milliseconds to start.`)
165+
);
166+
}, timeout);
167+
168+
const { stdout } = await subprocess;
142169

143170
const payload = JSON.parse(stdout);
144171

@@ -154,25 +181,36 @@ export const endpointProxy = async ({
154181
event,
155182
deadline,
156183
}: EndpointProxyRequest): Promise<EndpointResponse> => {
184+
const rawEvent = JSON.parse(event) as Partial<APIGatewayProxyEventV2>;
185+
157186
const {
158187
requestContext,
159188
rawPath,
160189
rawQueryString,
161190
headers: rawHeaders,
162191
body: rawBody,
163192
isBase64Encoded,
164-
} = event;
193+
} = rawEvent;
194+
195+
if (!requestContext) {
196+
throw new Error("No request context found in event");
197+
}
198+
165199
const method = requestContext.http.method;
166200

167201
log("Waiting for endpoint to start", { endpoint, deadline });
168202
const { timeout } = await waitForEndpoint(endpoint, deadline);
169203

170204
if (!timeout) {
171205
throw new Error(
172-
`${endpoint.toString()} took longer than ${deadline} milliseconds to start.`
206+
`${endpoint.toString()} took longer than ${timeout} milliseconds to start.`
173207
);
174208
}
175209

210+
if (!rawPath) {
211+
throw new Error("No path found in event");
212+
}
213+
176214
const url = new URL(rawPath, endpoint);
177215
if (rawQueryString) {
178216
url.search = new URLSearchParams(rawQueryString).toString();
@@ -181,7 +219,7 @@ export const endpointProxy = async ({
181219
const decodedBody =
182220
isBase64Encoded && rawBody ? Buffer.from(rawBody, "base64") : rawBody;
183221

184-
log("Proxying request", { url, method, rawHeaders, decodedBody, timeout });
222+
log("Proxying request", { url, method, rawHeaders, timeout });
185223

186224
let response: AxiosResponse<any, any> | undefined = undefined;
187225
try {
@@ -207,7 +245,7 @@ export const endpointProxy = async ({
207245

208246
const { data: rawData, headers: rawResponseHeaders } = response;
209247

210-
log("Proxy request complete", { url, method, rawResponseHeaders, rawData });
248+
log("Proxy request complete", { url, method, rawResponseHeaders });
211249

212250
return {
213251
requestId,

src/internal/routing.ts renamed to src/internal/events.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import { EndpointProxyRequest, EndpointExecRequest } from "./types";
2-
import { endpointExec, endpointProxy } from "./proxy";
31
import { log } from "./log";
4-
import { nextEvent, respondToEvent } from "./runtime";
2+
import { EndpointProxyRequest, EndpointExecRequest } from "./types";
3+
import { endpointExec, endpointProxy } from "./endpoints";
4+
import { getRuntimeEvent, postRuntimeEventResponse } from "./runtime";
55

6-
export const routeEvents = async (
6+
export const pollForEvents = async (
77
runtimeApi: string,
88
bin?: string,
99
endpoint?: URL
1010
): Promise<void> => {
1111
log("Waiting for next event from Lambda Runtime API", { runtimeApi });
1212

13-
const { requestId, event, deadline } = await nextEvent(runtimeApi);
13+
const { requestId, event, deadline } = await getRuntimeEvent(runtimeApi);
1414

1515
let payload: any | undefined = undefined;
1616

@@ -53,9 +53,9 @@ Expected format: {bin}@{endpoint} or {bin} or {endpoint}:
5353
);
5454
}
5555

56-
await respondToEvent(runtimeApi, requestId, payload);
56+
await postRuntimeEventResponse(runtimeApi, requestId, payload);
5757

5858
log("Response sent to Lambda Runtime API", { runtimeApi, requestId });
5959

60-
return routeEvents(runtimeApi, bin, endpoint);
60+
return pollForEvents(runtimeApi, bin, endpoint);
6161
};

src/internal/runtime.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import axios from "axios";
2-
import { LambdaEvent } from "./types";
2+
import { RuntimeEvent } from "./types";
33

4-
export const nextEvent = async (runtimeApi: string): Promise<LambdaEvent> => {
5-
const { headers, data } = await axios.get(
4+
export const getRuntimeEvent = async (
5+
runtimeApi: string
6+
): Promise<RuntimeEvent> => {
7+
const { headers, data: event } = await axios.get(
68
`http://${runtimeApi}/2018-06-01/runtime/invocation/next`,
79
{
810
// block indefinitely until a response is received
@@ -19,12 +21,14 @@ export const nextEvent = async (runtimeApi: string): Promise<LambdaEvent> => {
1921

2022
const deadline = Number.parseInt(headers["lambda-runtime-deadline-ms"]);
2123

22-
const event = JSON.parse(data);
24+
if (!event || typeof event !== "string") {
25+
throw new Error("No event found in response data");
26+
}
2327

2428
return { requestId, event, deadline };
2529
};
2630

27-
export const respondToEvent = async (
31+
export const postRuntimeEventResponse = async (
2832
runtimeApi: string,
2933
requestId: string,
3034
payload: any

src/internal/types.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
11
import { APIGatewayProxyResult } from "aws-lambda";
2+
import { ChildProcess } from "child_process";
23

3-
export type LambdaEvent = {
4+
export type SpawnResult = {
5+
childProcess?: ChildProcess;
6+
bin?: string;
7+
endpoint?: URL;
8+
wsEndpoint?: URL;
9+
};
10+
11+
export type RuntimeEvent = {
412
requestId: string;
5-
event: any;
13+
event: string;
614
deadline: number;
715
};
816

917
export type EndpointExecRequest = {
1018
requestId: string;
1119
bin: string;
12-
event: any;
20+
event: string;
1321
deadline: number;
1422
};
1523

1624
export type EndpointProxyRequest = {
1725
requestId: string;
1826
endpoint: URL;
19-
event: any;
27+
event: string;
2028
deadline: number;
2129
};
2230

0 commit comments

Comments
 (0)