forked from github/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
299 lines (259 loc) · 12.1 KB
/
index.js
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
import fs from 'fs'
import path from 'path'
import express from 'express'
import instrument from '../lib/instrument-middleware.js'
import haltOnDroppedConnection from './halt-on-dropped-connection.js'
import abort from './abort.js'
import timeout from './timeout.js'
import morgan from 'morgan'
import datadog from './connect-datadog.js'
import helmet from './helmet.js'
import cookieParser from './cookie-parser.js'
import {
setDefaultFastlySurrogateKey,
setLanguageFastlySurrogateKey,
} from './set-fastly-surrogate-key.js'
import reqUtils from './req-utils.js'
import recordRedirect from './record-redirect.js'
import handleErrors from './handle-errors.js'
import handleInvalidPaths from './handle-invalid-paths.js'
import handleNextDataPath from './handle-next-data-path.js'
import detectLanguage from './detect-language.js'
import context from './context.js'
import shortVersions from './contextualizers/short-versions.js'
import languageCodeRedirects from './redirects/language-code-redirects.js'
import handleRedirects from './redirects/handle-redirects.js'
import findPage from './find-page.js'
import blockRobots from './block-robots.js'
import archivedEnterpriseVersionsAssets from './archived-enterprise-versions-assets.js'
import api from './api/index.js'
import healthz from './healthz.js'
import anchorRedirect from './anchor-redirect.js'
import remoteIP from './remote-ip.js'
import buildInfo from './build-info.js'
import archivedEnterpriseVersions from './archived-enterprise-versions.js'
import robots from './robots.js'
import earlyAccessLinks from './contextualizers/early-access-links.js'
import categoriesForSupport from './categories-for-support.js'
import triggerError from './trigger-error.js'
import ghesReleaseNotes from './contextualizers/ghes-release-notes.js'
import ghaeReleaseNotes from './contextualizers/ghae-release-notes.js'
import whatsNewChangelog from './contextualizers/whats-new-changelog.js'
import layout from './contextualizers/layout.js'
import currentProductTree from './contextualizers/current-product-tree.js'
import genericToc from './contextualizers/generic-toc.js'
import breadcrumbs from './contextualizers/breadcrumbs.js'
import glossaries from './contextualizers/glossaries.js'
import features from './contextualizers/features.js'
import productExamples from './contextualizers/product-examples.js'
import productGroups from './contextualizers/product-groups.js'
import featuredLinks from './featured-links.js'
import learningTrack from './learning-track.js'
import next from './next.js'
import renderPage from './render-page.js'
import assetPreprocessing from './asset-preprocessing.js'
import archivedAssetRedirects from './archived-asset-redirects.js'
import favicons from './favicons.js'
import setStaticAssetCaching from './static-asset-caching.js'
import fastHead from './fast-head.js'
import fastlyCacheTest from './fastly-cache-test.js'
import trailingSlashes from './trailing-slashes.js'
import fastlyBehavior from './fastly-behavior.js'
const { DEPLOYMENT_ENV, NODE_ENV } = process.env
const isTest = NODE_ENV === 'test' || process.env.GITHUB_ACTIONS === 'true'
// By default, logging each request (with morgan), is on. And by default
// it's off if you're in a production environment or running automated tests.
// But if you set the env var, that takes precedence.
const ENABLE_DEV_LOGGING = JSON.parse(
process.env.ENABLE_DEV_LOGGING || !(DEPLOYMENT_ENV === 'azure' || isTest)
)
const ENABLE_FASTLY_TESTING = JSON.parse(process.env.ENABLE_FASTLY_TESTING || 'false')
// Catch unhandled promise rejections and passing them to Express's error handler
// https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
const asyncMiddleware = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next)
}
export default function (app) {
// *** Request connection management ***
if (!isTest) app.use(timeout)
app.use(abort)
// Don't use the proxy's IP, use the requester's for rate limiting or
// logging.
// See https://expressjs.com/en/guide/behind-proxies.html
// Essentially, setting this means it believe that the IP is the
// first of the `X-Forwarded-For` header values.
// If it was 0 (or false), the value would be that
// of `req.socket.remoteAddress`.
// Now, the `req.ip` becomes the first entry from x-forwarded-for
// and falls back on `req.socket.remoteAddress` in all other cases.
// Their documentation says:
//
// If true, the client's IP address is understood as the
// left-most entry in the X-Forwarded-For header.
//
app.set('trust proxy', true)
// *** Request logging ***
if (ENABLE_DEV_LOGGING) {
app.use(morgan('dev'))
}
// *** Observability ***
if (process.env.DD_API_KEY) {
app.use(datadog)
}
// Must appear before static assets and all other requests
// otherwise we won't be able to benefit from that functionality
// for static assets as well.
app.use(setDefaultFastlySurrogateKey)
// It can come before `rateLimit` because if it's a
// 200 OK, the rate limiting won't matter anyway.
// archivedEnterpriseVersionsAssets must come before static/assets
app.use(
asyncMiddleware(
instrument(archivedEnterpriseVersionsAssets, './archived-enterprise-versions-assets')
)
)
app.use(favicons)
// Any static URL that contains some sort of checksum that makes it
// unique gets the "manual" surrogate key. If it's checksummed,
// it's bound to change when it needs to change. Otherwise,
// we want to make sure it doesn't need to be purged just because
// there's a production deploy.
// Note, for `/assets/cb-*...` requests,
// this needs to come before `assetPreprocessing` because
// the `assetPreprocessing` middleware will rewrite `req.url` if
// it applies.
app.use(setStaticAssetCaching)
// Must come before any other middleware for assets
app.use(archivedAssetRedirects)
// This must come before the express.static('assets') middleware.
app.use(assetPreprocessing)
app.use(
'/assets/',
express.static('assets', {
index: false,
etag: false,
// Can be aggressive because images inside the content get unique
// URLs with a cache busting prefix.
maxAge: '7 days',
immutable: process.env.NODE_ENV !== 'development',
// This means, that if you request a file that starts with /assets/
// any file doesn't exist, don't bother (NextJS) rendering a
// pretty HTML error page.
fallthrough: false,
})
)
app.use(
'/public/',
express.static('data/graphql', {
index: false,
etag: false,
maxAge: '7 days', // A bit longer since releases are more sparse
// See note about about use of 'fallthrough'
fallthrough: false,
})
)
// In development, let NextJS on-the-fly serve the static assets.
// But in production, don't let NextJS handle any static assets
// because they are costly to generate (the 404 HTML page).
if (process.env.NODE_ENV !== 'development') {
const assetDir = path.join('.next', 'static')
if (!fs.existsSync(assetDir))
throw new Error(`${assetDir} directory has not been generated. Run 'npm run build' first.`)
app.use(
'/_next/static/',
express.static(assetDir, {
index: false,
etag: false,
maxAge: '365 days',
immutable: true,
// See note about about use of 'fallthrough'
fallthrough: false,
})
)
}
// *** Early exits ***
app.use(instrument(handleInvalidPaths, './handle-invalid-paths'))
app.use(instrument(handleNextDataPath, './handle-next-data-path'))
// *** Security ***
app.use(helmet)
app.use(cookieParser)
app.use(express.json())
if (ENABLE_FASTLY_TESTING) {
app.use(fastlyBehavior) // FOR TESTING.
}
// *** Headers ***
app.set('etag', false) // We will manage our own ETags if desired
// *** Config and context for redirects ***
app.use(reqUtils) // Must come before record-redirect and events
app.use(recordRedirect)
app.use(instrument(detectLanguage, './detect-language')) // Must come before context, breadcrumbs, find-page, handle-errors, homepages
app.use(asyncMiddleware(instrument(context, './context'))) // Must come before early-access-*, handle-redirects
app.use(instrument(shortVersions, './contextualizers/short-versions')) // Support version shorthands
// Must come before handleRedirects.
// This middleware might either redirect to serve something.
app.use(asyncMiddleware(instrument(archivedEnterpriseVersions, './archived-enterprise-versions')))
// *** Redirects, 3xx responses ***
// I ordered these by use frequency
app.use(instrument(trailingSlashes, './redirects/trailing-slashes'))
app.use(instrument(languageCodeRedirects, './redirects/language-code-redirects')) // Must come before contextualizers
app.use(instrument(handleRedirects, './redirects/handle-redirects')) // Must come before contextualizers
// *** Config and context for rendering ***
app.use(asyncMiddleware(instrument(findPage, './find-page'))) // Must come before archived-enterprise-versions, breadcrumbs, featured-links, products, render-page
app.use(instrument(blockRobots, './block-robots'))
// Check for a dropped connection before proceeding
app.use(haltOnDroppedConnection)
// *** Rendering, 2xx responses ***
app.use('/api', instrument(api, './api'))
app.use('/healthz', instrument(healthz, './healthz'))
app.use('/anchor-redirect', instrument(anchorRedirect, './anchor-redirect'))
app.get('/_ip', instrument(remoteIP, './remoteIP'))
app.get('/_build', instrument(buildInfo, './buildInfo'))
// Things like `/api` sets their own Fastly surrogate keys.
// Now that the `req.language` is known, set it for the remaining endpoints
app.use(setLanguageFastlySurrogateKey)
// Check for a dropped connection before proceeding (again)
app.use(haltOnDroppedConnection)
app.use(instrument(robots, './robots'))
app.use(
/(\/.*)?\/early-access$/,
instrument(earlyAccessLinks, './contextualizers/early-access-links')
)
app.use(
'/categories.json',
asyncMiddleware(instrument(categoriesForSupport, './categories-for-support'))
)
app.get('/_500', asyncMiddleware(instrument(triggerError, './trigger-error')))
// Check for a dropped connection before proceeding (again)
app.use(haltOnDroppedConnection)
// Specifically deal with HEAD requests before doing the slower
// full page rendering.
app.head('/*', fastHead)
// *** Preparation for render-page: contextualizers ***
app.use(asyncMiddleware(instrument(ghesReleaseNotes, './contextualizers/ghes-release-notes')))
app.use(asyncMiddleware(instrument(ghaeReleaseNotes, './contextualizers/ghae-release-notes')))
app.use(asyncMiddleware(instrument(whatsNewChangelog, './contextualizers/whats-new-changelog')))
app.use(instrument(layout, './contextualizers/layout'))
app.use(asyncMiddleware(instrument(currentProductTree, './contextualizers/current-product-tree')))
app.use(asyncMiddleware(instrument(genericToc, './contextualizers/generic-toc')))
app.use(instrument(breadcrumbs, './contextualizers/breadcrumbs'))
app.use(instrument(features, './contextualizers/features'))
app.use(asyncMiddleware(instrument(productExamples, './contextualizers/product-examples')))
app.use(asyncMiddleware(instrument(productGroups, './contextualizers/product-groups')))
app.use(instrument(glossaries, './contextualizers/glossaries'))
app.use(asyncMiddleware(instrument(featuredLinks, './featured-links')))
app.use(asyncMiddleware(instrument(learningTrack, './learning-track')))
if (ENABLE_FASTLY_TESTING) {
// The fastlyCacheTest middleware is intended to be used with Fastly to test caching behavior.
// This middleware will intercept ALL requests routed to it, so be careful if you need to
// make any changes to the following line:
app.use('/fastly-cache-test', fastlyCacheTest)
}
// handle serving NextJS bundled code (/_next/*)
app.use(instrument(next, './next'))
// Check for a dropped connection before proceeding (again)
app.use(haltOnDroppedConnection)
// *** Rendering, must go almost last ***
app.get('/*', asyncMiddleware(instrument(renderPage, './render-page')))
// *** Error handling, must go last ***
app.use(handleErrors)
}