From f07af8fe6eb3d9503e8d649d3d93e95a0dba338a Mon Sep 17 00:00:00 2001 From: cha0s Date: Fri, 16 Feb 2024 21:02:10 -0600 Subject: [PATCH] fix: allow request aborting --- packages/react/src/server.js | 2 +- packages/web/build/flecks.hooks.js | 10 ++++++- packages/web/src/server/abort.js | 1 + packages/web/src/server/config.js | 4 ++- packages/web/src/server/http.js | 30 ++++++++++++++++--- packages/web/test/abort-request.js | 17 +++++++++++ .../abort-request}/build/flecks.yml | 0 .../abort-request}/package.json | 0 .../abort-request}/test/package.json | 0 .../templates/abort-request/test/src/index.js | 1 + .../abort-request/test/src/server.js | 5 ++++ .../web/test/templates/up/build/flecks.yml | 4 +++ packages/web/test/templates/up/package.json | 1 + .../web/test/templates/up/test/package.json | 1 + .../up}/test/src/client.js | 0 .../up}/test/src/index.js | 0 .../up}/test/src/server.js | 0 packages/web/test/up.js | 13 ++++---- 18 files changed, 75 insertions(+), 14 deletions(-) create mode 100644 packages/web/src/server/abort.js create mode 100644 packages/web/test/abort-request.js rename packages/web/test/{template => templates/abort-request}/build/flecks.yml (100%) rename packages/web/test/{template => templates/abort-request}/package.json (100%) rename packages/web/test/{template => templates/abort-request}/test/package.json (100%) create mode 100644 packages/web/test/templates/abort-request/test/src/index.js create mode 100644 packages/web/test/templates/abort-request/test/src/server.js create mode 100644 packages/web/test/templates/up/build/flecks.yml create mode 100644 packages/web/test/templates/up/package.json create mode 100644 packages/web/test/templates/up/test/package.json rename packages/web/test/{template => templates/up}/test/src/client.js (100%) rename packages/web/test/{template => templates/up}/test/src/index.js (100%) rename packages/web/test/{template => templates/up}/test/src/server.js (100%) diff --git a/packages/react/src/server.js b/packages/react/src/server.js index 4ae08bc..04a47fd 100644 --- a/packages/react/src/server.js +++ b/packages/react/src/server.js @@ -5,7 +5,7 @@ import ssr from './ssr'; export const hooks = { '@flecks/electron/server.extensions': (installer) => [installer.REACT_DEVELOPER_TOOLS], '@flecks/web/server.stream.html': Flecks.priority( - (stream, req, flecks) => ( + (stream, req, res, flecks) => ( flecks.get('@flecks/react.ssr') ? ssr(stream, req, flecks) : stream ), {after: '@flecks/web/server'}, diff --git a/packages/web/build/flecks.hooks.js b/packages/web/build/flecks.hooks.js index efcaebe..036a171 100644 --- a/packages/web/build/flecks.hooks.js +++ b/packages/web/build/flecks.hooks.js @@ -49,11 +49,19 @@ export const hooks = { }, /** * Define composition functions to run over the HTML stream prepared for the client. + * * @param {stream.Readable} stream The HTML stream. * @param {http.ClientRequest} req The HTTP request object. + * @param {http.ServerResponse} res The HTTP response object. + * @returns {stream.Duplex} The stream to pipe to the response. * @invoke ComposedAsync */ - '@flecks/web/server.stream.html': (stream, req) => { + '@flecks/web/server.stream.html': (stream, req, res) => { + // You may call `req.abort()` to abort the request if you e.g. respond to it early: + if ('some-redirect-condition') { + res.redirect('/somewhere', 301); + req.abort(); + } return stream.pipe(myTransformStream); }, /** diff --git a/packages/web/src/server/abort.js b/packages/web/src/server/abort.js new file mode 100644 index 0000000..db6ba10 --- /dev/null +++ b/packages/web/src/server/abort.js @@ -0,0 +1 @@ +export class Abort extends Error {} diff --git a/packages/web/src/server/config.js b/packages/web/src/server/config.js index b98cc19..8d4adf0 100644 --- a/packages/web/src/server/config.js +++ b/packages/web/src/server/config.js @@ -56,4 +56,6 @@ class InlineConfig extends Transform { } -export const inlineConfig = (stream, req, flecks) => stream.pipe(new InlineConfig(flecks, req)); +export const inlineConfig = (stream, req, res, flecks) => ( + stream.pipe(new InlineConfig(flecks, req)) +); diff --git a/packages/web/src/server/http.js b/packages/web/src/server/http.js index 15e1069..66e761b 100644 --- a/packages/web/src/server/http.js +++ b/packages/web/src/server/http.js @@ -10,6 +10,8 @@ import compression from 'compression'; import express from 'express'; import httpProxy from 'http-proxy'; +import {Abort} from './abort'; + const { FLECKS_CORE_ROOT = process.cwd(), FLECKS_WEB_DEV_SERVER, @@ -19,8 +21,28 @@ const { const debug = D('@flecks/web/server/http'); -const deliverHtmlStream = async (stream, flecks, req, res) => { - (await flecks.invokeComposedAsync('@flecks/web/server.stream.html', stream, req)).pipe(res); +const deliverHtmlStream = async (stream, req, res, flecks) => { + req.abort = () => { + throw new Abort(); + }; + let walk = stream; + const implementations = flecks.flecksImplementing('@flecks/web/server.stream.html'); + for (let i = 0; i < implementations.length; ++i) { + const fleck = implementations[i]; + const implementation = flecks.fleckImplementation(fleck, '@flecks/web/server.stream.html'); + try { + // eslint-disable-next-line no-await-in-loop + walk = await implementation(walk, req, res, flecks); + } + catch (error) { + if (error instanceof Abort) { + res.status(500).end(); + return; + } + throw error; + } + } + walk.pipe(res); }; export const createHttpServer = async (flecks) => { @@ -169,7 +191,7 @@ export const createHttpServer = async (flecks) => { if (!res.headersSent) { res.setHeader('Content-Type', proxyRes.headers['content-type']); } - deliverHtmlStream(proxyRes, flecks, req, res); + deliverHtmlStream(proxyRes, req, res, flecks); }); } // Any other assets. @@ -211,7 +233,7 @@ export const createHttpServer = async (flecks) => { if (req.accepts('text/html')) { res.setHeader('Content-Type', 'text/html; charset=UTF-8'); const stream = createReadStream(join(FLECKS_CORE_ROOT, 'dist', 'web', 'index.html')); - deliverHtmlStream(stream, flecks, req, res); + deliverHtmlStream(stream, req, res, flecks); } else { res.status(400).end('Bad Request'); diff --git a/packages/web/test/abort-request.js b/packages/web/test/abort-request.js new file mode 100644 index 0000000..7d85ec1 --- /dev/null +++ b/packages/web/test/abort-request.js @@ -0,0 +1,17 @@ +import {expect} from 'chai'; + +import {withWeb} from '@flecks/headless/test/helpers/with-web'; + +it('allows request aborting', withWeb( + async ({ + response, + }) => { + expect(response) + .to.not.be.null; + expect(response.ok()) + .to.be.false; + }, + { + template: 'templates/abort-request', + }, +)); diff --git a/packages/web/test/template/build/flecks.yml b/packages/web/test/templates/abort-request/build/flecks.yml similarity index 100% rename from packages/web/test/template/build/flecks.yml rename to packages/web/test/templates/abort-request/build/flecks.yml diff --git a/packages/web/test/template/package.json b/packages/web/test/templates/abort-request/package.json similarity index 100% rename from packages/web/test/template/package.json rename to packages/web/test/templates/abort-request/package.json diff --git a/packages/web/test/template/test/package.json b/packages/web/test/templates/abort-request/test/package.json similarity index 100% rename from packages/web/test/template/test/package.json rename to packages/web/test/templates/abort-request/test/package.json diff --git a/packages/web/test/templates/abort-request/test/src/index.js b/packages/web/test/templates/abort-request/test/src/index.js new file mode 100644 index 0000000..7a4049b --- /dev/null +++ b/packages/web/test/templates/abort-request/test/src/index.js @@ -0,0 +1 @@ +export const hooks = {}; diff --git a/packages/web/test/templates/abort-request/test/src/server.js b/packages/web/test/templates/abort-request/test/src/server.js new file mode 100644 index 0000000..b07b4e4 --- /dev/null +++ b/packages/web/test/templates/abort-request/test/src/server.js @@ -0,0 +1,5 @@ +export const hooks = { + '@flecks/web/server.stream.html': async (stream, req) => { + req.abort(); + }, +}; diff --git a/packages/web/test/templates/up/build/flecks.yml b/packages/web/test/templates/up/build/flecks.yml new file mode 100644 index 0000000..da10f99 --- /dev/null +++ b/packages/web/test/templates/up/build/flecks.yml @@ -0,0 +1,4 @@ +'@flecks/core': {} +'@flecks/server': {} +'@flecks/web': {} +'test:./test': {} diff --git a/packages/web/test/templates/up/package.json b/packages/web/test/templates/up/package.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/packages/web/test/templates/up/package.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/web/test/templates/up/test/package.json b/packages/web/test/templates/up/test/package.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/packages/web/test/templates/up/test/package.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/web/test/template/test/src/client.js b/packages/web/test/templates/up/test/src/client.js similarity index 100% rename from packages/web/test/template/test/src/client.js rename to packages/web/test/templates/up/test/src/client.js diff --git a/packages/web/test/template/test/src/index.js b/packages/web/test/templates/up/test/src/index.js similarity index 100% rename from packages/web/test/template/test/src/index.js rename to packages/web/test/templates/up/test/src/index.js diff --git a/packages/web/test/template/test/src/server.js b/packages/web/test/templates/up/test/src/server.js similarity index 100% rename from packages/web/test/template/test/src/server.js rename to packages/web/test/templates/up/test/src/server.js diff --git a/packages/web/test/up.js b/packages/web/test/up.js index 8f74011..6b4e6ce 100644 --- a/packages/web/test/up.js +++ b/packages/web/test/up.js @@ -4,12 +4,6 @@ import {withWeb} from '@flecks/headless/test/helpers/with-web'; let report; -const options = { - beforeConnect: ({socket}) => { - report = socket.waitForAction('report'); - }, -}; - it('brings a client up', withWeb( async ({ page, @@ -37,5 +31,10 @@ it('brings a client up', withWeb( expect(request) .to.equal('testing-value-value'); }, - options, + { + beforeConnect: ({socket}) => { + report = socket.waitForAction('report'); + }, + template: 'templates/up', + }, ));