Redirects spike
This commit is contained in:
parent
48395c8152
commit
46e726960f
5 changed files with 102 additions and 47 deletions
|
@ -33,7 +33,7 @@ import {
|
|||
createAPIContext,
|
||||
throwIfRedirectNotAllowed,
|
||||
} from '../endpoint/index.js';
|
||||
import { AstroError } from '../errors/index.js';
|
||||
import { AstroError, AstroErrorData } from '../errors/index.js';
|
||||
import { debug, info } from '../logger/core.js';
|
||||
import { callMiddleware } from '../middleware/callMiddleware.js';
|
||||
import { createEnvironment, createRenderContext, renderPage } from '../render/index.js';
|
||||
|
@ -72,6 +72,12 @@ export function rootRelativeFacadeId(facadeId: string, settings: AstroSettings):
|
|||
return facadeId.slice(fileURLToPath(settings.config.root).length);
|
||||
}
|
||||
|
||||
function redirectWithNoLocation() {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.RedirectWithNoLocation
|
||||
});
|
||||
}
|
||||
|
||||
// Determines of a Rollup chunk is an entrypoint page.
|
||||
export function chunkIsPage(
|
||||
settings: AstroSettings,
|
||||
|
@ -510,10 +516,23 @@ async function generatePath(
|
|||
}
|
||||
throw err;
|
||||
}
|
||||
throwIfRedirectNotAllowed(response, opts.settings.config);
|
||||
// If there's no body, do nothing
|
||||
if (!response.body) return;
|
||||
body = await response.text();
|
||||
|
||||
switch(response.status) {
|
||||
case 301:
|
||||
case 302: {
|
||||
const location = response.headers.get("location");
|
||||
if(!location) {
|
||||
redirectWithNoLocation();
|
||||
}
|
||||
body = `<!doctype html><meta http-equiv="refresh" content="0;url=${location}" />`
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// If there's no body, do nothing
|
||||
if (!response.body) return;
|
||||
body = await response.text();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const outFolder = getOutFolder(settings.config, pathname, pageData.route.type);
|
||||
|
|
|
@ -45,6 +45,7 @@ export const AstroErrorData = {
|
|||
* To redirect on a static website, the [meta refresh attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta) can be used. Certain hosts also provide config-based redirects (ex: [Netlify redirects](https://docs.netlify.com/routing/redirects/)).
|
||||
*/
|
||||
StaticRedirectNotAvailable: {
|
||||
// TODO remove
|
||||
title: '`Astro.redirect` is not available in static mode.',
|
||||
code: 3001,
|
||||
message:
|
||||
|
@ -717,6 +718,18 @@ See https://docs.astro.build/en/guides/server-side-rendering/ for more informati
|
|||
return `The information stored in \`Astro.locals\` for the path "${href}" is not serializable.\nMake sure you store only serializable data.`;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* @docs
|
||||
* @see
|
||||
* - [Astro.redirect](https://docs.astro.build/en/guides/server-side-rendering/#astroredirect)
|
||||
* @description
|
||||
* A redirect must be given a location with the `Location` header.
|
||||
*/
|
||||
RedirectWithNoLocation: {
|
||||
// TODO remove
|
||||
title: 'A redirect must be given a location with the `Location` header.',
|
||||
code: 3035,
|
||||
},
|
||||
// No headings here, that way Vite errors are merged with Astro ones in the docs, which makes more sense to users.
|
||||
// Vite Errors - 4xxx
|
||||
/**
|
||||
|
|
|
@ -204,23 +204,21 @@ export function createResult(args: CreateResultArgs): SSRResult {
|
|||
locals,
|
||||
request,
|
||||
url,
|
||||
redirect: args.ssr
|
||||
? (path, status) => {
|
||||
// If the response is already sent, error as we cannot proceed with the redirect.
|
||||
if ((request as any)[responseSentSymbol]) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.ResponseSentError,
|
||||
});
|
||||
}
|
||||
redirect(path, status) {
|
||||
// If the response is already sent, error as we cannot proceed with the redirect.
|
||||
if ((request as any)[responseSentSymbol]) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.ResponseSentError,
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, {
|
||||
status: status || 302,
|
||||
headers: {
|
||||
Location: path,
|
||||
},
|
||||
});
|
||||
}
|
||||
: onlyAvailableInSSR('Astro.redirect'),
|
||||
return new Response(null, {
|
||||
status: status || 302,
|
||||
headers: {
|
||||
Location: path,
|
||||
},
|
||||
});
|
||||
},
|
||||
response: response as AstroGlobal['response'],
|
||||
slots: astroSlots as unknown as AstroGlobal['slots'],
|
||||
};
|
||||
|
|
|
@ -57,6 +57,12 @@ export function stringifyChunk(
|
|||
}
|
||||
return renderAllHeadContent(result);
|
||||
}
|
||||
default: {
|
||||
if(chunk instanceof Response) {
|
||||
return '';
|
||||
}
|
||||
throw new Error(`Unknown chunk type: ${(chunk as any).type}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isSlotString(chunk as string)) {
|
||||
|
@ -102,6 +108,7 @@ export function chunkToByteArray(
|
|||
if (chunk instanceof Uint8Array) {
|
||||
return chunk as Uint8Array;
|
||||
}
|
||||
|
||||
// stringify chunk might return a HTMLString
|
||||
let stringified = stringifyChunk(result, chunk);
|
||||
return encoder.encode(stringified.toString());
|
||||
|
|
|
@ -6,34 +6,52 @@ describe('Astro.redirect', () => {
|
|||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/ssr-redirect/',
|
||||
output: 'server',
|
||||
adapter: testAdapter(),
|
||||
describe('output: "server"', () => {
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/ssr-redirect/',
|
||||
output: 'server',
|
||||
adapter: testAdapter(),
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
it('Returns a 302 status', async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
const request = new Request('http://example.com/secret');
|
||||
const response = await app.render(request);
|
||||
expect(response.status).to.equal(302);
|
||||
expect(response.headers.get('location')).to.equal('/login');
|
||||
});
|
||||
|
||||
it('Warns when used inside a component', async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
const request = new Request('http://example.com/late');
|
||||
const response = await app.render(request);
|
||||
try {
|
||||
const text = await response.text();
|
||||
expect(false).to.equal(true);
|
||||
} catch (e) {
|
||||
expect(e.message).to.equal(
|
||||
'The response has already been sent to the browser and cannot be altered.'
|
||||
);
|
||||
}
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
it('Returns a 302 status', async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
const request = new Request('http://example.com/secret');
|
||||
const response = await app.render(request);
|
||||
expect(response.status).to.equal(302);
|
||||
expect(response.headers.get('location')).to.equal('/login');
|
||||
});
|
||||
|
||||
it('Warns when used inside a component', async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
const request = new Request('http://example.com/late');
|
||||
const response = await app.render(request);
|
||||
try {
|
||||
const text = await response.text();
|
||||
expect(false).to.equal(true);
|
||||
} catch (e) {
|
||||
expect(e.message).to.equal(
|
||||
'The response has already been sent to the browser and cannot be altered.'
|
||||
);
|
||||
}
|
||||
describe('output: "static"', () => {
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/ssr-redirect/',
|
||||
output: 'static',
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
it.only('Includes the meta refresh tag.', async () => {
|
||||
const html = await fixture.readFile('/secret/index.html');
|
||||
expect(html).to.include('http-equiv="refresh');
|
||||
expect(html).to.include('url=/login');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue