diff --git a/runtime/src/server/middleware/get_page_handler.ts b/runtime/src/server/middleware/get_page_handler.ts index 6ac35d4ba..2248ff09c 100644 --- a/runtime/src/server/middleware/get_page_handler.ts +++ b/runtime/src/server/middleware/get_page_handler.ts @@ -1,13 +1,12 @@ import { writable } from 'svelte/store'; import fs from 'fs'; import path from 'path'; -import cookie from 'cookie'; import devalue from 'devalue'; -import fetch from 'node-fetch'; import URL from 'url'; import { sourcemap_stacktrace } from './sourcemap_stacktrace'; import { Manifest, ManifestPage, Req, Res, build_dir, dev, src_dir } from '@sapper/internal/manifest-server'; import App from '@sapper/internal/App.svelte'; +import { getPreloadFetch } from './preload_fetch'; export function get_page_handler( manifest: Manifest, @@ -112,45 +111,10 @@ export function get_page_handler( error: (statusCode: number, message: Error | string) => { preload_error = { statusCode, message }; }, - fetch: (url: string, opts?: any) => { - const protocol = req.socket.encrypted ? 'https' : 'http'; - const parsed = new URL.URL(url, `${protocol}://127.0.0.1:${process.env.PORT}${req.baseUrl ? req.baseUrl + '/' :''}`); - - opts = Object.assign({}, opts); - - const include_credentials = ( - opts.credentials === 'include' || - opts.credentials !== 'omit' && parsed.origin === `${protocol}://127.0.0.1:${process.env.PORT}` - ); - - if (include_credentials) { - opts.headers = Object.assign({}, opts.headers); - - const cookies = Object.assign( - {}, - cookie.parse(req.headers.cookie || ''), - cookie.parse(opts.headers.cookie || '') - ); - - const set_cookie = res.getHeader('Set-Cookie'); - (Array.isArray(set_cookie) ? set_cookie : [set_cookie]).forEach(str => { - const match = /([^=]+)=([^;]+)/.exec(str); - if (match) cookies[match[1]] = match[2]; - }); - - const str = Object.keys(cookies) - .map(key => `${key}=${cookies[key]}`) - .join('; '); - - opts.headers.cookie = str; - - if (!opts.headers.authorization && req.headers.authorization) { - opts.headers.authorization = req.headers.authorization; - } - } - - return fetch(parsed.href, opts); - } + getHeader: (name: string) => req.headers[name.toLowerCase()], + setHeader: (name: string, value: string | number | string[]) => res.setHeader(name, value), + removeHeader: (name: string) => res.removeHeader(name), + fetch: getPreloadFetch(req, res) }; let preloaded; diff --git a/runtime/src/server/middleware/preload_fetch.ts b/runtime/src/server/middleware/preload_fetch.ts new file mode 100644 index 000000000..be004f121 --- /dev/null +++ b/runtime/src/server/middleware/preload_fetch.ts @@ -0,0 +1,51 @@ +import cookie from 'cookie'; +import URL from 'url'; +import { Req, Res } from '@sapper/internal/manifest-server'; +import fetch from 'node-fetch'; + +export type PreloadFetchOpts = RequestInit & { headers: { [key: string]: string }; credentials: 'include' | 'omit' }; +type FetchFn = WindowOrWorkerGlobalScope['fetch']; + +export function getPreloadFetch(req: Req, res: Res): FetchFn { + return (url: string, opts?: PreloadFetchOpts) => { + const protocol = req.socket.encrypted ? 'https' : 'http'; + const parsed = new URL.URL( + url, + `${protocol}://127.0.0.1:${process.env.PORT}${req.baseUrl ? req.baseUrl + '/' : ''}` + ); + + opts = Object.assign({}, opts); + + const include_credentials = + opts.credentials === 'include' || + (opts.credentials !== 'omit' && parsed.origin === `${protocol}://127.0.0.1:${process.env.PORT}`); + + if (include_credentials) { + opts.headers = Object.assign({}, opts.headers); + + const cookies = Object.assign( + {}, + cookie.parse(req.headers.cookie || ''), + cookie.parse(opts.headers.cookie || '') + ); + + const set_cookie = res.getHeader('Set-Cookie'); + (Array.isArray(set_cookie) ? set_cookie : [set_cookie]).forEach(str => { + const match = /([^=]+)=([^;]+)/.exec(str); + if (match) cookies[match[1]] = match[2]; + }); + + const str = Object.keys(cookies) + .map(key => `${key}=${cookies[key]}`) + .join('; '); + + opts.headers.cookie = str; + + if (!opts.headers.authorization && req.headers.authorization) { + opts.headers.authorization = req.headers.authorization; + } + } + + return fetch(parsed.href, opts); + }; +} diff --git a/site/content/docs/04-preloading.md b/site/content/docs/04-preloading.md index 849110f89..914f7840d 100644 --- a/site/content/docs/04-preloading.md +++ b/site/content/docs/04-preloading.md @@ -58,11 +58,14 @@ When Sapper renders a page on the server, it will attempt to serialize the resol ### Context -Inside `preload`, you have access to three methods: +Inside `preload`, you have access to the following methods: * `this.fetch(url, options)` * `this.error(statusCode, error)` * `this.redirect(statusCode, location)` +* `this.setHeader(name, value)` +* `this.getHeader(name)` +* `this.removeHeader(name)` #### this.fetch @@ -132,3 +135,34 @@ You can abort rendering and redirect to a different location with `this.redirect } ``` + +#### this.setHeader / removeHeader + +You can set custom response headers with `this.setHeader` or clear them again using `this.removeHeader`. +The most common use case is to change cache policies for the page. + +Sapper by default does not set any set the `Cache-Control` header. You can set one as follows: + +```html + +``` + +You can also use `setHeader` to set cookies. + +#### this.getHeader + +Use `this.getHeader` to retrieve any headers sent with the request. + +```html + +``` diff --git a/test/apps/basics/src/routes/echo-header.svelte b/test/apps/basics/src/routes/echo-header.svelte new file mode 100644 index 000000000..c6f0a9a10 --- /dev/null +++ b/test/apps/basics/src/routes/echo-header.svelte @@ -0,0 +1,13 @@ +

Headers

+ +

The foobar header was: {headerValue}

+ + + + \ No newline at end of file diff --git a/test/apps/basics/src/routes/nocache.svelte b/test/apps/basics/src/routes/nocache.svelte new file mode 100644 index 000000000..f515dc79f --- /dev/null +++ b/test/apps/basics/src/routes/nocache.svelte @@ -0,0 +1,9 @@ +

Look, no cache!

+ +

I have a `no-cache` header.

+ + \ No newline at end of file diff --git a/test/apps/basics/test.ts b/test/apps/basics/test.ts index 388fb0b86..ca5bba7fe 100644 --- a/test/apps/basics/test.ts +++ b/test/apps/basics/test.ts @@ -389,6 +389,26 @@ describe('basics', function() { assert.equal(await r.text('h2'), 'Called 1 time'); }); + it('sets response headers', async () => { + const response = await r.load('/nocache'); + + assert.ok(response.ok()); + + assert.equal(response.headers()['cache-control'], 'no-cache'); + }); + + it('retrieves request headers', async () => { + const headerValue = '123'; + + await r.page.setExtraHTTPHeaders({ Foobar: headerValue }); + + const response = await r.load('/echo-header'); + + assert.ok(response.ok()); + + assert.equal(await r.text('#cookie'), headerValue); + }); + it('survives the tests with no server errors', () => { assert.deepEqual(r.errors, []); });