Skip to content

Commit 3851478

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

File tree

10 files changed

+156
-11
lines changed

10 files changed

+156
-11
lines changed

src/utils/proxy.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,14 @@ const formatEdgeFunctionError = (errorBuffer, acceptsHtml) => {
121121
})
122122
}
123123

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

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

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

207211
// @ts-expect-error TS(7006) FIXME: Parameter 'publicFolder' implicitly has an 'any' t... Remove this comment to see the full error message
@@ -415,8 +419,8 @@ const serveRedirect = async function ({
415419
const ct = req.headers['content-type'] ? contentType.parse(req).type : ''
416420
if (
417421
req.method === 'POST' &&
418-
!isInternal(req.url) &&
419-
!isInternal(destURL) &&
422+
!isInternalFunctions(req.url) &&
423+
!isInternalFunctions(destURL) &&
420424
(ct.endsWith('/x-www-form-urlencoded') || ct === 'multipart/form-data')
421425
) {
422426
return proxy.web(req, res, { target: options.functionsServer })
@@ -431,9 +435,13 @@ const serveRedirect = async function ({
431435
match.force ||
432436
(!staticFile && ((!options.framework && destStaticFile) || isInternal(destURL) || matchingFunction))
433437
) {
438+
// 3xx redirects parsed above, here are 2xx meaning just override the url of proxying page and use the status
439+
// which comes from that url
434440
req.url = destStaticFile ? destStaticFile + dest.search : destURL
435441
const { status } = match
436-
statusValue = status
442+
if (match.force || status !== 200) {
443+
statusValue = status
444+
}
437445
console.log(`${NETLIFYDEVLOG} Rewrote URL to`, req.url)
438446
}
439447

@@ -450,9 +458,11 @@ const serveRedirect = async function ({
450458

451459
return proxy.web(req, res, { headers: functionHeaders, target: options.functionsServer })
452460
}
461+
453462
if (isImageRequest(req)) {
454463
return imageProxy(req, res)
455464
}
465+
456466
const addonUrl = getAddonUrl(options.addonsUrls, req)
457467
if (addonUrl) {
458468
return handleAddonUrl({ req, res, addonUrl })
@@ -518,6 +528,8 @@ const initializeProxy = async function ({
518528
})
519529

520530
proxy.on('error', (err, req, res, proxyUrl) => {
531+
console.error('Got error from proxy', err)
532+
521533
// @ts-expect-error TS(2339) FIXME: Property 'proxyOptions' does not exist on type 'In... Remove this comment to see the full error message
522534
const options = req.proxyOptions
523535

@@ -632,7 +644,11 @@ const initializeProxy = async function ({
632644
}
633645
}
634646

647+
<<<<<<< HEAD
635648
if (options.staticFile && isRedirect({ status: proxyRes.statusCode }) && proxyRes.headers.location) {
649+
=======
650+
if (isRedirect({ status: proxyRes.statusCode }) && proxyRes.headers.location) {
651+
>>>>>>> 90cbb20c (fix: support for redirects with 200 status. Fix crashing dev server when have socket error. Follow redirects from proxyRes)
636652
req.url = proxyRes.headers.location
637653
return serveRedirect({
638654
// We don't want to match functions at this point because any redirects
@@ -875,7 +891,7 @@ const onRequest = async (
875891
hasFormSubmissionHandler &&
876892
functionsServer &&
877893
req.method === 'POST' &&
878-
!isInternal(req.url) &&
894+
!isInternalFunctions(req.url) &&
879895
(ct.endsWith('/x-www-form-urlencoded') || ct === 'multipart/form-data')
880896
) {
881897
return proxy.web(req, res, { target: functionsServer })

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 0 deletions
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>
Lines changed: 3 additions & 0 deletions
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

Lines changed: 10 additions & 0 deletions
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"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.netlify
Lines changed: 29 additions & 0 deletions
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+
})
Lines changed: 14 additions & 0 deletions
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
Lines changed: 9 additions & 0 deletions
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

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { withSiteBuilder } from '../../utils/site-builder.js'
88
describe('redirects', () => {
99
setupFixtureTests('dev-server-with-functions', { devServer: true }, () => {
1010
test<FixtureTestContext>('should send original query params to functions', async ({ devServer }) => {
11-
const response = await fetch(`http://localhost:${devServer.port}/with-params?param2=world&other=1`, {})
11+
const response = await fetch(`http://localhost:${devServer.port}/with-params?param2=world&other=1`)
1212

1313
expect(response.status).toBe(200)
1414

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

2626
expect(response.status).toBe(200)
2727

@@ -33,23 +33,85 @@ describe('redirects', () => {
3333

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

3838
expect(response.status).toBe(200)
3939

4040
const result = await response.text()
4141
expect(result.trim()).toEqual('hello world')
42+
expect(devServer?.output).not.toContain('Proxying to https://www.netlify.app')
4243
})
4344

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

4950
expect(response.status).toBe(200)
5051

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

0 commit comments

Comments
 (0)