-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
server.ts
156 lines (130 loc) · 3.96 KB
/
server.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import {
createRequestHandler as createRemixRequestHandler,
Headers as NodeHeaders,
Request as NodeRequest,
readableStreamToString,
} from "@remix-run/node";
import type {
Handler,
HandlerEvent,
HandlerContext,
HandlerResponse,
} from "@netlify/functions";
import type {
AppLoadContext,
ServerBuild,
RequestInit as NodeRequestInit,
Response as NodeResponse,
} from "@remix-run/node";
import { isBinaryType } from "./binaryTypes";
/**
* A function that returns the value to use as `context` in route `loader` and
* `action` functions.
*
* You can think of this as an escape hatch that allows you to pass
* environment/platform-specific values through to your loader/action.
*/
export type GetLoadContextFunction = (
event: HandlerEvent,
context: HandlerContext
) => AppLoadContext;
export type RequestHandler = Handler;
export function createRequestHandler({
build,
getLoadContext,
mode = process.env.NODE_ENV,
}: {
build: ServerBuild;
getLoadContext?: GetLoadContextFunction;
mode?: string;
}): RequestHandler {
let handleRequest = createRemixRequestHandler(build, mode);
return async (event, context) => {
let request = createRemixRequest(event);
let loadContext = getLoadContext?.(event, context);
let response = (await handleRequest(request, loadContext)) as NodeResponse;
return sendRemixResponse(response);
};
}
export function createRemixRequest(event: HandlerEvent): NodeRequest {
let url: URL;
if (process.env.NODE_ENV !== "development") {
url = new URL(event.rawUrl);
} else {
let origin = event.headers.host;
let rawPath = getRawPath(event);
url = new URL(rawPath, `http://${origin}`);
}
// Note: No current way to abort these for Netlify, but our router expects
// requests to contain a signal so it can detect aborted requests
let controller = new AbortController();
let init: NodeRequestInit = {
method: event.httpMethod,
headers: createRemixHeaders(event.multiValueHeaders),
signal: controller.signal,
};
if (event.httpMethod !== "GET" && event.httpMethod !== "HEAD" && event.body) {
let isFormData = event.headers["content-type"]?.includes(
"multipart/form-data"
);
init.body = event.isBase64Encoded
? isFormData
? Buffer.from(event.body, "base64")
: Buffer.from(event.body, "base64").toString()
: event.body;
}
return new NodeRequest(url.href, init);
}
export function createRemixHeaders(
requestHeaders: HandlerEvent["multiValueHeaders"]
): NodeHeaders {
let headers = new NodeHeaders();
for (let [key, values] of Object.entries(requestHeaders)) {
if (values) {
for (let value of values) {
headers.append(key, value);
}
}
}
return headers;
}
// `netlify dev` doesn't return the full url in the event.rawUrl, so we need to create it ourselves
function getRawPath(event: HandlerEvent): string {
let rawPath = event.path;
let searchParams = new URLSearchParams();
if (!event.multiValueQueryStringParameters) {
return rawPath;
}
let paramKeys = Object.keys(event.multiValueQueryStringParameters);
for (let key of paramKeys) {
let values = event.multiValueQueryStringParameters[key];
if (!values) continue;
for (let val of values) {
searchParams.append(key, val);
}
}
let rawParams = searchParams.toString();
if (rawParams) rawPath += `?${rawParams}`;
return rawPath;
}
export async function sendRemixResponse(
nodeResponse: NodeResponse
): Promise<HandlerResponse> {
let contentType = nodeResponse.headers.get("Content-Type");
let body: string | undefined;
let isBase64Encoded = isBinaryType(contentType);
if (nodeResponse.body) {
if (isBase64Encoded) {
body = await readableStreamToString(nodeResponse.body, "base64");
} else {
body = await nodeResponse.text();
}
}
let multiValueHeaders = nodeResponse.headers.raw();
return {
statusCode: nodeResponse.status,
multiValueHeaders,
body,
isBase64Encoded,
};
}