next
Version:
The React Framework
116 lines (115 loc) • 5.85 kB
JavaScript
const OVERRIDE_HEADERS = 'x-middleware-override-headers';
const REQUEST_HEADER_PREFIX = 'x-middleware-request-';
// The trace ID and parent ID of a W3C `traceparent`
// (`version-traceid-parentid-flags`), lower-cased.
function getTraceparentIds(traceparent) {
const [, traceId, parentId] = (traceparent == null ? void 0 : traceparent.toLowerCase().split('-')) ?? [];
return traceId && parentId ? `${traceId}-${parentId}` : undefined;
}
/**
* The trace context of the span that the middleware span and the downstream
* request (the render) share as their parent, given the middleware's active
* context (before the middleware span starts).
*
* When the active context is the one the request carries (an incoming
* `traceparent`, or the one next-server's middleware-invoke pass wrote onto
* the request), the received `traceparent` / `tracestate` are returned
* verbatim, so the middleware never changes them. Otherwise (an active host
* span, or a root span inserted by the middleware adapter) the active
* context is returned.
*/ export function getSharedParentTraceContext(requestHeaders, activeTraceContext) {
if (!activeTraceContext) {
return undefined;
}
const receivedTraceparent = requestHeaders.get('traceparent');
const receivedIds = getTraceparentIds(receivedTraceparent);
if (receivedTraceparent && receivedIds && receivedIds === getTraceparentIds(activeTraceContext.traceparent)) {
const receivedTracestate = requestHeaders.get('tracestate');
return receivedTracestate ? {
traceparent: receivedTraceparent,
tracestate: receivedTracestate
} : {
traceparent: receivedTraceparent,
tracestate: undefined
};
}
return activeTraceContext;
}
/**
* Makes sure the downstream request of a middleware response that continues
* the request (`NextResponse.next()` or a rewrite) carries the trace context
* of the middleware span's parent, so the render is a sibling of the
* middleware span in the same trace. It is passed as `traceparent` /
* `tracestate` request-header overrides, which every host applies to the
* downstream request: hosts that invoke middleware separately from the render
* (deployments, adapters, direct entrypoints) as well as next-server's router
* server.
*
* `traceparent` and `tracestate` are forwarded as a pair: the downstream
* request never carries one trace context's `traceparent` with another's
* `tracestate`. Request-header overrides replace the whole downstream request
* header set, so:
* - Without user overrides (or with an empty list, which hosts ignore):
* nothing is added when the request already carries that trace context.
* Otherwise every request header the middleware received is listed
* (unchanged) alongside the trace context, except a received `tracestate`
* when the trace context has none.
* - With user overrides, they are kept and the trace context headers are
* added. A `traceparent` the user explicitly set (to a value other than the
* one the middleware received) wins, together with whatever `tracestate`
* the user set (if any). Otherwise (`traceparent` copied unchanged from the
* request, e.g. via `new Headers(request.headers)`, or omitted) both are set
* to the trace context, dropping a user `tracestate` when it has none.
*/ export function forwardTraceContextToRequest(responseHeaders, requestHeaders, traceContext) {
if (!traceContext) {
return;
}
// Redirects and direct responses have no downstream request.
if (!responseHeaders.has('x-middleware-next') && !responseHeaders.has('x-middleware-rewrite')) {
return;
}
const userOverrides = responseHeaders.get(OVERRIDE_HEADERS);
// An empty override list is ignored by hosts (no headers are replaced), so
// treat it like no overrides rather than turning it into "only the trace
// context headers".
if (!userOverrides) {
if (requestHeaders.get('traceparent') === traceContext.traceparent && requestHeaders.get('tracestate') === (traceContext.tracestate ?? null)) {
// The downstream request already carries the trace context.
return;
}
const keys = [];
for (const [key, value] of requestHeaders){
if (!isTraceHeader(key)) {
responseHeaders.set(REQUEST_HEADER_PREFIX + key, value);
keys.push(key);
}
}
setTraceContextOverrides(responseHeaders, keys, traceContext);
return;
}
const keys = userOverrides.split(',').map((key)=>key.trim()).filter(Boolean);
if (keys.includes('traceparent') && responseHeaders.get(REQUEST_HEADER_PREFIX + 'traceparent') !== requestHeaders.get('traceparent')) {
// The user explicitly set the `traceparent`: keep both trace headers as
// the user set them.
return;
}
setTraceContextOverrides(responseHeaders, keys.filter((key)=>!isTraceHeader(key)), traceContext);
}
function isTraceHeader(key) {
return key === 'traceparent' || key === 'tracestate';
}
// Lists the trace context's `traceparent` and `tracestate` (or no
// `tracestate`, if it has none) as request-header overrides, after `keys`
// (the other overridden headers).
function setTraceContextOverrides(responseHeaders, keys, traceContext) {
responseHeaders.set(REQUEST_HEADER_PREFIX + 'traceparent', traceContext.traceparent);
keys.push('traceparent');
if (traceContext.tracestate) {
responseHeaders.set(REQUEST_HEADER_PREFIX + 'tracestate', traceContext.tracestate);
keys.push('tracestate');
} else {
responseHeaders.delete(REQUEST_HEADER_PREFIX + 'tracestate');
}
responseHeaders.set(OVERRIDE_HEADERS, keys.join(','));
}
//# sourceMappingURL=forward-trace-context.js.map