Skip to content

Conversation

@xsalefter
Copy link
Contributor

When Jersey writing a response, it try to set Content-Length header before writing the body [1]. While this is normal for non text/stream response, it breaks SSE since data the data need to flow as a continuous stream.

EventStreamResponseFilter solves that incompatibility. It detects text/event-stream responses and wraps Jersey’s ContainerResponseWriter so enableResponseBuffering() always returns false. The rest of the pipeline stays untouched.

But in the end, this PR alone wont fix killbill/killbill-platform#126. We need another fix there.


[1] : See the source code. But I'm wondering if I'm pointed at the wrong source code. Because if this is indeed the source of truth about enableResponseBuffering() that simply return true, then how Jersey's set ServerProperties.OUTBOUND_CONTENT_LENGTH_BUFFER configuration into account ?

@pierre pierre requested a review from Copilot October 20, 2025 06:58
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a dedicated filter to fix SSE (Server-Sent Events) compatibility issues with Jersey by bypassing response buffering for event-stream responses.

  • Adds EventStreamResponseFilter to detect text/event-stream responses and wrap Jersey's ContainerResponseWriter
  • Configures the filter to always return false for enableResponseBuffering() on SSE responses
  • Includes comprehensive test coverage for the filter behavior

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
EventStreamResponseFilter.java New filter that wraps ContainerResponseWriter to disable buffering for SSE responses
KillbillGuiceListener.java Registers the new EventStreamResponseFilter with Jersey
TestEventStreamResponseFilter.java Comprehensive test suite covering filter behavior for SSE and non-SSE responses

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@xsalefter xsalefter requested review from pierre and sbrossie October 20, 2025 09:22
@sbrossie
Copy link
Member

But in the end, this PR alone wont fix killbill/killbill-platform#126. We need another fix there.

Could you explain how this fix - assuming it works - helps with solving killbill/killbill-platform#126. Which other fix would we require? Are we sure this fix is required?

See the source code. But I'm wondering if I'm pointed at the wrong source code. Because if this is indeed the source of truth about enableResponseBuffering() that simply return true, then how Jersey's set ServerProperties.OUTBOUND_CONTENT_LENGTH_BUFFER configuration into account ?

Regarding the fix itself, could you set a breakpoint when doing regular request and doing SSE to check that it works as expected?

@xsalefter
Copy link
Contributor Author

About,

Regarding the fix itself, could you set a breakpoint when doing regular request and doing SSE to check that it works as expected?

I hope video below is enough:

  • 00.00 : server started
  • 00.25 : Set breakpoint in Intellij
  • 00.29 : Perform regular request to /catalog/versions . The this.eventStreamRequest variable is false, and in 00.38, it just execute line return this.delegate.enableResponseBuffering();
  • 00.58 : Perform call to SSE endpoint /plugins/killbill-osgi-logger/. The this.eventStreamRequest variable is true, and in 01.07 it just return false without execute the this.delegate.enableResponseBuffering(); line.
platform-126-normal-request-vs-sse.webm

@xsalefter
Copy link
Contributor Author

Additionally, I post another video about error: IllegalArgumentException: setContentLength(0) when already written .... . This video executed using Kill Bill's master branch.

  • 00.00 : Show the branch
  • 00.04 : Run with reset && mvn -Dorg.killbill.server.properties=<properties> jetty:run
  • 00.30 : Server started
  • 00.35 : Call curl to sse endpoint and with trace enabled (curl -N -H 'Accept: text/event-stream' --trace-ascii /dev/stderr --trace-time http://localhost:8080/plugins/killbill-osgi-logger/). Once executed, the curl exit. Normally, it shouldn't because it should keep listening (see next video).
  • 00.43 : Seems like the server send content-length header as 0. For SSE endpoint, this is should not happened.
  • 00.48 : The server log still normal.
  • 00.57 : Call the same curl to sse endpoint in 2nd time.
  • 01.02 : Now, the curl command not exit and "listening" properly. but,
  • 01.06 : As you can see, 2nd call causing error java.lang.IllegalArgumentException: setContentLength(0) when already written ....
platform-126-with-master-branch-errors.webm

@xsalefter
Copy link
Contributor Author

Now in this section I'll explain about another error : .... java.lang.IllegalStateException: s=OPEN,api=BLOCKED,sc=false,e=null ..... In this video, I run Kill Bill server with xsalefter:platform-126-add-eventstreamfilter branch.

  • 00.00 : Get compiled at the target branch, and show branch name.
  • 00.08 : Run Kill Bill
  • 00.32 : Server run and ready
  • 00.38 : Call SSE endpoint via curl: curl -N -H 'Accept: text/event-stream' --trace-ascii /dev/stderr --trace-time http://localhost:8080/plugins/killbill-osgi-logger/
  • 00.40 : curl not exit anymore. This is because enableResponseBuffering() now just return false.
  • 00.55 : Server log still normal (no exception)
  • 01.00 : Call /catalog/versions to make sure non heartbeat message sent to client (curl)
  • 01:21 : Prepare to call SSE endpoint from new terminal tab.
  • 01:30 : Connection established, the 2nd tab not exit, but,
  • 01:35 : There's another error introduced: java.lang.IllegalStateException: s=OPEN,api=BLOCKED,sc=false,e=null
platform-126-with-event-filter-branch-error.webm

@xsalefter
Copy link
Contributor Author

Summary:

  1. We have 2 different errors caused by 2 different problems.
  2. The first problem is IllegalArgumentException: setContentLength(0) when already written ..... This is happened because jersey keep enableResponseBuffering() in SSE endpoint. Breakpoints shows in first video. The thrown exception shown in second video.
  3. There's another problem: java.lang.IllegalStateException: s=OPEN,api=BLOCKED,sc=false,e=null . I think this problem could be reproduced consistently when the point 2 fixed (by adding EventStreamResponseFilter). The java.lang.IllegalStateException: s=OPEN,api=BLOCKED,sc=false,e=null problem show in the 3rd video.

So, even without PR in killbill-platform, the EventStreamResponseFilter fix one of exception originally described in the ticket. Now to fix point 3 above, fix should be applied in killbill-platform (osgi-bundles/bundles/logger).

@sbrossie sbrossie merged commit 6e96d39 into killbill:master Nov 20, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SSE Logs - Error while closing the output stream in order to commit response

2 participants