Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

generate etags for binary responses #1381

Closed
wants to merge 1 commit into from
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
4 changes: 2 additions & 2 deletions packages/kit/src/core/adapt/prerender.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export async function prerender({ cwd, out, log, config, build_data, fallback, a
if (seen.has(path)) return;
seen.add(path);

/** @type {Map<string, import('types/endpoint').ServerResponse>} */
/** @type {Map<string, import('types/hooks').ServerResponse>} */
const dependencies = new Map();

const rendered = await app.render(
Expand Down Expand Up @@ -172,7 +172,7 @@ export async function prerender({ cwd, out, log, config, build_data, fallback, a
});

if (is_html && config.kit.prerender.crawl) {
const cleaned = clean_html(rendered.body);
const cleaned = clean_html(/** @type {string} */ (rendered.body));

let match;
const pattern = /<(a|img|link|source)\s+([\s\S]+?)>/gm;
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 @@ -296,16 +296,18 @@ class Watcher extends EventEmitter {

if (rendered) {
res.writeHead(rendered.status, rendered.headers);
res.end(rendered.body);
res.write(rendered.body);
} else {
res.statusCode = 404;
res.end('Not found');
res.write('Not found');
}
} catch (e) {
this.vite.ssrFixStacktrace(e);
res.statusCode = 500;
res.end(e.stack);
res.write(e.stack);
}

res.end();
});
});
}
Expand Down
6 changes: 4 additions & 2 deletions packages/kit/src/core/start/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,13 @@ export async function start({ port, host, config, https: use_https = false, cwd

if (rendered) {
res.writeHead(rendered.status, rendered.headers);
res.end(rendered.body);
res.write(rendered.body);
} else {
res.statusCode = 404;
res.end('Not found');
res.write('Not found');
}

res.end();
});
});
});
Expand Down
15 changes: 12 additions & 3 deletions packages/kit/src/runtime/server/endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { lowercase_keys } from './utils.js';
/**
* @param {import('types/endpoint').ServerRequest} request
* @param {import('types/internal').SSREndpoint} route
* @returns {Promise<import('types/endpoint').ServerResponse>}
* @returns {Promise<import('types/hooks').ServerResponse>}
*/
export default async function render_route(request, route) {
const mod = await route.load();
Expand All @@ -21,8 +21,8 @@ export default async function render_route(request, route) {
if (typeof response !== 'object') {
return {
status: 500,
body: `Invalid response from route ${request.path};
expected an object, got ${typeof response}`,
// prettier-ignore
body: `Invalid response from route ${request.path}: expected an object, got ${typeof response}`,
headers: {}
};
}
Expand All @@ -39,6 +39,15 @@ export default async function render_route(request, route) {
body = JSON.stringify(body);
}

if (typeof body !== 'string' && !(body instanceof Uint8Array)) {
return {
status: 500,
// prettier-ignore
body: `Invalid response from route ${request.path}: body must be a string or Uint8Array`,
headers: {}
};
}

return { status, body, headers };
}
}
Expand Down
17 changes: 12 additions & 5 deletions packages/kit/src/runtime/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { lowercase_keys } from './utils.js';
* @param {import('types/hooks').Incoming} incoming
* @param {import('types/internal').SSRRenderOptions} options
* @param {import('types/internal').SSRRenderState} [state]
* @returns {Promise<import('types/hooks').ServerResponse>}
*/
export async function respond(incoming, options, state = {}) {
if (incoming.path.endsWith('/') && incoming.path !== '/') {
Expand Down Expand Up @@ -87,10 +88,16 @@ export async function respond(incoming, options, state = {}) {
}
}

/** @param {string} str */
function hash(str) {
let hash = 5381,
i = str.length;
while (i) hash = (hash * 33) ^ str.charCodeAt(--i);
/** @param {string | Uint8Array} value */
function hash(value) {
let hash = 5381;
let i = value.length;

if (typeof value === 'string') {
while (i) hash = (hash * 33) ^ value.charCodeAt(--i);
} else {
while (i) hash = (hash * 33) ^ value[--i];
}

return (hash >>> 0).toString(36);
}
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/server/page/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { respond_with_error } from './respond_with_error.js';
* @param {import('types/internal').SSRPage} route
* @param {import('types/internal').SSRRenderOptions} options
* @param {import('types/internal').SSRRenderState} state
* @returns {Promise<import('types/endpoint').ServerResponse>}
* @returns {Promise<import('types/hooks').ServerResponse>}
*/
export default async function render_page(request, route, options, state) {
if (state.initiator === route) {
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/server/page/respond.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { respond_with_error } from './respond_with_error.js';
* $session: any;
* route: import('types/internal').SSRPage;
* }} opts
* @returns {Promise<import('types/endpoint').ServerResponse>}
* @returns {Promise<import('types/hooks').ServerResponse>}
*/
export async function respond({ request, options, state, $session, route }) {
const match = route.pattern.exec(request.path);
Expand Down
46 changes: 46 additions & 0 deletions packages/kit/test/apps/basics/src/routes/etag/_tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as assert from 'uvu/assert';

/** @type {import('test').TestMaker} */
export default function (test) {
test(
'generates etag/304 for text body',
null,
async ({ response, fetch }) => {
const r1 = await fetch('/etag/text');
const etag = r1.headers.get('etag');
assert.ok(!!etag);

const r2 = await fetch('/etag/text', {
headers: {
'if-none-match': etag
}
});

assert.equal(r2.status, 304);
},
{
js: false
}
);

test(
'generates etag/304 for binary body',
null,
async ({ fetch }) => {
const r1 = await fetch('/etag/binary');
const etag = r1.headers.get('etag');
assert.ok(!!etag);

const r2 = await fetch('/etag/binary', {
headers: {
'if-none-match': etag
}
});

assert.equal(r2.status, 304);
},
{
js: false
}
);
}
9 changes: 9 additions & 0 deletions packages/kit/test/apps/basics/src/routes/etag/binary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** @type {import('@sveltejs/kit').RequestHandler} */
export function get() {
return {
headers: {
'content-type': 'application/octet-stream'
},
body: new Uint8Array([1, 2, 3, 4, 5])
};
}
6 changes: 6 additions & 0 deletions packages/kit/test/apps/basics/src/routes/etag/text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/** @type {import('@sveltejs/kit').RequestHandler} */
export function get() {
return {
body: 'some text'
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
export function post(request) {
return {
body: {
body: request.body,
rawBody: request.rawBody
body: /** @type {string} */ (request.body),
rawBody: /** @type {string} */ (request.rawBody)
}
};
}
15 changes: 12 additions & 3 deletions packages/kit/types/endpoint.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,21 @@ export type ServerRequest<Locals = Record<string, any>, Body = unknown> = {
locals: Locals;
};

export type ServerResponse = {
type JSONValue =
| string
| number
| boolean
| null
| Date
| JSONValue[]
| { [key: string]: JSONValue };

export type EndpointOutput = {
status?: number;
headers?: Headers;
body?: any;
body?: string | Uint8Array | JSONValue;
};

export type RequestHandler<Locals = Record<string, any>, Body = unknown> = (
request: ServerRequest<Locals, Body>
) => void | ServerResponse | Promise<ServerResponse>;
) => void | EndpointOutput | Promise<EndpointOutput>;
2 changes: 1 addition & 1 deletion packages/kit/types/helper.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface ReadOnlyFormData extends Iterator<[string, string]> {
values: () => Iterator<string>;
}

export type BaseBody = string | Buffer | ReadOnlyFormData;
export type BaseBody = string | Uint8Array | ReadOnlyFormData;
export type ParameterizedBody<Body = unknown> = Body extends FormData
? ReadOnlyFormData
: BaseBody & Body;
Expand Down
8 changes: 7 additions & 1 deletion packages/kit/types/hooks.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BaseBody, Headers } from './helper';
import { ServerRequest, ServerResponse } from './endpoint';
import { ServerRequest } from './endpoint';

export type Incoming = {
method: string;
Expand All @@ -19,3 +19,9 @@ export type Handle<Locals = Record<string, any>> = (input: {
request: ServerRequest<Locals>;
render: (request: ServerRequest<Locals>) => ServerResponse | Promise<ServerResponse>;
}) => ServerResponse | Promise<ServerResponse>;

export type ServerResponse = {
status?: number;
headers?: Headers;
body?: string | Uint8Array;
};
4 changes: 2 additions & 2 deletions packages/kit/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import './ambient-modules';

export { Adapter, AdapterUtils, Config } from './config';
export { ErrorLoad, Load, Page } from './page';
export { Incoming, GetSession, Handle } from './hooks';
export { ServerRequest as Request, ServerResponse as Response, RequestHandler } from './endpoint';
export { Incoming, GetSession, Handle, ServerResponse } from './hooks';
export { ServerRequest as Request, EndpointOutput, RequestHandler } from './endpoint';
4 changes: 2 additions & 2 deletions packages/kit/types/internal.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Load } from './page';
import { Incoming, GetSession, Handle } from './hooks';
import { RequestHandler, ServerResponse } from './endpoint';
import { Incoming, GetSession, Handle, ServerResponse } from './hooks';
import { RequestHandler } from './endpoint';

type PageId = string;

Expand Down