diff --git a/README.md b/README.md index df23e7a3..0f84f995 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/test/text-event-stream-custom-parser.test.js b/test/text-event-stream-custom-parser.test.js new file mode 100644 index 00000000..6f4a4183 --- /dev/null +++ b/test/text-event-stream-custom-parser.test.js @@ -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' }) +})