Skip to content

Commit

Permalink
use ReadableStream to modify html chunks
Browse files Browse the repository at this point in the history
  • Loading branch information
Lms24 committed Nov 9, 2023
1 parent 272cbbd commit 32a7a04
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 9 deletions.
3 changes: 1 addition & 2 deletions packages/astro/src/server/meta.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Hub } from '@sentry/core';
import { getDynamicSamplingContextFromClient } from '@sentry/core';
import type { Span } from '@sentry/types';
import type { Hub, Span } from '@sentry/types';
import {
dynamicSamplingContextToSentryBaggageHeader,
generateSentryTraceHeader,
Expand Down
42 changes: 35 additions & 7 deletions packages/astro/src/server/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { captureException, configureScope, getCurrentHub, startSpan } from '@sentry/node';
import type { Hub, Span } from '@sentry/types';
import { addExceptionMechanism, objectify, stripUrlQueryAndFragment, tracingContextFromHeaders } from '@sentry/utils';
import type { APIContext, MiddlewareResponseHandler } from 'astro';

Expand Down Expand Up @@ -109,16 +110,29 @@ export const handleRequest: (options?: MiddlewareOptions) => MiddlewareResponseH
return originalResponse;
}

const html = await originalResponse.text();
if (typeof html !== 'string' || !html.includes('<head>')) {
const { body, ...restOfOriginalResponse } = originalResponse;

// Type case necessary b/c the body's ReadableStream type doesn't include
// the async iterator that is actually available in Node
// We later on use the async iterator to read the body chunks
// see https://github.com/microsoft/TypeScript/issues/39051
const originalBody = body as NodeJS.ReadableStream | null;
if (!originalBody) {
return originalResponse;
}

const { sentryTrace, baggage } = getTracingMetaTags(span, hub);
const content = `<head>\n${sentryTrace}\n${baggage}\n`;
const modifiedHtml = html.replace('<head>', content);

return new Response(modifiedHtml, originalResponse);
const newResponseStream = new ReadableStream({
start: async controller => {
for await (const chunk of originalBody) {
const html = typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk);
const modifiedHtml = addMetaTagToHead(html, hub, span);
controller.enqueue(modifiedHtml);
}
controller.close();
},
});

return new Response(newResponseStream, restOfOriginalResponse);
},
);
return res;
Expand All @@ -130,6 +144,20 @@ export const handleRequest: (options?: MiddlewareOptions) => MiddlewareResponseH
};
};

/**
* This function optimistically assumes that the HTML coming in chunks will not be split
* within the <head> tag. If this still happens, we simply won't replace anything.
*/
function addMetaTagToHead(htmlChunk: string, hub: Hub, span?: Span): string {
if (typeof htmlChunk !== 'string') {
return htmlChunk;
}

const { sentryTrace, baggage } = getTracingMetaTags(span, hub);
const content = `<head>\n${sentryTrace}\n${baggage}\n`;
return htmlChunk.replace('<head>', content);
}

/**
* Interpolates the route from the URL and the passed params.
* Best we can do to get a route name instead of a raw URL.
Expand Down

0 comments on commit 32a7a04

Please sign in to comment.