Allow flushing HTTP headers
As user I would very much like to deliver http status and headers in the context of a chunked http response before sending any body chunks.
The challenge I'm facing
I am currently implementing SSE in the inversify framework. I'm super interested in allowing users to use uWebSockets.js. When implementing sse events, the first message might arrive after an undetermined period of time. It seems chunked responses are handled via HttpResponse.write, but I really don't want to wait to send headers until I got the first chunk for it could take ages. Invoking HttpResponse.write with an empty body is not an option, it does not trigger sending the crlf start of the body mark. It could be great if we could have a way to flush headers and send the mark so we can inform http clients our disposition to eventually deliver body chunks. Otherwise clients might fail after a given timeout.
The proposal
Update HttpResponse with a flush headers method:
template <bool SSL>
struct HttpResponse : public AsyncSocket<SSL> {
public:
void flushHeaders() {
/* Write status if not already done */
writeStatus(HTTP_200_OK);
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) {
/* Write mark on first call to write */
writeMark();
writeHeader("Transfer-Encoding", "chunked");
httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED;
/* Start of the body */
Super::write("\r\n", 2);
}
}
}
This way we could propagate it to uWebSockets.js
I'd be glad to submit a PR or discuss about any implementation details. If I'm missing something and I'm not understanding the way I'm supposed to solve my current challenge, I'd be more than glad to know the right way to go ❤️
This sounds very reasonable, but can you please modify this example https://github.com/uNetworking/uWebSockets.js/blob/master/examples/ServerSentEvents.js so that the timeout issue triggers, then we have a test to fix and I don't need to jump into this before a clear failure case.
Should be just increasing this to 60000 or something, right? https://github.com/uNetworking/uWebSockets.js/blob/6499438b41ac8fff22bc9429860fc3ecd9787fce/examples/ServerSentEvents.js#L35
This code does not actually "flush headers", it assumes write mode, so if you "flush headers" in a normal non-write flow, it will break it. So the naming flushHeaders is very misrepresenting
Something like beginWrite could work since the function is idempotent and can be called any time before you write
This sounds very reasonable, but can you please modify this example https://github.com/uNetworking/uWebSockets.js/blob/master/examples/ServerSentEvents.js so that the timeout issue triggers, then we have a test to fix and I don't need to jump into this before a clear failure case.
Sure, but I think we don't need to update the code example. My point is: there's no written rule about timeouts in this context. 60 seconds sounds reasonable, but if you are expecting to recreate the issue, I think you can actually recreate it with a single javascript fetch request (The eventsource spec is on top of the fetch spec, so I think it's a great way to test it):
void (async () => {
try {
const response = await fetch("http://localhost:9001", {
cache: "no-store",
credentials: "include",
mode: "no-cors",
redirect: "follow",
signal: AbortSignal.timeout(500),
});
console.log(response);
} catch (error) {
console.log(error.message);
}
})();
void (async () => {
try {
const response = await fetch("http://localhost:9001", {
cache: "no-store",
credentials: "include",
mode: "no-cors",
redirect: "follow",
signal: AbortSignal.timeout(2500),
});
console.log(response);
} catch (error) {
console.log(error.message);
}
})();
The first request is aborted after 500 ms, so it fails. The second request is not aborted, we got the start of the body on time so we can get a Response:
Listening to port 9001
Client with id: 1763642922215 connected, starting streaming
Client with id: 1763642922216 connected, starting streaming
The operation was aborted due to timeout
Client with id: 1763642922215 disconnected
Warning: uWS.HttpResponse writes must be made from within a corked callback. See documentation for uWS.HttpResponse.cork and consult the user manual.
Response {
status: 200,
statusText: 'OK',
headers: Headers {
'content-type': 'text/event-stream',
connection: 'keep-alive',
'cache-control': 'no-cache',
date: 'Thu, 20 Nov 2025 12:48:43 GMT',
uwebsockets: '20',
'transfer-encoding': 'chunked'
},
body: ReadableStream { locked: false, state: 'readable', supportsBYOB: true },
bodyUsed: false,
ok: true,
redirected: false,
type: 'basic',
url: 'http://localhost:9001/'
}
Warning: uWS.HttpResponse writes must be made from within a corked callback. See documentation for uWS.HttpResponse.cork and consult the user manual.
Client with id: 1763642922216 disconnected
Having said that, I can do the update in the example with a longer timeout if you want it 🙂.
This code does not actually "flush headers", it assumes write mode, so if you "flush headers" in a normal non-write flow, it will break it. So the naming flushHeaders is very misrepresenting
Oh, I see, absolutely.
Something like beginWrite could work since the function is idempotent and can be called any time before you write
Sure, I think it's a better name, let's use it instead.
Hey @uNetworkingAB, I submitted #1897, is it anything else I should do? Thanks in advance 🙂