Skip to content

Commit

Permalink
Expose rawBody (#1109)
Browse files Browse the repository at this point in the history
* expect rawBody and parse inside kit

* update adapter-node

* ignore type error for now

* random lockfile stuff

* update docs

* add wrangler.toml

* use rawBody, bundle worker with esbuild

* fix dependencies

* fix dependencies

* update lockfile

* ugh who asked you, eslint

* changesets

* add test

* pass rawBody from netlify adapter

* expose getRawBody from kit/http

* use getRawBody in adapter-node

* use getRawBody in adapter-vercel
  • Loading branch information
Rich Harris authored Apr 18, 2021
1 parent 361bd3b commit 1237eb3
Show file tree
Hide file tree
Showing 36 changed files with 410 additions and 280 deletions.
5 changes: 5 additions & 0 deletions .changeset/clever-lizards-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/adapter-node': patch
---

Use getRawBody
5 changes: 5 additions & 0 deletions .changeset/cold-buttons-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/adapter-cloudflare-workers': patch
---

Pass rawBody to SvelteKit, bundle worker with esbuild
5 changes: 5 additions & 0 deletions .changeset/flat-ducks-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/adapter-vercel': patch
---

Fix dependencies
5 changes: 5 additions & 0 deletions .changeset/lemon-ways-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/adapter-netlify': patch
---

Fix dependencies
5 changes: 5 additions & 0 deletions .changeset/modern-dryers-join.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

Expose rawBody on request, and expect rawBody from adapters
5 changes: 5 additions & 0 deletions .changeset/nasty-boats-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/adapter-netlify': patch
---

Pass rawBody from netlify adapter
5 changes: 5 additions & 0 deletions .changeset/tiny-candles-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

Expose getRawBody from kit/http
5 changes: 5 additions & 0 deletions .changeset/unlucky-wasps-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/adapter-vercel': patch
---

Use getRawBody in adapter-vercel
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ yarn.lock
.vercel_build_output
.netlify
.svelte
.cloudflare
12 changes: 6 additions & 6 deletions documentation/docs/01-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ type Request<Context = any> = {
path: string;
params: Record<string, string | string[]>;
query: URLSearchParams;
body: string | Buffer | ReadOnlyFormData;
rawBody: string | ArrayBuffer;
body: string | ArrayBuffer | ReadOnlyFormData | any;
context: Context; // see getContext, below
};

Expand Down Expand Up @@ -95,10 +96,10 @@ export async function get({ params }) {
The job of this function is to return a `{status, headers, body}` object representing the response, where `status` is an [HTTP status code](https://httpstatusdogs.com):

* `2xx` — successful response (default is `200`)
* `3xx` — redirection (should be accompanied by a `location` header)
* `4xx` — client error
* `5xx` — server error
- `2xx` — successful response (default is `200`)
- `3xx` — redirection (should be accompanied by a `location` header)
- `4xx` — client error
- `5xx` — server error

> For successful responses, SvelteKit will generate 304s automatically
Expand Down Expand Up @@ -126,7 +127,6 @@ return {
};
```


### Private modules

A filename that has a segment with a leading underscore, such as `src/routes/foo/_Private.svelte` or `src/routes/bar/_utils/cool-util.js`, is hidden from the router, but can be imported by files that are not.
Expand Down
3 changes: 2 additions & 1 deletion documentation/docs/04-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ type Request<Context = any> = {
path: string;
params: Record<string, string>;
query: URLSearchParams;
body: string | Buffer | ReadOnlyFormData;
rawBody: string | ArrayBuffer;
body: string | ArrayBuffer | ReadOnlyFormData | any;
context: Context;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,17 @@
import { render } from './app.js'; // eslint-disable-line import/no-unresolved
// TODO hardcoding the relative location makes this brittle
import { render } from '../output/server/app.js'; // eslint-disable-line import/no-unresolved
import { getAssetFromKV, NotFoundError } from '@cloudflare/kv-asset-handler'; // eslint-disable-line import/no-unresolved

// From https://developers.cloudflare.com/workers/examples/read-post
async function readRequestBody(request) {
const { headers } = request;
const contentType = headers.get('content-type') || '';
if (contentType.includes('application/json')) {
return await request.json();
} else if (contentType.includes('application/text')) {
return await request.text();
} else if (contentType.includes('text/html')) {
return await request.text();
} else if (contentType.includes('form')) {
return await request.formData();
} else {
const myBlob = await request.blob();
const objectURL = URL.createObjectURL(myBlob);
return objectURL;
}
}

addEventListener('fetch', (event) => {
event.respondWith(handleEvent(event));
event.respondWith(handle(event));
});

async function handleEvent(event) {
//try static files first
async function handle(event) {
// try static files first
if (event.request.method == 'GET') {
try {
// TODO rather than attempting to get an asset,
// use the asset manifest to see if it exists
return await getAssetFromKV(event);
} catch (e) {
if (!(e instanceof NotFoundError)) {
Expand All @@ -38,7 +22,7 @@ async function handleEvent(event) {
}
}

//fall back to an app route
// fall back to an app route
const request = event.request;
const request_url = new URL(request.url);

Expand All @@ -47,17 +31,16 @@ async function handleEvent(event) {
host: request_url.host,
path: request_url.pathname,
query: request_url.searchParams,
body: request.body ? await readRequestBody(request) : null,
rawBody: request.body ? await read(request) : null,
headers: Object.fromEntries(request.headers),
method: request.method
});

if (rendered) {
const response = new Response(rendered.body, {
return new Response(rendered.body, {
status: rendered.status,
headers: rendered.headers
});
return response;
}
} catch (e) {
return new Response('Error rendering route:' + (e.message || e.toString()), { status: 500 });
Expand All @@ -68,3 +51,12 @@ async function handleEvent(event) {
statusText: 'Not Found'
});
}

function read(request) {
const type = request.headers.get('content-type') || '';
if (type.includes('application/octet-stream')) {
return request.arrayBuffer();
}

return request.text();
}
117 changes: 72 additions & 45 deletions packages/adapter-cloudflare-workers/index.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,95 @@
'use strict';

const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const esbuild = require('esbuild');
const toml = require('toml');

module.exports = function () {
/** @type {import('@sveltejs/kit').Adapter} */
const adapter = {
name: '@sveltejs/adapter-cloudflare-workers',
async adapt(utils) {
let wrangler_config;

if (fs.existsSync('wrangler.toml')) {
try {
wrangler_config = toml.parse(fs.readFileSync('wrangler.toml', 'utf-8'));
} catch (err) {
err.message = `Error parsing wrangler.toml: ${err.message}`;
throw err;
}
} else {
// TODO offer to create one?
throw new Error(
'Missing a wrangler.toml file. Consult https://developers.cloudflare.com/workers/platform/sites/configuration on how to setup your site'
);
}

if (!wrangler_config.site || !wrangler_config.site.bucket) {
throw new Error(
'You must specify site.bucket in wrangler.toml. Consult https://developers.cloudflare.com/workers/platform/sites/configuration'
);
}

const bucket = path.resolve(wrangler_config.site.bucket);
const entrypoint = path.resolve(wrangler_config.site['entry-point'] ?? 'workers-site');
const { site } = validate_config(utils);

utils.copy_static_files(bucket);
utils.copy_client_files(bucket);
utils.copy_server_files(entrypoint);
const bucket = site.bucket;
const entrypoint = site['entry-point'] || 'workers-site';

utils.rimraf(bucket);
utils.rimraf(entrypoint);

// copy the renderer
utils.copy(path.resolve(__dirname, 'files/render.js'), `${entrypoint}/index.js`);
utils.copy(path.resolve(__dirname, 'files/_package.json'), `${entrypoint}/package.json`);
utils.log.info('Installing worker dependencies...');
utils.copy(`${__dirname}/files/_package.json`, '.svelte/cloudflare-workers/package.json');

// TODO would be cool if we could make this step unnecessary somehow
const stdout = execSync('npm install', { cwd: '.svelte/cloudflare-workers' });
utils.log.info(stdout.toString());

utils.log.minor('Generating worker...');
utils.copy(`${__dirname}/files/entry.js`, '.svelte/cloudflare-workers/entry.js');

await esbuild.build({
entryPoints: ['.svelte/cloudflare-workers/entry.js'],
outfile: `${entrypoint}/index.js`,
bundle: true,
platform: 'node'
});

utils.log.info('Prerendering static pages...');
await utils.prerender({
dest: bucket
});

utils.log.info('Installing Worker Dependencies...');
exec(
'npm install',
{
cwd: entrypoint
},
(error, stdout, stderr) => {
utils.log.info(stderr);
if (error) {
utils.log.error(error);
}
}
);
utils.log.minor('Copying assets...');
utils.copy_static_files(bucket);
utils.copy_client_files(bucket);
}
};

return adapter;
};

function validate_config(utils) {
if (fs.existsSync('wrangler.toml')) {
let wrangler_config;

try {
wrangler_config = toml.parse(fs.readFileSync('wrangler.toml', 'utf-8'));
} catch (err) {
err.message = `Error parsing wrangler.toml: ${err.message}`;
throw err;
}

if (!wrangler_config.site || !wrangler_config.site.bucket) {
throw new Error(
'You must specify site.bucket in wrangler.toml. Consult https://developers.cloudflare.com/workers/platform/sites/configuration'
);
}

return wrangler_config;
}

utils.log.error(
'Consult https://developers.cloudflare.com/workers/platform/sites/configuration on how to setup your site'
);

utils.log(
`
Sample wrangler.toml:
name = "<your-site-name>"
type = "javascript"
account_id = "<your-account-id>"
workers_dev = true
route = ""
zone_id = ""
[site]
bucket = "./.cloudflare/assets"
entry-point = "./.cloudflare/worker"`
.replace(/^\t+/gm, '')
.trim()
);

throw new Error('Missing a wrangler.toml file');
}
1 change: 1 addition & 0 deletions packages/adapter-cloudflare-workers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"check-format": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore"
},
"dependencies": {
"esbuild": "^0.11.12",
"toml": "^3.0.0"
},
"devDependencies": {
Expand Down
12 changes: 3 additions & 9 deletions packages/adapter-netlify/files/entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,7 @@ import '@sveltejs/kit/install-fetch'; // eslint-disable-line import/no-unresolve
import { render } from '../output/server/app.js'; // eslint-disable-line import/no-unresolved

export async function handler(event) {
const {
path,
httpMethod,
headers,
queryStringParameters
// body, // TODO pass this to renderer
// isBase64Encoded // TODO is this useful?
} = event;
const { path, httpMethod, headers, queryStringParameters, body, isBase64Encoded } = event;

const query = new URLSearchParams();
for (const k in queryStringParameters) {
Expand All @@ -25,7 +18,8 @@ export async function handler(event) {
method: httpMethod,
headers,
path,
query
query,
rawBody: isBase64Encoded ? new TextEncoder('base64').encode(body).buffer : body
});

if (rendered) {
Expand Down
3 changes: 2 additions & 1 deletion packages/adapter-netlify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
"check-format": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore"
},
"dependencies": {
"@sveltejs/kit": "workspace:*",
"esbuild": "^0.11.12",
"toml": "^3.0.0"
},
"devDependencies": {
"@sveltejs/kit": "workspace:*",
"typescript": "^4.2.3"
}
}
4 changes: 2 additions & 2 deletions packages/adapter-node/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { fileURLToPath } from 'url';
import compression from 'compression';
import polka from 'polka';
import sirv from 'sirv';
import { get_body } from '@sveltejs/kit/http'; // eslint-disable-line import/no-unresolved
import { getRawBody } from '@sveltejs/kit/http'; // eslint-disable-line import/no-unresolved
import '@sveltejs/kit/install-fetch'; // eslint-disable-line import/no-unresolved

// App is a dynamic file built from the application layer.
Expand Down Expand Up @@ -44,7 +44,7 @@ export function createServer({ render }) {
method: req.method,
headers: req.headers, // TODO: what about repeated headers, i.e. string[]
path: parsed.pathname,
body: await get_body(req),
rawBody: await getRawBody(req),
query: parsed.searchParams
});

Expand Down
Loading

1 comment on commit 1237eb3

@vercel
Copy link

@vercel vercel bot commented on 1237eb3 Apr 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.