Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -524,26 +524,34 @@ This is due to the fact that `@fastify/multipart` consumes the multipart content

However, the two plugins may be used within the same fastify instance, at the condition that they belong to disjoint branches of the fastify plugins hierarchy tree.

### Proxying multipart/form-data without @fastify/multipart
### Proxying specific content types without parsing

If you need to proxy `multipart/form-data` requests without parsing them, you can use a custom content type parser instead of `@fastify/multipart`:
If you need to proxy certain content types (like `multipart/form-data` or `text/event-stream`) without parsing them, you can use custom content type parsers:

```js
// Register a custom content type parser for multipart/form-data
// This passes the raw body through without parsing
// Register custom content type parsers that pass raw body through
fastify.addContentTypeParser('multipart/form-data', function (req, body, done) {
done(null, body)
})

fastify.addContentTypeParser('text/event-stream', function (req, body, done) {
done(null, body)
})

fastify.register(require('@fastify/reply-from'))

fastify.post('/upload', (request, reply) => {
// The multipart data will be proxied as-is to the upstream server
reply.from('http://upstream-server.com/upload')
})

fastify.post('/events', (request, reply) => {
// The SSE data will be proxied as-is to the upstream server
reply.from('http://upstream-server.com/events')
})
```

This approach allows `multipart/form-data` to be proxied correctly while avoiding the incompatibility with `@fastify/multipart`.
This approach allows these content types to be proxied correctly while avoiding parsing that would consume the request body.

## License

Expand Down
68 changes: 68 additions & 0 deletions test/text-event-stream-custom-parser.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use strict'

const t = require('node:test')
const Fastify = require('fastify')
const { request } = require('undici')
const From = require('..')
const http = require('node:http')

t.test('text/event-stream proxying with custom content type parser', async (t) => {
t.plan(6)

// Target server that sends SSE data
const target = http.createServer((req, res) => {
t.assert.ok('request proxied')
t.assert.strictEqual(req.method, 'POST')
t.assert.match(req.headers['content-type'], /^text\/event-stream/)

let data = ''
req.setEncoding('utf8')
req.on('data', (chunk) => {
data += chunk
})
req.on('end', () => {
// Verify the SSE data is received
t.assert.match(data, /data: test message/)
t.assert.match(data, /event: custom/)

res.setHeader('content-type', 'application/json')
res.statusCode = 200
res.end(JSON.stringify({ received: 'sse data' }))
})
})

// Fastify instance with custom text/event-stream parser
const fastify = Fastify()

// Register custom content type parser for text/event-stream
// This allows the raw body to be passed through without parsing
fastify.addContentTypeParser('text/event-stream', function (req, body, done) {
done(null, body)
})

fastify.register(From)

fastify.post('/', (request, reply) => {
reply.from(`http://localhost:${target.address().port}`)
})

t.after(() => fastify.close())
t.after(() => target.close())

await fastify.listen({ port: 0 })
await target.listen({ port: 0 })

// Create SSE-like data
const sseData = 'data: test message\nevent: custom\ndata: another line\n\n'

// Send request with SSE data
const result = await request(`http://localhost:${fastify.server.address().port}`, {
method: 'POST',
headers: {
'content-type': 'text/event-stream'
},
body: sseData
})

t.assert.deepStrictEqual(await result.body.json(), { received: 'sse data' })
})
Loading