Skip to content

Commit 280152f

Browse files
committed
fix(server): prevent compression of server-sent events in CompressionPlugin
1 parent d31094d commit 280152f

File tree

3 files changed

+73
-1
lines changed

3 files changed

+73
-1
lines changed

packages/server/src/adapters/fetch/compression-plugin.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,4 +517,31 @@ describe('compressionPlugin', () => {
517517

518518
expect(filter).toHaveBeenCalledWith(expect.any(Request), response)
519519
})
520+
521+
it('should not compress event iterator responses', async () => {
522+
const handler = new RPCHandler(os.handler(async function* () {
523+
yield 'event1'
524+
yield 'event2'
525+
}), {
526+
plugins: [
527+
new CompressionPlugin(),
528+
],
529+
})
530+
531+
const { response } = await handler.handle(new Request('https://example.com/', {
532+
method: 'POST',
533+
headers: {
534+
'content-type': 'text/event-stream',
535+
'accept-encoding': 'gzip',
536+
},
537+
body: JSON.stringify({}),
538+
}))
539+
540+
expect(response?.headers.get('content-encoding')).toBeNull()
541+
expect(response?.status).toBe(200)
542+
543+
const text = await response?.text()
544+
expect(text).toContain('event1')
545+
expect(text).toContain('event2')
546+
})
520547
})

packages/server/src/adapters/node/compression-plugin.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,27 @@ describe('compressionPlugin', () => {
150150
expect(res.status).toBe(500)
151151
expect(res.headers['content-encoding']).toBeUndefined()
152152
})
153+
154+
it('should not compress event iterator responses', async () => {
155+
const res = await request(async (req: IncomingMessage, res: ServerResponse) => {
156+
const handler = new RPCHandler(os.handler(async function* () {
157+
yield 'yield1'
158+
yield 'yield2'
159+
}), {
160+
plugins: [
161+
new CompressionPlugin(),
162+
],
163+
})
164+
165+
await handler.handle(req, res)
166+
})
167+
.post('/')
168+
.set('accept-encoding', 'gzip, deflate')
169+
.send({ input: 'test' })
170+
171+
expect(res.status).toBe(200)
172+
expect(res.headers['content-encoding']).toBeUndefined()
173+
expect(res.text).toContain('yield1')
174+
expect(res.text).toContain('yield2')
175+
})
153176
})

packages/server/src/adapters/node/compression-plugin.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
import type { NodeHttpRequest, NodeHttpResponse } from '@orpc/standard-server-node'
12
import type { Context } from '../../context'
23
import type { NodeHttpHandlerOptions } from './handler'
34
import type { NodeHttpHandlerPlugin } from './plugin'
45
import compression from '@orpc/interop/compression'
56

67
export interface CompressionPluginOptions extends compression.CompressionOptions {
8+
/**
9+
* A filter function to determine if a response should be compressed.
10+
* This function is called in addition to the default compression checks
11+
* and allows for custom compression logic based on the request and response.
12+
*/
13+
filter?: (req: NodeHttpRequest, res: NodeHttpResponse) => boolean
714
}
815
/**
916
* The Compression Plugin adds response compression to the Node.js HTTP Server.
@@ -14,7 +21,22 @@ export class CompressionPlugin<T extends Context> implements NodeHttpHandlerPlug
1421
private readonly compressionHandler: ReturnType<typeof compression>
1522

1623
constructor(options: CompressionPluginOptions = {}) {
17-
this.compressionHandler = compression(options)
24+
this.compressionHandler = compression({
25+
...options,
26+
filter: (req, res) => {
27+
if (res.getHeader('content-type')?.toString().startsWith('text/event-stream')) {
28+
return false
29+
}
30+
31+
if (!compression.filter(req, res)) {
32+
return false
33+
}
34+
35+
return options.filter
36+
? options.filter(req, res)
37+
: true
38+
},
39+
})
1840
}
1941

2042
initRuntimeAdapter(options: NodeHttpHandlerOptions<T>): void {

0 commit comments

Comments
 (0)