Skip to content
Merged
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
2 changes: 2 additions & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2

### :bug: Bug Fixes

* fix(instrumentation-fetch): Handling null-body-status responses [#6037](https://github.com/open-telemetry/opentelemetry-js/pull/6037) @m0sa

### :books: Documentation

### :house: Internal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -507,8 +507,14 @@ export class FetchInstrumentation extends InstrumentationBase<FetchInstrumentati
const body = resClone.body;
if (body) {
const reader = body.getReader();

const wrappedBody = withCancelPropagation(response.body, reader);
const isNullBodyStatus =
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume these statuses are the ones with null body according to a spec. But my experience tells me some apis may not follow these specs and return body anyways. I wonder if it's okay to not process the body in this scenario

Copy link
Contributor Author

@m0sa m0sa Oct 29, 2025

Choose a reason for hiding this comment

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

The new Response({ /* non-null-wrapped-body */ }, { status: 204 /* or 101, 205, 304 */ }) constructor always throws:

Image

The only way of processing it further would mean avoid reconstructing it / preserving a code path that was in place prior to #5894

Copy link
Contributor

Choose a reason for hiding this comment

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

well, backends could be written in different languages and those may let the dev pass a payload. According to https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status we should not expect payloads for these statuses so I'm fine. If there is interest in reading the body even with these cases we can add it later.

// 101 responses and protocol upgrading is handled internally by the browser
response.status === 204 ||
response.status === 205 ||
response.status === 304;
const wrappedBody = isNullBodyStatus
? null
: withCancelPropagation(response.body, reader);

proxiedResponse = new Response(wrappedBody, {
status: response.status,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,15 @@ describe('fetch', () => {
msw.http.get('/boom', () => {
return new msw.HttpResponse(null, { status: 500 });
}),
msw.http.get('/null-body-204', () => {
return new msw.HttpResponse(null, { status: 204 });
}),
msw.http.get('/null-body-205', () => {
return new msw.HttpResponse(null, { status: 205 });
}),
msw.http.get('/null-body-304', () => {
return new msw.HttpResponse(null, { status: 304 });
}),
],
callback = () => fetch('/api/status.json'),
config = {},
Expand All @@ -391,6 +400,40 @@ describe('fetch', () => {
return { rootSpan, response };
};

describe('null-bodied response', () => {
// https://chromium.googlesource.com/chromium/src/+/ac85ca2a9cb8c76a37f9d7a6c611c24114f1f05d/third_party/WebKit/Source/core/fetch/Response.cpp#106
it('204 (No Content) will correctly end the span', async () => {
await tracedFetch({
callback: () => fetch('/null-body-204'),
});
assert.strictEqual(exportedSpans.length, 1);
assert.strictEqual(
exportedSpans[0].attributes[ATTR_HTTP_STATUS_CODE],
204
);
});
it('205 (Reset Content) will correctly end the span', async () => {
await tracedFetch({
callback: () => fetch('/null-body-205'),
});
assert.strictEqual(exportedSpans.length, 1);
assert.strictEqual(
exportedSpans[0].attributes[ATTR_HTTP_STATUS_CODE],
205
);
});
it('304 (Not Modified) will correctly end the span', async () => {
await tracedFetch({
callback: () => fetch('/null-body-304'),
});
assert.strictEqual(exportedSpans.length, 1);
assert.strictEqual(
exportedSpans[0].attributes[ATTR_HTTP_STATUS_CODE],
304
);
});
});

describe('simple request', () => {
let rootSpan: api.Span | undefined;
let response: Response | undefined;
Expand Down