@@ -79,53 +79,12 @@ export const withSentry = (origHandler: NextApiHandler): WrappedNextApiHandler =
79
79
try {
80
80
const handlerResult = await origHandler ( req , res ) ;
81
81
82
- // Temporarily mark the response as finished, as a hack to get nextjs to not complain that we're coming back
83
- // from the handler successfully without `res.end()` having completed its work. This is necessary (and we know
84
- // we can do it safely) for a few reasons:
85
- //
86
- // - Normally, `res.end()` is sync and completes before the request handler returns, as part of the handler
87
- // sending data back to the client. As soon as the handler is done, nextjs checks to make sure that the
88
- // response is indeed finished. (`res.end()` signals this by setting `res.finished` to `true`.) If it isn't,
89
- // nextjs complains. ("Warning: API resolved without sending a response for <url>.")
90
- //
91
- // - In order to prevent the lambda running the route handler from shutting down before we can send events to
92
- // Sentry, we monkeypatch `res.end()` so that we can call `flush()`, wait for it to finish, and only then
93
- // allow the response to be marked complete. This turns the normally-sync `res.end()` into an async function,
94
- // which isn't awaited because it's assumed to still be sync. As a result, nextjs runs the aforementioned
95
- // check before the now-async `res.end()` has had a chance to set `res.finished = false`, and therefore thinks
96
- // there's a problem when there's not.
97
- //
98
- // - In order to trick nextjs into not complaining, we can set `res.finished` to `true` before exiting the
99
- // handler. If we do that, though, `res.end()` gets mad because it thinks *it* should be the one to get to
100
- // mark the response complete. We therefore need to flip it back to `false` 1) after nextjs's check but 2)
101
- // before the original `res.end()` is called.
102
- //
103
- // - The second part is easy - we control when the original `res.end()` is called, so we can do the flipping
104
- // right beforehand and `res.end()` will be none the wiser.
105
- //
106
- // - The first part isn't as obvious. How do we know we won't end up with a race condition, such that the
107
- // flipping to `false` might happen before the check, negating the entire purpose of this hack? Fortunately,
108
- // before it's done, our async `res.end()` wrapper has to await a `setImmediate()` callback, guaranteeing its
109
- // run lasts at least until the next event loop. The check, on the other hand, happens synchronously,
110
- // immediately after the request handler (so in the same event loop). So as long as we wait to flip
111
- // `res.finished` back to `false` until after the `setImmediate` callback has run, we know we'll be safely in
112
- // the next event loop when we do so.
113
- //
114
- // And with that, everybody's happy: Nextjs doesn't complain about an unfinished response, `res.end()` doesn’t
115
- // complain about an already-finished response, and we have time to make sure events are flushed to Sentry.
116
- //
117
- // One final note: It might seem like making `res.end()` an awaited async function would run the danger of
118
- // having the lambda close before it's done its thing, meaning we *still* might not get events sent to Sentry.
119
- // Fortunately, even though it's called `res.end()`, and even though it's normally sync, a) it's far from the
120
- // end of the request process, so there's other stuff which needs to happen before the lambda can close in any
121
- // case, and b) that other stuff isn't triggered until `res.end()` emits a `prefinished` event, so even though
122
- // it's not technically awaited, it's still the case that the process can't go on until it's done.
123
- //
124
- // See
125
- // https://github.com/vercel/next.js/blob/e1464ae5a5061ae83ad015018d4afe41f91978b6/packages/next/server/api-utils.ts#L106-L118
126
- // and
127
- // https://github.com/nodejs/node/blob/d8f1823d5fca5e3c00b19530fb15343fdd3c8bf5/lib/_http_outgoing.js#L833-L911.
128
- res . finished = true ;
82
+ if ( process . env . NODE_ENV === 'development' ) {
83
+ // eslint-disable-next-line no-console
84
+ console . warn (
85
+ '[sentry] If Next.js logs a warning "API resolved without sending a response", it\'s a false positive, which we\'re working to rectify.' ,
86
+ ) ;
87
+ }
129
88
130
89
return handlerResult ;
131
90
} catch ( e ) {
@@ -183,8 +142,11 @@ type WrappedResponseEndMethod = AugmentedNextApiResponse['end'];
183
142
* Wrap `res.end()` so that it closes the transaction and flushes events before letting the request finish.
184
143
*
185
144
* Note: This wraps a sync method with an async method. While in general that's not a great idea in terms of keeping
186
- * things in the right order, in this case it's safe', as explained in detail in the long comment in the main
187
- * `withSentry()` function.
145
+ * things in the right order, in this case it's safe, because the native `.end()` actually *is* async, and its run
146
+ * actually *is* awaited, just manually so (which reflects the fact that the core of the request/response code in Node
147
+ * by far predates the introduction of `async`/`await`). When `.end()` is done, it emits the `prefinish` event, and
148
+ * only once that fires does request processing continue. See
149
+ * https://github.com/nodejs/node/commit/7c9b607048f13741173d397795bac37707405ba7.
188
150
*
189
151
* @param origEnd The original `res.end()` method
190
152
* @returns The wrapped version
@@ -193,11 +155,6 @@ function wrapEndMethod(origEnd: ResponseEndMethod): WrappedResponseEndMethod {
193
155
return async function newEnd ( this : AugmentedNextApiResponse , ...args : unknown [ ] ) {
194
156
await finishSentryProcessing ( this ) ;
195
157
196
- // If the request didn't error, we will have temporarily marked the response finished to avoid a nextjs warning
197
- // message. (See long note above.) Now we need to flip `finished` back to `false` so that the real `res.end()`
198
- // method doesn't throw `ERR_STREAM_WRITE_AFTER_END` (which it will if presented with an already-finished response).
199
- this . finished = false ;
200
-
201
158
return origEnd . call ( this , ...args ) ;
202
159
} ;
203
160
}
0 commit comments