fix: allow request aborting

This commit is contained in:
cha0s 2024-02-16 21:02:10 -06:00
parent 85aa9d78ca
commit f07af8fe6e
18 changed files with 75 additions and 14 deletions

View File

@ -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'},

View File

@ -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);
},
/**

View File

@ -0,0 +1 @@
export class Abort extends Error {}

View File

@ -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))
);

View File

@ -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');

View File

@ -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',
},
));

View File

@ -0,0 +1 @@
export const hooks = {};

View File

@ -0,0 +1,5 @@
export const hooks = {
'@flecks/web/server.stream.html': async (stream, req) => {
req.abort();
},
};

View File

@ -0,0 +1,4 @@
'@flecks/core': {}
'@flecks/server': {}
'@flecks/web': {}
'test:./test': {}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
{}

View File

@ -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',
},
));