@sveltejs/kit 1.0.0-next.455 → 1.0.0-next.458
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/package.json +1 -1
- package/src/core/sync/write_types/index.js +1 -2
- package/src/runtime/client/client.js +8 -15
- package/src/runtime/client/utils.js +37 -10
- package/src/runtime/server/endpoint.js +7 -10
- package/src/runtime/server/index.js +118 -146
- package/src/runtime/server/page/index.js +1 -1
- package/src/runtime/server/utils.js +32 -0
package/package.json
CHANGED
|
@@ -71,8 +71,7 @@ export async function write_types(config, manifest_data, file) {
|
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const
|
|
75
|
-
const id = path.dirname(filepath);
|
|
74
|
+
const id = path.relative(config.kit.files.routes, path.dirname(file));
|
|
76
75
|
|
|
77
76
|
const route = manifest_data.routes.find((route) => route.id === id);
|
|
78
77
|
if (!route) return; // this shouldn't ever happen
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { onMount, tick } from 'svelte';
|
|
2
2
|
import { normalize_error } from '../../utils/error.js';
|
|
3
3
|
import { make_trackable, decode_params, normalize_path } from '../../utils/url.js';
|
|
4
|
-
import { find_anchor, get_base_uri,
|
|
4
|
+
import { find_anchor, get_base_uri, scroll_state } from './utils.js';
|
|
5
5
|
import { lock_fetch, unlock_fetch, initial_fetch, native_fetch } from './fetcher.js';
|
|
6
6
|
import { parse } from './parse.js';
|
|
7
7
|
import { error } from '../../exports/index.js';
|
|
@@ -1139,9 +1139,9 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
1139
1139
|
|
|
1140
1140
|
/** @param {Event} event */
|
|
1141
1141
|
const trigger_prefetch = (event) => {
|
|
1142
|
-
const
|
|
1143
|
-
if (
|
|
1144
|
-
prefetch(
|
|
1142
|
+
const { url, options } = find_anchor(event);
|
|
1143
|
+
if (url && options.prefetch === '') {
|
|
1144
|
+
prefetch(url);
|
|
1145
1145
|
}
|
|
1146
1146
|
};
|
|
1147
1147
|
|
|
@@ -1172,13 +1172,10 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
1172
1172
|
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
1173
1173
|
if (event.defaultPrevented) return;
|
|
1174
1174
|
|
|
1175
|
-
const a = find_anchor(event);
|
|
1176
|
-
if (!a) return;
|
|
1177
|
-
|
|
1178
|
-
if (!a.href) return;
|
|
1175
|
+
const { a, url, options } = find_anchor(event);
|
|
1176
|
+
if (!a || !url) return;
|
|
1179
1177
|
|
|
1180
1178
|
const is_svg_a_element = a instanceof SVGAElement;
|
|
1181
|
-
const url = get_href(a);
|
|
1182
1179
|
|
|
1183
1180
|
// Ignore non-HTTP URL protocols (e.g. `mailto:`, `tel:`, `myapp:`, etc.)
|
|
1184
1181
|
// MEMO: Without this condition, firefox will open mailer twice.
|
|
@@ -1192,11 +1189,7 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
1192
1189
|
// 2. 'rel' attribute includes external
|
|
1193
1190
|
const rel = (a.getAttribute('rel') || '').split(/\s+/);
|
|
1194
1191
|
|
|
1195
|
-
if (
|
|
1196
|
-
a.hasAttribute('download') ||
|
|
1197
|
-
rel.includes('external') ||
|
|
1198
|
-
a.hasAttribute('data-sveltekit-reload')
|
|
1199
|
-
) {
|
|
1192
|
+
if (a.hasAttribute('download') || rel.includes('external') || options.reload === '') {
|
|
1200
1193
|
return;
|
|
1201
1194
|
}
|
|
1202
1195
|
|
|
@@ -1222,7 +1215,7 @@ export function create_client({ target, base, trailing_slash }) {
|
|
|
1222
1215
|
|
|
1223
1216
|
navigate({
|
|
1224
1217
|
url,
|
|
1225
|
-
scroll:
|
|
1218
|
+
scroll: options.noscroll === '' ? scroll_state() : null,
|
|
1226
1219
|
keepfocus: false,
|
|
1227
1220
|
redirect_chain: [],
|
|
1228
1221
|
details: {
|
|
@@ -24,17 +24,44 @@ export function scroll_state() {
|
|
|
24
24
|
|
|
25
25
|
/** @param {Event} event */
|
|
26
26
|
export function find_anchor(event) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
27
|
+
/** @type {HTMLAnchorElement | SVGAElement | undefined} */
|
|
28
|
+
let a;
|
|
29
|
+
|
|
30
|
+
const options = {
|
|
31
|
+
/** @type {string | null} */
|
|
32
|
+
noscroll: null,
|
|
33
|
+
|
|
34
|
+
/** @type {string | null} */
|
|
35
|
+
prefetch: null,
|
|
36
|
+
|
|
37
|
+
/** @type {string | null} */
|
|
38
|
+
reload: null
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
for (const element of event.composedPath()) {
|
|
42
|
+
if (!(element instanceof Element)) continue;
|
|
43
|
+
|
|
44
|
+
if (!a && element.nodeName.toUpperCase() === 'A') {
|
|
45
|
+
// SVG <a> elements have a lowercase name
|
|
46
|
+
a = /** @type {HTMLAnchorElement | SVGAElement} */ (element);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (options.noscroll === null) {
|
|
50
|
+
options.noscroll = element.getAttribute('data-sveltekit-noscroll');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (options.prefetch === null) {
|
|
54
|
+
options.prefetch = element.getAttribute('data-sveltekit-prefetch');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (options.reload === null) {
|
|
58
|
+
options.reload = element.getAttribute('data-sveltekit-reload');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const url = a && new URL(a instanceof SVGAElement ? a.href.baseVal : a.href, document.baseURI);
|
|
32
63
|
|
|
33
|
-
|
|
34
|
-
export function get_href(node) {
|
|
35
|
-
return node instanceof SVGAElement
|
|
36
|
-
? new URL(node.href.baseVal, document.baseURI)
|
|
37
|
-
: new URL(node.href);
|
|
64
|
+
return { a, url, options };
|
|
38
65
|
}
|
|
39
66
|
|
|
40
67
|
/** @param {any} value */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Redirect } from '../control.js';
|
|
2
2
|
import { check_method_names, method_not_allowed } from './utils.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -39,9 +39,8 @@ export async function render_endpoint(event, mod, state) {
|
|
|
39
39
|
);
|
|
40
40
|
|
|
41
41
|
if (!(response instanceof Response)) {
|
|
42
|
-
|
|
43
|
-
`Invalid response from route ${event.url.pathname}: handler should return a Response object
|
|
44
|
-
{ status: 500 }
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Invalid response from route ${event.url.pathname}: handler should return a Response object`
|
|
45
44
|
);
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -52,15 +51,13 @@ export async function render_endpoint(event, mod, state) {
|
|
|
52
51
|
|
|
53
52
|
return response;
|
|
54
53
|
} catch (error) {
|
|
55
|
-
if (error instanceof
|
|
56
|
-
return new Response(error.message, { status: error.status });
|
|
57
|
-
} else if (error instanceof Redirect) {
|
|
54
|
+
if (error instanceof Redirect) {
|
|
58
55
|
return new Response(undefined, {
|
|
59
56
|
status: error.status,
|
|
60
|
-
headers: {
|
|
57
|
+
headers: { location: error.location }
|
|
61
58
|
});
|
|
62
|
-
} else {
|
|
63
|
-
throw error;
|
|
64
59
|
}
|
|
60
|
+
|
|
61
|
+
throw error;
|
|
65
62
|
}
|
|
66
63
|
}
|
|
@@ -3,10 +3,9 @@ import { render_page } from './page/index.js';
|
|
|
3
3
|
import { render_response } from './page/render.js';
|
|
4
4
|
import { respond_with_error } from './page/respond_with_error.js';
|
|
5
5
|
import { coalesce_to_error } from '../../utils/error.js';
|
|
6
|
-
import {
|
|
6
|
+
import { GENERIC_ERROR, handle_fatal_error } from './utils.js';
|
|
7
7
|
import { decode_params, disable_search, normalize_path } from '../../utils/url.js';
|
|
8
8
|
import { exec } from '../../utils/routing.js';
|
|
9
|
-
import { negotiate } from '../../utils/http.js';
|
|
10
9
|
import { render_data } from './data/index.js';
|
|
11
10
|
import { DATA_SUFFIX } from '../../constants.js';
|
|
12
11
|
|
|
@@ -190,178 +189,151 @@ export async function respond(request, options, state) {
|
|
|
190
189
|
transformPageChunk: default_transform
|
|
191
190
|
};
|
|
192
191
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
// @ts-expect-error
|
|
208
|
-
if (opts.ssr) {
|
|
209
|
-
throw new Error(
|
|
210
|
-
'ssr has been removed, set it in the appropriate +layout.js instead. See the PR for more information: https://github.com/sveltejs/kit/pull/6197'
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
resolve_opts = {
|
|
215
|
-
transformPageChunk: opts.transformPageChunk || default_transform
|
|
216
|
-
};
|
|
192
|
+
/**
|
|
193
|
+
*
|
|
194
|
+
* @param {import('types').RequestEvent} event
|
|
195
|
+
* @param {import('types').ResolveOptions} [opts]
|
|
196
|
+
*/
|
|
197
|
+
async function resolve(event, opts) {
|
|
198
|
+
try {
|
|
199
|
+
if (opts) {
|
|
200
|
+
// TODO remove for 1.0
|
|
201
|
+
if ('transformPage' in opts) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
'transformPage has been replaced by transformPageChunk — see https://github.com/sveltejs/kit/pull/5657 for more information'
|
|
204
|
+
);
|
|
217
205
|
}
|
|
218
206
|
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
state,
|
|
224
|
-
page_config: { ssr: false, csr: true },
|
|
225
|
-
status: 200,
|
|
226
|
-
error: null,
|
|
227
|
-
branch: [],
|
|
228
|
-
fetched: [],
|
|
229
|
-
validation_errors: undefined,
|
|
230
|
-
cookies: [],
|
|
231
|
-
resolve_opts
|
|
232
|
-
});
|
|
207
|
+
if ('ssr' in opts) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
'ssr has been removed, set it in the appropriate +layout.js instead. See the PR for more information: https://github.com/sveltejs/kit/pull/6197'
|
|
210
|
+
);
|
|
233
211
|
}
|
|
234
212
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (is_data_request) {
|
|
240
|
-
response = await render_data(event, route, options, state);
|
|
241
|
-
} else if (route.page) {
|
|
242
|
-
response = await render_page(event, route, route.page, options, state, resolve_opts);
|
|
243
|
-
} else if (route.endpoint) {
|
|
244
|
-
response = await render_endpoint(event, await route.endpoint(), state);
|
|
245
|
-
} else {
|
|
246
|
-
// a route will always have a page or an endpoint, but TypeScript
|
|
247
|
-
// doesn't know that
|
|
248
|
-
throw new Error('This should never happen');
|
|
249
|
-
}
|
|
213
|
+
resolve_opts = {
|
|
214
|
+
transformPageChunk: opts.transformPageChunk || default_transform
|
|
215
|
+
};
|
|
216
|
+
}
|
|
250
217
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
218
|
+
if (state.prerendering?.fallback) {
|
|
219
|
+
return await render_response({
|
|
220
|
+
event,
|
|
221
|
+
options,
|
|
222
|
+
state,
|
|
223
|
+
page_config: { ssr: false, csr: true },
|
|
224
|
+
status: 200,
|
|
225
|
+
error: null,
|
|
226
|
+
branch: [],
|
|
227
|
+
fetched: [],
|
|
228
|
+
validation_errors: undefined,
|
|
229
|
+
cookies: [],
|
|
230
|
+
resolve_opts
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (route) {
|
|
235
|
+
/** @type {Response} */
|
|
236
|
+
let response;
|
|
259
237
|
|
|
260
|
-
|
|
261
|
-
|
|
238
|
+
if (is_data_request) {
|
|
239
|
+
response = await render_data(event, route, options, state);
|
|
240
|
+
} else if (route.page) {
|
|
241
|
+
response = await render_page(event, route, route.page, options, state, resolve_opts);
|
|
242
|
+
} else if (route.endpoint) {
|
|
243
|
+
response = await render_endpoint(event, await route.endpoint(), state);
|
|
244
|
+
} else {
|
|
245
|
+
// a route will always have a page or an endpoint, but TypeScript
|
|
246
|
+
// doesn't know that
|
|
247
|
+
throw new Error('This should never happen');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!is_data_request) {
|
|
251
|
+
// we only want to set cookies on __data.js requests, we don't
|
|
252
|
+
// want to cache stuff erroneously etc
|
|
253
|
+
for (const key in headers) {
|
|
254
|
+
const value = headers[key];
|
|
255
|
+
response.headers.set(key, /** @type {string} */ (value));
|
|
262
256
|
}
|
|
257
|
+
}
|
|
263
258
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
259
|
+
for (const cookie of cookies) {
|
|
260
|
+
response.headers.append('set-cookie', cookie);
|
|
261
|
+
}
|
|
267
262
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
263
|
+
// respond with 304 if etag matches
|
|
264
|
+
if (response.status === 200 && response.headers.has('etag')) {
|
|
265
|
+
let if_none_match_value = request.headers.get('if-none-match');
|
|
272
266
|
|
|
273
|
-
|
|
267
|
+
// ignore W/ prefix https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match#directives
|
|
268
|
+
if (if_none_match_value?.startsWith('W/"')) {
|
|
269
|
+
if_none_match_value = if_none_match_value.substring(2);
|
|
270
|
+
}
|
|
274
271
|
|
|
275
|
-
|
|
276
|
-
const headers = new Headers({ etag });
|
|
272
|
+
const etag = /** @type {string} */ (response.headers.get('etag'));
|
|
277
273
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const value = response.headers.get(key);
|
|
281
|
-
if (value) headers.set(key, value);
|
|
282
|
-
}
|
|
274
|
+
if (if_none_match_value === etag) {
|
|
275
|
+
const headers = new Headers({ etag });
|
|
283
276
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
277
|
+
// https://datatracker.ietf.org/doc/html/rfc7232#section-4.1
|
|
278
|
+
for (const key of ['cache-control', 'content-location', 'date', 'expires', 'vary']) {
|
|
279
|
+
const value = response.headers.get(key);
|
|
280
|
+
if (value) headers.set(key, value);
|
|
288
281
|
}
|
|
289
|
-
}
|
|
290
282
|
|
|
291
|
-
|
|
283
|
+
return new Response(undefined, {
|
|
284
|
+
status: 304,
|
|
285
|
+
headers
|
|
286
|
+
});
|
|
287
|
+
}
|
|
292
288
|
}
|
|
293
289
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
status: 500
|
|
297
|
-
});
|
|
298
|
-
}
|
|
290
|
+
return response;
|
|
291
|
+
}
|
|
299
292
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
options,
|
|
306
|
-
state,
|
|
307
|
-
status: 404,
|
|
308
|
-
error: new Error(`Not found: ${event.url.pathname}`),
|
|
309
|
-
resolve_opts
|
|
310
|
-
});
|
|
311
|
-
}
|
|
293
|
+
if (state.initiator === GENERIC_ERROR) {
|
|
294
|
+
return new Response('Internal Server Error', {
|
|
295
|
+
status: 500
|
|
296
|
+
});
|
|
297
|
+
}
|
|
312
298
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
299
|
+
// if this request came direct from the user, rather than
|
|
300
|
+
// via a `fetch` in a `load`, render a 404 page
|
|
301
|
+
if (!state.initiator) {
|
|
302
|
+
return await respond_with_error({
|
|
303
|
+
event,
|
|
304
|
+
options,
|
|
305
|
+
state,
|
|
306
|
+
status: 404,
|
|
307
|
+
error: new Error(`Not found: ${event.url.pathname}`),
|
|
308
|
+
resolve_opts
|
|
309
|
+
});
|
|
310
|
+
}
|
|
316
311
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
},
|
|
312
|
+
if (state.prerendering) {
|
|
313
|
+
return new Response('not found', { status: 404 });
|
|
314
|
+
}
|
|
321
315
|
|
|
316
|
+
// we can't load the endpoint from our own manifest,
|
|
317
|
+
// so we need to make an actual HTTP request
|
|
318
|
+
return await fetch(request);
|
|
319
|
+
} catch (e) {
|
|
320
|
+
const error = coalesce_to_error(e);
|
|
321
|
+
return handle_fatal_error(event, options, error);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
return await options.hooks.handle({
|
|
327
|
+
event,
|
|
328
|
+
resolve,
|
|
322
329
|
// TODO remove for 1.0
|
|
323
330
|
// @ts-expect-error
|
|
324
331
|
get request() {
|
|
325
332
|
throw new Error('request in handle has been replaced with event' + details);
|
|
326
333
|
}
|
|
327
334
|
});
|
|
328
|
-
|
|
329
|
-
// TODO for 1.0, change the error message to point to docs rather than PR
|
|
330
|
-
if (response && !(response instanceof Response)) {
|
|
331
|
-
throw new Error('handle must return a Response object' + details);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return response;
|
|
335
335
|
} catch (/** @type {unknown} */ e) {
|
|
336
336
|
const error = coalesce_to_error(e);
|
|
337
|
-
|
|
338
|
-
options.handle_error(error, event);
|
|
339
|
-
|
|
340
|
-
const type = negotiate(event.request.headers.get('accept') || 'text/html', [
|
|
341
|
-
'text/html',
|
|
342
|
-
'application/json'
|
|
343
|
-
]);
|
|
344
|
-
|
|
345
|
-
if (is_data_request || type === 'application/json') {
|
|
346
|
-
return new Response(serialize_error(error, options.get_stack), {
|
|
347
|
-
status: 500,
|
|
348
|
-
headers: { 'content-type': 'application/json; charset=utf-8' }
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// TODO is this necessary? should we just return a plain 500 at this point?
|
|
353
|
-
try {
|
|
354
|
-
return await respond_with_error({
|
|
355
|
-
event,
|
|
356
|
-
options,
|
|
357
|
-
state,
|
|
358
|
-
status: 500,
|
|
359
|
-
error,
|
|
360
|
-
resolve_opts
|
|
361
|
-
});
|
|
362
|
-
} catch (/** @type {unknown} */ e) {
|
|
363
|
-
const error = coalesce_to_error(e);
|
|
364
|
-
return static_error_page(options, 500, error.message);
|
|
365
|
-
}
|
|
337
|
+
return handle_fatal_error(event, options, error);
|
|
366
338
|
}
|
|
367
339
|
}
|
|
@@ -334,7 +334,7 @@ export async function render_page(event, route, page, options, state, resolve_op
|
|
|
334
334
|
});
|
|
335
335
|
} catch (error) {
|
|
336
336
|
// if we end up here, it means the data loaded successfull
|
|
337
|
-
// but the page failed to render
|
|
337
|
+
// but the page failed to render, or that a prerendering error occurred
|
|
338
338
|
options.handle_error(/** @type {Error} */ (error), event);
|
|
339
339
|
|
|
340
340
|
return await respond_with_error({
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { devalue } from 'devalue';
|
|
2
|
+
import { DATA_SUFFIX } from '../../constants.js';
|
|
3
|
+
import { negotiate } from '../../utils/http.js';
|
|
2
4
|
import { HttpError } from '../control.js';
|
|
3
5
|
|
|
4
6
|
/** @param {any} body */
|
|
@@ -175,3 +177,33 @@ export function static_error_page(options, status, message) {
|
|
|
175
177
|
status
|
|
176
178
|
});
|
|
177
179
|
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @param {import('types').RequestEvent} event
|
|
183
|
+
* @param {import('types').SSROptions} options
|
|
184
|
+
* @param {Error} error
|
|
185
|
+
*/
|
|
186
|
+
export function handle_fatal_error(event, options, error) {
|
|
187
|
+
let status = 500;
|
|
188
|
+
|
|
189
|
+
if (error instanceof HttpError) {
|
|
190
|
+
status = error.status;
|
|
191
|
+
} else {
|
|
192
|
+
options.handle_error(error, event);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ideally we'd use sec-fetch-dest instead, but Safari — quelle surprise — doesn't support it
|
|
196
|
+
const type = negotiate(event.request.headers.get('accept') || 'text/html', [
|
|
197
|
+
'text/html',
|
|
198
|
+
'application/json'
|
|
199
|
+
]);
|
|
200
|
+
|
|
201
|
+
if (event.url.pathname.endsWith(DATA_SUFFIX) || type === 'application/json') {
|
|
202
|
+
return new Response(serialize_error(error, options.get_stack), {
|
|
203
|
+
status,
|
|
204
|
+
headers: { 'content-type': 'application/json; charset=utf-8' }
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return static_error_page(options, status, error.message);
|
|
209
|
+
}
|