-
Notifications
You must be signed in to change notification settings - Fork 27.4k
/
Copy pathsend-payload.ts
151 lines (130 loc) · 3.87 KB
/
send-payload.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import type { IncomingMessage, ServerResponse } from 'http'
import type RenderResult from './render-result'
import type { Revalidate, SwrDelta } from './lib/revalidate'
import { isResSent } from '../shared/lib/utils'
import { generateETag } from './lib/etag'
import fresh from 'next/dist/compiled/fresh'
import { formatRevalidate } from './lib/revalidate'
import { RSC_CONTENT_TYPE_HEADER } from '../client/components/app-router-headers'
export function sendEtagResponse(
req: IncomingMessage,
res: ServerResponse,
etag: string | undefined
): boolean {
if (etag) {
/**
* The server generating a 304 response MUST generate any of the
* following header fields that would have been sent in a 200 (OK)
* response to the same request: Cache-Control, Content-Location, Date,
* ETag, Expires, and Vary. https://tools.ietf.org/html/rfc7232#section-4.1
*/
res.setHeader('ETag', etag)
}
if (fresh(req.headers, { etag })) {
res.statusCode = 304
res.end()
return true
}
return false
}
export async function sendRenderResult({
req,
res,
result,
type,
generateEtags,
poweredByHeader,
revalidate,
swrDelta,
}: {
req: IncomingMessage
res: ServerResponse
result: RenderResult
type: 'html' | 'json' | 'rsc'
generateEtags: boolean
poweredByHeader: boolean
revalidate: Revalidate | undefined
swrDelta: SwrDelta | undefined
}): Promise<void> {
if (isResSent(res)) {
return
}
if (poweredByHeader && type === 'html') {
res.setHeader('X-Powered-By', 'Next.js')
}
if (typeof revalidate !== 'undefined') {
res.setHeader(
'Cache-Control',
formatRevalidate({
revalidate,
swrDelta,
})
)
}
const payload = result.isDynamic ? null : result.toUnchunkedString()
if (payload !== null) {
let etagPayload = payload
if (type === 'rsc') {
// ensure etag generation is deterministic as
// ordering can differ even if underlying content
// does not differ
etagPayload = payload.split('\n').sort().join('\n')
} else if (type === 'html' && payload.includes('__next_f')) {
const { parse } =
require('next/dist/compiled/node-html-parser') as typeof import('next/dist/compiled/node-html-parser')
try {
// Parse the HTML
let root = parse(payload)
// Get script tags in the body element
let scriptTags = root
.querySelector('body')
?.querySelectorAll('script')
.filter(
(node) =>
!node.hasAttribute('src') && node.innerHTML?.includes('__next_f')
)
// Sort the script tags by their inner text
scriptTags?.sort((a, b) => a.innerHTML.localeCompare(b.innerHTML))
// Remove the original script tags
scriptTags?.forEach((script: any) => script.remove())
// Append the sorted script tags to the body
scriptTags?.forEach((script: any) =>
root.querySelector('body')?.appendChild(script)
)
// Stringify back to HTML
etagPayload = root.toString()
} catch (err) {
console.error(`Error parsing HTML payload`, err)
}
}
const etag = generateEtags ? generateETag(etagPayload) : undefined
if (sendEtagResponse(req, res, etag)) {
return
}
}
if (!res.getHeader('Content-Type')) {
res.setHeader(
'Content-Type',
result.contentType
? result.contentType
: type === 'rsc'
? RSC_CONTENT_TYPE_HEADER
: type === 'json'
? 'application/json'
: 'text/html; charset=utf-8'
)
}
if (payload) {
res.setHeader('Content-Length', Buffer.byteLength(payload))
}
if (req.method === 'HEAD') {
res.end(null)
return
}
if (payload !== null) {
res.end(payload)
return
}
// Pipe the render result to the response after we get a writer for it.
await result.pipeToNodeResponse(res)
}