Skip to content

Commit 90cbb20

Browse files
committed
fix: support for redirects with 200 status. Fix crashing dev server when have socket error. Follow redirects from proxyRes
1 parent a87fd59 commit 90cbb20

File tree

10 files changed

+159
-18
lines changed

10 files changed

+159
-18
lines changed

src/utils/proxy.ts

+25-13
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,14 @@ const formatEdgeFunctionError = (errorBuffer, acceptsHtml) => {
122122
})
123123
}
124124

125-
function isInternal(url?: string): boolean {
125+
function isInternalFunctions(url?: string): boolean {
126126
return url?.startsWith('/.netlify/') ?? false
127127
}
128128

129+
function isInternal(url?: string): boolean {
130+
return url?.startsWith('/') ?? false
131+
}
132+
129133
function isFunction(functionsPort: boolean | number | undefined, url: string) {
130134
return functionsPort && url.match(DEFAULT_FUNCTION_URL_EXPRESSION)
131135
}
@@ -202,7 +206,7 @@ const handleAddonUrl = function ({ addonUrl, req, res }) {
202206

203207
// @ts-expect-error TS(7006) FIXME: Parameter 'match' implicitly has an 'any' type.
204208
const isRedirect = function (match) {
205-
return match.status && match.status >= 300 && match.status <= 400
209+
return match.status && match.status >= 300 && match.status < 400
206210
}
207211

208212
// @ts-expect-error TS(7006) FIXME: Parameter 'publicFolder' implicitly has an 'any' t... Remove this comment to see the full error message
@@ -417,8 +421,8 @@ const serveRedirect = async function ({
417421
const ct = req.headers['content-type'] ? contentType.parse(req).type : ''
418422
if (
419423
req.method === 'POST' &&
420-
!isInternal(req.url) &&
421-
!isInternal(destURL) &&
424+
!isInternalFunctions(req.url) &&
425+
!isInternalFunctions(destURL) &&
422426
(ct.endsWith('/x-www-form-urlencoded') || ct === 'multipart/form-data')
423427
) {
424428
return proxy.web(req, res, { target: options.functionsServer })
@@ -433,9 +437,13 @@ const serveRedirect = async function ({
433437
match.force ||
434438
(!staticFile && ((!options.framework && destStaticFile) || isInternal(destURL) || matchingFunction))
435439
) {
440+
// 3xx redirects parsed above, here are 2xx meaning just override the url of proxying page and use the status
441+
// which comes from that url
436442
req.url = destStaticFile ? destStaticFile + dest.search : destURL
437443
const { status } = match
438-
statusValue = status
444+
if (match.force || status !== 200) {
445+
statusValue = status
446+
}
439447
console.log(`${NETLIFYDEVLOG} Rewrote URL to`, req.url)
440448
}
441449

@@ -452,9 +460,11 @@ const serveRedirect = async function ({
452460

453461
return proxy.web(req, res, { headers: functionHeaders, target: options.functionsServer })
454462
}
463+
455464
if (isImageRequest(req)) {
456465
return imageProxy(req, res)
457466
}
467+
458468
const addonUrl = getAddonUrl(options.addonsUrls, req)
459469
if (addonUrl) {
460470
return handleAddonUrl({ req, res, addonUrl })
@@ -519,11 +529,14 @@ const initializeProxy = async function ({
519529
}
520530
})
521531

522-
proxy.on('error', (_, req, res) => {
523-
// @ts-expect-error TS(2339) FIXME: Property 'writeHead' does not exist on type 'Socke... Remove this comment to see the full error message
524-
res.writeHead(500, {
525-
'Content-Type': 'text/plain',
526-
})
532+
proxy.on('error', (err, req, res) => {
533+
console.error('Got error from proxy', err)
534+
535+
if (res instanceof http.ServerResponse) {
536+
res.writeHead(500, {
537+
'Content-Type': 'text/plain',
538+
})
539+
}
527540

528541
const message = isEdgeFunctionsRequest(req)
529542
? 'There was an error with an Edge Function. Please check the terminal for more details.'
@@ -596,8 +609,7 @@ const initializeProxy = async function ({
596609
}
597610
}
598611

599-
// @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
600-
if (req.proxyOptions.staticFile && isRedirect({ status: proxyRes.statusCode }) && proxyRes.headers.location) {
612+
if (isRedirect({ status: proxyRes.statusCode }) && proxyRes.headers.location) {
601613
req.url = proxyRes.headers.location
602614
return serveRedirect({
603615
// We don't want to match functions at this point because any redirects
@@ -838,7 +850,7 @@ const onRequest = async (
838850
hasFormSubmissionHandler &&
839851
functionsServer &&
840852
req.method === 'POST' &&
841-
!isInternal(req.url) &&
853+
!isInternalFunctions(req.url) &&
842854
(ct.endsWith('/x-www-form-urlencoded') || ct === 'multipart/form-data')
843855
) {
844856
return proxy.web(req, res, { target: functionsServer })

tests/integration/__fixtures__/hugo-site/netlify.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
command = "npm run serve"
33
framework = "#custom"
44
functions = "functions/"
5-
port = 7000
5+
port = 7001
66
publish = "out"
77
targetPort = 1313

tests/integration/__fixtures__/next-app/app/page.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export default function Home() {
55
return (
66
<main className={styles.main}>
77
<div className={styles.description}>
8+
<p>Local site Dev Server</p>
89
<p>
910
Get started by editing&nbsp;
1011
<code className={styles.code}>app/page.js</code>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function ExistsPage() {
2+
return <p>Exists page</p>
3+
}

tests/integration/__fixtures__/next-app/netlify.toml

+10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ publish = "public"
44
[dev]
55
targetPort = 6123
66

7+
[[redirects]]
8+
from = "/local/*"
9+
to = "/:splat"
10+
status = 200
11+
12+
[[redirects]]
13+
from = "/test/*"
14+
to = "https://www.netlify.app/:splat"
15+
status = 200
16+
717
[[redirects]]
818
from = "*"
919
to = "https://www.netlify.app/:splat"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.netlify
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const http = require('http')
2+
3+
const server = http.createServer((req, res) => {
4+
const pathname = new URL(req.url, 'http://localhost').pathname
5+
6+
console.log(`Got ${pathname}`)
7+
8+
if (pathname === '/') {
9+
res.write('Root page')
10+
res.end()
11+
} else if (pathname === '/test/exists') {
12+
res.writeHead(302, undefined, { location: '/test/exists/' })
13+
res.end()
14+
} else if (pathname === '/test/exists/') {
15+
res.write('Test exists page')
16+
res.end()
17+
} else if (pathname === '/test/not-allowed') {
18+
res.writeHead(405)
19+
res.write('This not allowed')
20+
res.end()
21+
} else {
22+
res.writeHead(404).write('Page is not found')
23+
res.end()
24+
}
25+
})
26+
27+
server.listen(6124, () => {
28+
console.log('Server is Running')
29+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[dev]
2+
targetPort = 6124
3+
command = "npm run dev"
4+
5+
[[redirects]]
6+
from = "/local/*"
7+
to = "/:splat"
8+
status = 200
9+
10+
[[redirects]]
11+
from = "/local-force/*"
12+
to = "/:splat"
13+
status = 402
14+
force = true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "site-with-redirect",
3+
"version": "1.0.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "node index.js"
7+
},
8+
"dependencies": {}
9+
}

tests/integration/commands/dev/redirects.test.ts

+66-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import fetch from 'node-fetch'
66
describe('redirects', () => {
77
setupFixtureTests('dev-server-with-functions', { devServer: true }, () => {
88
test<FixtureTestContext>('should send original query params to functions', async ({ devServer }) => {
9-
const response = await fetch(`http://localhost:${devServer.port}/with-params?param2=world&other=1`, {})
9+
const response = await fetch(`http://localhost:${devServer.port}/with-params?param2=world&other=1`)
1010

1111
expect(response.status).toBe(200)
1212

@@ -19,7 +19,7 @@ describe('redirects', () => {
1919
test<FixtureTestContext>('should send original query params to functions when using duplicate parameters', async ({
2020
devServer,
2121
}) => {
22-
const response = await fetch(`http://localhost:${devServer.port}/api/echo?param=hello&param=world`, {})
22+
const response = await fetch(`http://localhost:${devServer.port}/api/echo?param=hello&param=world`)
2323

2424
expect(response.status).toBe(200)
2525

@@ -31,23 +31,85 @@ describe('redirects', () => {
3131

3232
setupFixtureTests('next-app', { devServer: { env: { NETLIFY_DEV_SERVER_CHECK_SSG_ENDPOINTS: 1 } } }, () => {
3333
test<FixtureTestContext>('should prefer local files instead of redirect when not forced', async ({ devServer }) => {
34-
const response = await fetch(`http://localhost:${devServer.port}/test.txt`, {})
34+
const response = await fetch(`http://localhost:${devServer.port}/test.txt`)
3535

3636
expect(response.status).toBe(200)
3737

3838
const result = await response.text()
3939
expect(result.trim()).toEqual('hello world')
40+
expect(devServer?.output).not.toContain('Proxying to https://www.netlify.app')
4041
})
4142

4243
test<FixtureTestContext>('should check for the dynamic page existence before doing redirect', async ({
4344
devServer,
4445
}) => {
45-
const response = await fetch(`http://localhost:${devServer.port}/`, {})
46+
const response = await fetch(`http://localhost:${devServer.port}/`)
4647

4748
expect(response.status).toBe(200)
4849

4950
const result = await response.text()
51+
expect(result.toLowerCase()).toContain('local site dev server')
5052
expect(result.toLowerCase()).not.toContain('netlify')
53+
expect(devServer?.output).not.toContain('Proxying to https://www.netlify.app')
54+
})
55+
56+
test<FixtureTestContext>('nested route redirect check for the page existence', async ({ devServer }) => {
57+
let response = await fetch(`http://localhost:${devServer.port}/test/exists`)
58+
expect(response.status).toBe(200)
59+
60+
let result = await response.text()
61+
expect(result.toLowerCase()).toContain('exists page')
62+
expect(devServer?.output).not.toContain('Proxying to https://www.netlify.app/exists')
63+
64+
response = await fetch(`http://localhost:${devServer.port}/test/about`)
65+
expect(response.status).toBe(200)
66+
67+
result = await response.text()
68+
expect(result.toLowerCase()).toContain('netlify')
69+
70+
expect(devServer?.output).toContain('Proxying to https://www.netlify.app/about')
71+
})
72+
73+
test<FixtureTestContext>('should do local redirect', async ({ devServer }) => {
74+
const response = await fetch(`http://localhost:${devServer.port}/local/test/exists`)
75+
76+
expect(response.status).toBe(200)
77+
78+
const result = await response.text()
79+
expect(response.headers.get('location')).toBeNull()
80+
expect(response.status).toBe(200)
81+
expect(result.toLowerCase()).toContain('exists page')
82+
expect(result.toLowerCase()).not.toContain('netlify')
83+
expect(devServer?.output).not.toContain('Proxying to https://www.netlify.app/test')
84+
})
85+
})
86+
87+
setupFixtureTests('site-with-redirect', { devServer: true }, () => {
88+
test<FixtureTestContext>('should do local redirect', async ({ devServer }) => {
89+
const response = await fetch(`http://localhost:${devServer.port}/local/test/exists`)
90+
91+
expect(response.status).toBe(200)
92+
93+
const result = await response.text()
94+
expect(response.url).toEqual(`http://localhost:${devServer.port}/local/test/exists`)
95+
expect(response.status).toBe(200)
96+
expect(result.toLowerCase()).toContain('exists page')
97+
expect(result.toLowerCase()).not.toContain('netlify')
98+
expect(devServer?.output).not.toContain('Proxying to https://www.netlify.app')
99+
})
100+
101+
test<FixtureTestContext>('should pass proper status code of the redirected page', async ({ devServer }) => {
102+
let response = await fetch(`http://localhost:${devServer.port}/local/test/not-allowed`)
103+
104+
expect(response.status).toBe(405)
105+
const result = await response.text()
106+
expect(result.toLowerCase()).toContain('this not allowed')
107+
108+
response = await fetch(`http://localhost:${devServer.port}/local/test/not-found`)
109+
expect(response.status).toBe(404)
110+
111+
response = await fetch(`http://localhost:${devServer.port}/local-force/test/exists`)
112+
expect(response.status).toBe(402)
51113
})
52114
})
53115
})

0 commit comments

Comments
 (0)