Skip to content

Commit 433d8ef

Browse files
authored
RSC: createRscRequestHandler (#9330)
1 parent 104c8e2 commit 433d8ef

2 files changed

Lines changed: 101 additions & 101 deletions

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import busboy from 'busboy'
2+
import type { Request, Response } from 'express'
3+
import RSDWServer from 'react-server-dom-webpack/server.node.unbundled'
4+
5+
import { hasStatusCode } from '../lib/StatusError'
6+
import { renderRSC } from '../waku-lib/rsc-handler-worker'
7+
8+
const { decodeReply, decodeReplyFromBusboy } = RSDWServer
9+
10+
export function createRscRequestHandler() {
11+
// This is mounted at /RSC, so will have /RSC stripped from req.url
12+
return async (req: Request, res: Response) => {
13+
const basePath = '/RSC/'
14+
console.log('basePath', basePath)
15+
console.log('req.originalUrl', req.originalUrl, 'req.url', req.url)
16+
console.log('req.headers.host', req.headers.host)
17+
18+
const url = new URL(req.originalUrl || '', 'http://' + req.headers.host)
19+
let rscId: string | undefined
20+
let props = {}
21+
let rsfId: string | undefined
22+
let args: unknown[] = []
23+
24+
console.log('url.pathname', url.pathname)
25+
26+
if (url.pathname.startsWith(basePath)) {
27+
const index = url.pathname.lastIndexOf('/')
28+
const params = new URLSearchParams(url.pathname.slice(index + 1))
29+
rscId = url.pathname.slice(basePath.length, index)
30+
rsfId = params.get('action_id') || undefined
31+
32+
console.log('rscId', rscId)
33+
console.log('rsfId', rsfId)
34+
35+
if (rscId && rscId !== '_') {
36+
res.setHeader('Content-Type', 'text/x-component')
37+
props = JSON.parse(params.get('props') || '{}')
38+
} else {
39+
rscId = undefined
40+
}
41+
42+
if (rsfId) {
43+
if (req.headers['content-type']?.startsWith('multipart/form-data')) {
44+
const bb = busboy({ headers: req.headers })
45+
const reply = decodeReplyFromBusboy(bb)
46+
47+
req.pipe(bb)
48+
args = await reply
49+
} else {
50+
let body = ''
51+
52+
for await (const chunk of req) {
53+
body += chunk
54+
}
55+
56+
if (body) {
57+
args = await decodeReply(body)
58+
}
59+
}
60+
}
61+
}
62+
63+
if (rscId || rsfId) {
64+
const handleError = (err: unknown) => {
65+
if (hasStatusCode(err)) {
66+
res.statusCode = err.statusCode
67+
} else {
68+
console.info('Cannot render RSC', err)
69+
res.statusCode = 500
70+
}
71+
72+
// Getting a warning on GitHub about this
73+
// https://github.com/redwoodjs/redwood/security/code-scanning/211
74+
// Handle according to TODO below
75+
res.end(String(err))
76+
// TODO (RSC): When we have `yarn rw dev` support we should do this:
77+
// if (options.command === 'dev') {
78+
// res.end(String(err))
79+
// } else {
80+
// res.end()
81+
// }
82+
}
83+
84+
try {
85+
const pipeable = await renderRSC({ rscId, props, rsfId, args })
86+
// TODO (RSC): See if we can/need to do more error handling here
87+
// pipeable.on(handleError)
88+
pipeable.pipe(res)
89+
} catch (e) {
90+
handleError(e)
91+
}
92+
}
93+
}
94+
}

packages/vite/src/runRscFeServer.ts

Lines changed: 7 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,17 @@
66
import fs from 'fs/promises'
77
import path from 'path'
88

9-
import busboy from 'busboy'
109
// @ts-expect-error We will remove dotenv-defaults from this package anyway
1110
import { config as loadDotEnv } from 'dotenv-defaults'
1211
import express from 'express'
1312
import { createProxyMiddleware } from 'http-proxy-middleware'
14-
import isbot from 'isbot'
15-
import RSDWServer from 'react-server-dom-webpack/server.node.unbundled'
1613
import type { Manifest as ViteBuildManifest } from 'vite'
1714

1815
import { getConfig, getPaths } from '@redwoodjs/project-config'
1916

20-
import { hasStatusCode } from './lib/StatusError'
17+
import { createRscRequestHandler } from './rsc/rscRequestHandler'
2118
import { registerFwGlobals } from './streaming/registerGlobals'
22-
import { renderRSC, setClientEntries } from './waku-lib/rsc-handler-worker'
23-
24-
const { decodeReply, decodeReplyFromBusboy } = RSDWServer
19+
import { setClientEntries } from './waku-lib/rsc-handler-worker'
2520

2621
/**
2722
* TODO (STREAMING)
@@ -39,10 +34,7 @@ loadDotEnv({
3934
defaults: path.join(getPaths().base, '.env.defaults'),
4035
multiline: true,
4136
})
42-
//------------------------------------------------
43-
44-
const checkUaForSeoCrawler = isbot.spawn()
45-
checkUaForSeoCrawler.exclude(['chrome-lighthouse'])
37+
// ------------------------------------------------
4638

4739
export async function runFeServer() {
4840
const app = express()
@@ -72,10 +64,7 @@ export async function runFeServer() {
7264
// const routeManifest: RWRouteManifest = JSON.parse(routeManifestStr)
7365

7466
// TODO See above about using `import { with: { type: 'json' } }` instead
75-
const manifestPath = path.join(
76-
getPaths().web.dist,
77-
'client-build-manifest.json'
78-
)
67+
const manifestPath = path.join(rwPaths.web.dist, 'client-build-manifest.json')
7968
const buildManifestStr = await fs.readFile(manifestPath, 'utf-8')
8069
const buildManifest: ViteBuildManifest = JSON.parse(buildManifestStr)
8170

@@ -91,11 +80,11 @@ export async function runFeServer() {
9180
throw new Error('Could not find index.html in build manifest')
9281
}
9382

94-
// 👉 1. Use static handler for assets
83+
// 1. Use static handler for assets
9584
// For CF workers, we'd need an equivalent of this
9685
app.use('/assets', express.static(rwPaths.web.dist + '/assets'))
9786

98-
// 👉 2. Proxy the api server
87+
// 2. Proxy the api server
9988
// TODO (STREAMING) we need to be able to specify whether proxying is required or not
10089
// e.g. deploying to Netlify, we don't need to proxy but configure it in Netlify
10190
// Also be careful of differences between v2 and v3 of the server
@@ -114,91 +103,8 @@ export async function runFeServer() {
114103
})
115104
)
116105

117-
app.use((req, _res, next) => {
118-
console.log('req.url', req.url)
119-
next()
120-
})
121-
122106
// Mounting middleware at /RSC will strip /RSC from req.url
123-
app.use('/RSC', async (req, res) => {
124-
const basePath = '/RSC/'
125-
console.log('basePath', basePath)
126-
console.log('req.originalUrl', req.originalUrl, 'req.url', req.url)
127-
console.log('req.headers.host', req.headers.host)
128-
129-
const url = new URL(req.originalUrl || '', 'http://' + req.headers.host)
130-
let rscId: string | undefined
131-
let props = {}
132-
let rsfId: string | undefined
133-
let args: unknown[] = []
134-
135-
console.log('url.pathname', url.pathname)
136-
137-
if (url.pathname.startsWith(basePath)) {
138-
const index = url.pathname.lastIndexOf('/')
139-
const params = new URLSearchParams(url.pathname.slice(index + 1))
140-
rscId = url.pathname.slice(basePath.length, index)
141-
rsfId = params.get('action_id') || undefined
142-
143-
console.log('rscId', rscId)
144-
console.log('rsfId', rsfId)
145-
146-
if (rscId && rscId !== '_') {
147-
res.setHeader('Content-Type', 'text/x-component')
148-
props = JSON.parse(params.get('props') || '{}')
149-
} else {
150-
rscId = undefined
151-
}
152-
153-
if (rsfId) {
154-
if (req.headers['content-type']?.startsWith('multipart/form-data')) {
155-
const bb = busboy({ headers: req.headers })
156-
const reply = decodeReplyFromBusboy(bb)
157-
158-
req.pipe(bb)
159-
args = await reply
160-
} else {
161-
let body = ''
162-
163-
for await (const chunk of req) {
164-
body += chunk
165-
}
166-
167-
if (body) {
168-
args = await decodeReply(body)
169-
}
170-
}
171-
}
172-
}
173-
174-
if (rscId || rsfId) {
175-
const handleError = (err: unknown) => {
176-
if (hasStatusCode(err)) {
177-
res.statusCode = err.statusCode
178-
} else {
179-
console.info('Cannot render RSC', err)
180-
res.statusCode = 500
181-
}
182-
183-
res.end(String(err))
184-
// TODO (RSC): When we have `yarn rw dev` support we should do this:
185-
// if (options.command === 'dev') {
186-
// res.end(String(err))
187-
// } else {
188-
// res.end()
189-
// }
190-
}
191-
192-
try {
193-
const pipeable = await renderRSC({ rscId, props, rsfId, args })
194-
// TODO (RSC): See if we can/need to do more error handling here
195-
// pipeable.on(handleError)
196-
pipeable.pipe(res)
197-
} catch (e) {
198-
handleError(e)
199-
}
200-
}
201-
})
107+
app.use('/RSC', createRscRequestHandler())
202108

203109
app.use(express.static(rwPaths.web.dist))
204110

0 commit comments

Comments
 (0)