From ebc2e991a909d33f4720914c08d2704ae2d3f0b2 Mon Sep 17 00:00:00 2001 From: Luiz Ferraz Date: Mon, 7 Apr 2025 09:57:17 -0300 Subject: [PATCH 1/2] Add test for expected behavior --- packages/astro/test/server/middleware.test.ts | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/packages/astro/test/server/middleware.test.ts b/packages/astro/test/server/middleware.test.ts index eff473d4401f..08dcb6112421 100644 --- a/packages/astro/test/server/middleware.test.ts +++ b/packages/astro/test/server/middleware.test.ts @@ -149,6 +149,49 @@ describe('sentryMiddleware', () => { }); }); + it('throws and sends an error to sentry if response streaming throws', async () => { + const captureExceptionSpy = vi.spyOn(SentryNode, 'captureException'); + + const middleware = handleRequest(); + const ctx = { + request: { + method: 'GET', + url: '/users', + headers: new Headers(), + }, + url: new URL('https://myDomain.io/users/'), + params: {}, + }; + + const error = new Error('Something went wrong'); + + const faultyStream = new ReadableStream({ + pull: controller => { + controller.error(error); + controller.close(); + }, + }); + + const next = vi.fn(() => + Promise.resolve( + new Response(faultyStream, { + headers: new Headers({ 'content-type': 'text/html' }), + }), + ), + ); + + // @ts-expect-error, a partial ctx object is fine here + const resultFromNext = await middleware(ctx, next); + + expect(resultFromNext?.headers.get('content-type')).toEqual('text/html'); + + await expect(() => resultFromNext!.text()).rejects.toThrowError(); + + expect(captureExceptionSpy).toHaveBeenCalledWith(error, { + mechanism: { handled: false, type: 'astro', data: { function: 'astroMiddleware' } }, + }); + }); + describe('track client IP address', () => { it('attaches client IP if `trackClientIp=true` when handling dynamic page requests', async () => { const middleware = handleRequest({ trackClientIp: true }); From 40731fd507b07bc762e12323afa5b06397dac5e1 Mon Sep 17 00:00:00 2001 From: Luiz Ferraz Date: Mon, 7 Apr 2025 09:59:30 -0300 Subject: [PATCH 2/2] Implement streaming error handling --- packages/astro/src/server/middleware.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index 6b55dbd8a976..692a8a7256a3 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -184,12 +184,18 @@ async function instrumentRequest( const newResponseStream = new ReadableStream({ start: async controller => { - for await (const chunk of originalBody) { - const html = typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true }); - const modifiedHtml = addMetaTagToHead(html); - controller.enqueue(new TextEncoder().encode(modifiedHtml)); + try { + for await (const chunk of originalBody) { + const html = typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true }); + const modifiedHtml = addMetaTagToHead(html); + controller.enqueue(new TextEncoder().encode(modifiedHtml)); + } + } catch (e) { + sendErrorToSentry(e); + controller.error(e); + } finally { + controller.close(); } - controller.close(); }, });