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

Convert proxy to Cloudflare Pages Function. #840

Open
wants to merge 2 commits into
base: 11ty
Choose a base branch
from
Open
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
39 changes: 39 additions & 0 deletions .eleventy.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// development host for playground proxy
const PLAYGROUND_PROXY_HOST = 'http://localhost:8788';

const drafts = [
'CG-FINAL',
'CR',
Expand All @@ -18,6 +21,7 @@ module.exports = function(eleventyConfig) {
eleventyConfig.addPassthroughCopy('examples/**/*.{html,ttl,txt,json}');
eleventyConfig.addPassthroughCopy('favicon.ico');
eleventyConfig.addPassthroughCopy('fonts');
eleventyConfig.addPassthroughCopy('functions/**/*.js');
eleventyConfig.addPassthroughCopy('images/**/*.{htaccess,png,svg,xcf}');
eleventyConfig.addPassthroughCopy('ns/**/*.{html,jsonld}');
eleventyConfig.addPassthroughCopy('playground/**/*.{css,php,js}');
Expand Down Expand Up @@ -49,4 +53,39 @@ module.exports = function(eleventyConfig) {
eleventyConfig.ignores.add(`spec/${draft}`);
}
eleventyConfig.ignores.add('test-suite');

// setup development proxy to cloudflare pages function server
if(process.env.ELEVENTY_RUN_MODE === 'serve') {
eleventyConfig.setServerOptions({
onRequest: {
'/playground/proxy': playgroundProxy
}
});
}
};

// proxy to worker proxy
async function playgroundProxy({url}) {
Copy link
Member

Choose a reason for hiding this comment

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

Can this be defined above its use on line 61?

const targetUrl = url.searchParams.get('url');
// eleventy only provides the URL
// approximate what the live playground does
const search = new URLSearchParams();
search.set('url', targetUrl);
const proxyUrl =
new URL(`${PLAYGROUND_PROXY_HOST}/playground/proxy?${search}`);
const res = await fetch(proxyUrl, {
headers: {
'Accept': 'application/ld+json, application/json'
}
});
// create headers object and filter properties
// suffient for the site development purposes
const headers = Object.fromEntries(
Array.from(res.headers.entries()).filter(
v => !['content-length', 'content-encoding'].includes(v[0])));
return {
status: res.status,
headers,
body: await res.text()
}
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
*~
*.backup
*.sw[op]
.DS_Store
.wrangler
node_modules
playground/jsonld.js
_site
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,37 @@ A list of all previous specification drafts is also available.

https://json-ld.org/spec/

[list of users]: https://github.com/json-ld/json-ld.org/wiki/Users-of-JSON-LD
Website Development
-------------------

- This site is published using [Eleventy][].
- The site is deployed using [Cloudflare Pages][].
- The [playground][] has a special proxy to handle `http:` URLs.

To develop this website locally:

```sh
# install dependencies
npm i
# to rebuild on changes and run a server:
npm run serve
# to rebuild on changes:
npm run watch
```

Additionally, if you want to use or test the playground `http:` proxy, also run
the [Wrangler][] server to emulate the [Cloudflare Pages Functions][] code:

```sh
npm run dev
```

[Cloudflare Pages Functions]: https://developers.cloudflare.com/pages/functions/
[Cloudflare Pages]: https://pages.cloudflare.com/
[Eleventy]: https://www.11ty.dev/
[JSON-LD 1.1 - A JSON-based Serialization for Linked Data]: http://www.w3.org/TR/json-ld/
[JSON-LD 1.1 Processing Algorithms and API]: https://www.w3.org/TR/json-ld-api/
[JSON-LD 1.1 Framing]: https://www.w3.org/TR/json-ld-framing/
[JSON-LD 1.1 Processing Algorithms and API]: https://www.w3.org/TR/json-ld-api/
[Wrangler]: https://developers.cloudflare.com/workers/wrangler/
[list of users]: https://github.com/json-ld/json-ld.org/wiki/Users-of-JSON-LD
BigBlueHat marked this conversation as resolved.
Show resolved Hide resolved
[playground]: https://json-ld.org/playground/
68 changes: 68 additions & 0 deletions functions/playground/proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// json-ld.org playground proxy
//
// details:
// - Basic proxy for use with the json-ld.org playground.
// - Built for deployment using Cloudflare Pages Functions API
// - https://developers.cloudflare.com/pages/functions/
// - Only handle GET requests.
// - Only designed to be used by the playground, no CORS support.
// - Only handle requests for 'http:' URLs.
BigBlueHat marked this conversation as resolved.
Show resolved Hide resolved
// - Short timeout enough for expected development use cases.
// - Only support JSON-LD and JSON content types for target response.
// - Not intended to be very robust (but improvements welcome).
//
// usage:
// - GET /playground/proxy?url={encoded-url}
// - Proxy errors have JSON content and a
// 'X-JSON-LD-Playground-Proxy-Status' header.

const RESPONSE_HEADER = 'X-JSON-LD-Playground-Proxy-Status';

function makeError({error}) {
return Response.json({error}, {
status: 400,
headers: {
[RESPONSE_HEADER]: '400'
}
});
}

export async function onRequestGet(context) {
const request = context.request;
try {
const requestUrl = new URL(request.url);
const targetUrl = new URL(requestUrl.searchParams.get('url'));
// check self request
if(targetUrl.host === requestUrl.host) {
return makeError({error: 'self request'});
}
// check url protocol
if(targetUrl.protocol !== 'http:') {
return makeError({error: 'unsupported URL protocol'});
}
// make similar request with new target url
const req = new Request(targetUrl, request);
const res = await fetch(req, {
redirect: 'follow',
// fail for long requests
signal: AbortSignal.timeout(3000)
});
// check return type is JSON-LD or JSON
const ct = res.headers.get('content-type');
if(!(ct === 'application/ld+json' || ct === 'application/json')) {
return makeError({error: 'unsupported response content type'});
}
// check if remote seems to be the proxy itself
if(res.headers.has(RESPONSE_HEADER)) {
return makeError({error: 'playground proxy response found'});
}
return res;
} catch(e) {
// special case timeout error
if(e.name === 'TimeoutError') {
return makeError({error: 'timeout'});
}
// fallback error
return makeError({error: 'bad request'});
}
}
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
"scripts": {
"build": "eleventy",
"serve": "eleventy --serve",
"watch": "eleventy --watch",
"dev": "wrangler pages dev _site --compatibility-date=2024-04-27 --live-reload --port 8788",
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"@11ty/eleventy": "^2.0.1"
"@11ty/eleventy": "^3.0.0-alpha.9"
}
}
2 changes: 1 addition & 1 deletion playground/1.0/playground.js
Original file line number Diff line number Diff line change
Expand Up @@ -1480,7 +1480,7 @@
// NOTE: using hard-coded path so file can be shared with dev page
//location.pathname,
'/playground/',
'proxy.php?url=',
'proxy?url=',
url
].join('');
}
Expand Down
53 changes: 0 additions & 53 deletions playground/1.0/proxy.php

This file was deleted.

2 changes: 1 addition & 1 deletion playground/playground.js
Original file line number Diff line number Diff line change
Expand Up @@ -1638,7 +1638,7 @@
// NOTE: using hard-coded path so file can be shared with dev page
//location.pathname,
'/playground/',
'proxy.php?url=',
'proxy?url=',
url
].join('');
}
Expand Down
53 changes: 0 additions & 53 deletions playground/proxy.php

This file was deleted.