Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions packages/kit/src/core/adapt/prerender.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { dirname, join, resolve as resolve_path, sep as path_separator } from 'p
import { parse, pathToFileURL, resolve, URLSearchParams } from 'url';
import glob from 'tiny-glob/sync.js';
import { mkdirp } from '@sveltejs/app-utils/files';
import { Headers } from '../../runtime/app/headers.js';

/** @param {string} html */
function clean_html(html) {
Expand Down Expand Up @@ -83,14 +84,15 @@ export async function prerender({ cwd, out, log, config, force }) {
if (seen.has(path)) return;
seen.add(path);

const rendered = await app.render(
const rendered = app.render(
{
host: config.kit.host,
method: 'GET',
headers: {},
headers: new Headers(),
path,
body: null,
query: new URLSearchParams()
query: new URLSearchParams(),
params: null
},
{
local: true,
Expand All @@ -102,7 +104,7 @@ export async function prerender({ cwd, out, log, config, force }) {
if (rendered) {
const response_type = Math.floor(rendered.status / 100);
const headers = rendered.headers;
const type = headers && headers['content-type'];
const type = headers && headers.get('content-type');
const is_html = response_type === REDIRECT || type === 'text/html';

const parts = path.split('/');
Expand All @@ -114,7 +116,7 @@ export async function prerender({ cwd, out, log, config, force }) {
mkdirp(dirname(file));

if (response_type === REDIRECT) {
const { location } = headers;
const location = headers.get('location');

log.warn(`${rendered.status} ${path} -> ${location}`);
writeFileSync(file, `<meta http-equiv="refresh" content="0;url=${encodeURI(location)}">`);
Expand All @@ -136,7 +138,7 @@ export async function prerender({ cwd, out, log, config, force }) {
const result = dependencies[path];
const response_type = Math.floor(result.status / 100);

const is_html = result.headers['content-type'] === 'text/html';
const is_html = result.headers.get('content-type') === 'text/html';

const parts = path.split('/');
if (is_html && parts[parts.length - 1] !== 'index.html') {
Expand Down
8 changes: 5 additions & 3 deletions packages/kit/src/core/dev/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import vite from 'vite';
import create_manifest_data from '../../core/create_manifest_data/index.js';
import { create_app } from '../../core/create_app/index.js';
import { rimraf } from '@sveltejs/app-utils/files';
import { Headers } from '../../runtime/app/headers.js';
import { ssr } from '../../runtime/server/index.js';
import { get_body } from '@sveltejs/app-utils/http';
import { copy_assets } from '../utils.js';
Expand Down Expand Up @@ -126,12 +127,13 @@ class Watcher extends EventEmitter {

const rendered = await ssr(
{
headers: req.headers,
headers: new Headers(req.headers),
method: req.method,
host: null,
path: parsed.pathname,
query: new URLSearchParams(parsed.query),
body
body,
params: null
},
{
paths: this.config.kit.paths,
Expand Down Expand Up @@ -208,7 +210,7 @@ class Watcher extends EventEmitter {
);

if (rendered) {
res.writeHead(rendered.status, rendered.headers);
res.writeHead(rendered.status, rendered.headers.asMap());
res.end(rendered.body);
} else {
res.statusCode = 404;
Expand Down
9 changes: 6 additions & 3 deletions packages/kit/src/core/start/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { parse, pathToFileURL, URLSearchParams } from 'url';
import sirv from 'sirv';
import { get_body } from '@sveltejs/app-utils/http';
import { join, resolve } from 'path';
import { Headers } from '../../runtime/app/headers.js';

/** @param {string} dir */
const mutable = (dir) =>
Expand Down Expand Up @@ -45,10 +46,12 @@ export async function start({ port, config, cwd = process.cwd() }) {
const rendered = await app.render(
{
method: req.method,
headers: req.headers,
headers: new Headers(req.headers),
path: parsed.pathname,
body: await get_body(req),
query: new URLSearchParams(parsed.query || '')
query: new URLSearchParams(parsed.query || ''),
params: null,
host: null
},
{
paths: {
Expand All @@ -61,7 +64,7 @@ export async function start({ port, config, cwd = process.cwd() }) {
);

if (rendered) {
res.writeHead(rendered.status, rendered.headers);
res.writeHead(rendered.status, rendered.headers.asMap());
res.end(rendered.body);
} else {
res.statusCode = 404;
Expand Down
80 changes: 80 additions & 0 deletions packages/kit/src/runtime/app/headers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* @type {import("../../../types.internal").Headers}
*/
export class Headers {
/**
* @param {Record<string,string|string[]>} [headers]
*/
constructor(headers) {
this.headers = lowercase_keys(headers) || {};
}

/**
* @param {string} key
* @param {string} value
*/
set(key, value) {
this.headers[key.toLowerCase()] = value;
}

/**
* @param {string} key
* @returns {string}
*/
get(key) {
key = key.toLowerCase();
if (Array.isArray(this.headers[key])) {
if (this.headers[key].length > 1) {
throw new Error(`Called get but multiple headers found for ${key}`);
}
return this.headers[key][0];
}
return /** @type {string} */ (this.headers[key]);
}

/**
* @param {string} key
* @returns {string[]}
*/
getAll(key) {
key = key.toLowerCase();
if (typeof this.headers[key] === 'string') {
return [/** @type {string} */ (this.headers[key])];
}
if (typeof this.headers[key] === 'undefined') {
return [];
}
return /** @type {string[]} */ (this.headers[key]);
}

/**
* @returns {Record<string,string|string[]>}
*/
asMap() {
return this.headers;
}

/**
* @returns {Record<string,string>}
*/
asSingleValuedMap() {
/** @type {Record<string,string>} */
const result = {};
for (const key of Object.keys(this.headers)) {
result[key] = this.get(key);
}
return result;
}
}

/** @param {Record<string, string|string[]>} obj */
function lowercase_keys(obj) {
/** @type {Record<string, string|string[]>} */
const clone = {};

for (const key in obj) {
clone[key.toLowerCase()] = obj[key];
}

return clone;
}
28 changes: 8 additions & 20 deletions packages/kit/src/runtime/server/endpoint.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Headers } from '../app/headers.js';

/**
* @param {import('../../../types.internal').Request} request
* @param {*} context // TODO
Expand All @@ -18,7 +20,7 @@ export default function render_route(request, context, options) {

const response = await handler(
{
host: options.host || request.headers[options.host_header || 'host'],
host: options.host || request.headers.get(options.host_header || 'host'),
path: request.path,
headers: request.headers,
query: request.query,
Expand All @@ -34,19 +36,17 @@ export default function render_route(request, context, options) {
body: `Invalid response from route ${request.path}; ${
response.body == null ? 'body is missing' : `expected an object, got ${typeof response}`
}`,
headers: {}
headers: new Headers()
};
}

let { status = 200, body, headers = {} } = response;

headers = lowercase_keys(headers);
let { status = 200, body, headers = new Headers() } = response;

if (
(typeof body === 'object' && !('content-type' in headers)) ||
headers['content-type'] === 'application/json'
headers.get('content-type') === 'application/json'
) {
headers = { ...headers, 'content-type': 'application/json' };
headers.set('content-type', 'application/json');
body = JSON.stringify(body);
}

Expand All @@ -55,20 +55,8 @@ export default function render_route(request, context, options) {
return {
status: 501,
body: `${request.method} is not implemented for ${request.path}`,
headers: {}
headers: new Headers()
};
}
});
}

/** @param {Record<string, string>} obj */
function lowercase_keys(obj) {
/** @type {Record<string, string>} */
const clone = {};

for (const key in obj) {
clone[key.toLowerCase()] = obj[key];
}

return clone;
}
17 changes: 9 additions & 8 deletions packages/kit/src/runtime/server/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createHash } from 'crypto';
import { Headers } from '../app/headers.js';
import render_page from './page.js';
import render_endpoint from './endpoint.js';

Expand All @@ -17,13 +18,13 @@ export async function ssr(request, options) {

return {
status: 301,
headers: {
headers: new Headers({
location: request.path.slice(0, -1) + (q ? `?${q}` : '')
}
})
};
}

const { context, headers = {} } =
const { context, headers = new Headers({}) } =
(await (options.setup.prepare && options.setup.prepare({ headers: request.headers }))) || {};

try {
Expand All @@ -33,24 +34,24 @@ export async function ssr(request, options) {
if (response) {
// inject ETags for 200 responses
if (response.status === 200) {
if (!/(no-store|immutable)/.test(response.headers['cache-control'])) {
if (!/(no-store|immutable)/.test(response.headers.get('cache-control'))) {
const etag = `"${md5(response.body)}"`;

if (request.headers['if-none-match'] === etag) {
if (request.headers.get('if-none-match') === etag) {
return {
status: 304,
headers: {},
headers: new Headers({}),
body: null
};
}

response.headers['etag'] = etag;
response.headers.set('etag', etag);
}
}

return {
status: response.status,
headers: { ...headers, ...response.headers },
headers: new Headers({ ...headers.asMap(), ...response.headers.asMap() }),
body: response.body,
dependencies: response.dependencies
};
Expand Down
Loading