-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
endpoint.js
131 lines (109 loc) · 3.68 KB
/
endpoint.js
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
import { to_headers } from '../../utils/http.js';
import { hash } from '../hash.js';
import { is_pojo, normalize_request_method } from './utils.js';
/** @param {string} body */
function error(body) {
return new Response(body, {
status: 500
});
}
/** @param {unknown} s */
function is_string(s) {
return typeof s === 'string' || s instanceof String;
}
const text_types = new Set([
'application/xml',
'application/json',
'application/x-www-form-urlencoded',
'multipart/form-data'
]);
/**
* Decides how the body should be parsed based on its mime type
*
* @param {string | undefined | null} content_type The `content-type` header of a request/response.
* @returns {boolean}
*/
export function is_text(content_type) {
if (!content_type) return true; // defaults to json
const type = content_type.split(';')[0].toLowerCase(); // get the mime type
return type.startsWith('text/') || type.endsWith('+xml') || text_types.has(type);
}
/**
* @param {import('types').RequestEvent} event
* @param {{ [method: string]: import('types').RequestHandler }} mod
* @returns {Promise<Response>}
*/
export async function render_endpoint(event, mod) {
const method = normalize_request_method(event);
/** @type {import('types').RequestHandler} */
let handler = mod[method];
if (!handler && method === 'head') {
handler = mod.get;
}
if (!handler) {
const allowed = [];
for (const method in ['get', 'post', 'put', 'patch']) {
if (mod[method]) allowed.push(method.toUpperCase());
}
if (mod.del) allowed.push('DELETE');
if (mod.get || mod.head) allowed.push('HEAD');
return event.request.headers.get('x-sveltekit-load')
? // TODO would be nice to avoid these requests altogether,
// by noting whether or not page endpoints export `get`
new Response(undefined, {
status: 204
})
: new Response(`${event.request.method} method not allowed`, {
status: 405,
headers: {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
// "The server must generate an Allow header field in a 405 status code response"
allow: allowed.join(', ')
}
});
}
const response = await handler(event);
const preface = `Invalid response from route ${event.url.pathname}`;
if (typeof response !== 'object') {
return error(`${preface}: expected an object, got ${typeof response}`);
}
// TODO remove for 1.0
// @ts-expect-error
if (response.fallthrough) {
throw new Error(
'fallthrough is no longer supported. Use matchers instead: https://kit.svelte.dev/docs/routing#advanced-routing-matching'
);
}
const { status = 200, body = {} } = response;
const headers =
response.headers instanceof Headers
? new Headers(response.headers)
: to_headers(response.headers);
const type = headers.get('content-type');
if (!is_text(type) && !(body instanceof Uint8Array || is_string(body))) {
return error(
`${preface}: body must be an instance of string or Uint8Array if content-type is not a supported textual content-type`
);
}
/** @type {import('types').StrictBody} */
let normalized_body;
if (is_pojo(body) && (!type || type.startsWith('application/json'))) {
headers.set('content-type', 'application/json; charset=utf-8');
normalized_body = JSON.stringify(body);
} else {
normalized_body = /** @type {import('types').StrictBody} */ (body);
}
if (
(typeof normalized_body === 'string' || normalized_body instanceof Uint8Array) &&
!headers.has('etag')
) {
const cache_control = headers.get('cache-control');
if (!cache_control || !/(no-store|immutable)/.test(cache_control)) {
headers.set('etag', `"${hash(normalized_body)}"`);
}
}
return new Response(method !== 'head' ? normalized_body : undefined, {
status,
headers
});
}