How to implement Server Sent Events using Win32 APIs correctly?

Shyam Butani 425 Reputation points
2025-11-11T04:50:53.4433333+00:00

Hello,

I'm working on WinRT/C++ application and trying to implement Server Sent Event(SSE) using Win32 HTTP APIs. My current code looks like: https://gist.github.com/ShyamB-TW/b39392dbbf745933a59d837f4606b051

In this, I'm able to receive the request coming from the client (React app using EventSouce for SSE) and also able to send the response with some data. However, it's not keeping the connection open as it should. I've added a logic to send response at some time interval but after the initial response I sent (with headers, "text/event-stream", "no-cache", "keep-alive"), on the next response it is failing and returning the error code 1229 (ERROR_HTTP_INVALID_HEADER or ERROR_CONNECTION_INVALID).

If you can point out the the step where I'm making the mistake, it would be a great help.

Thanks.

Windows development | Windows API - Win32
0 comments No comments
{count} votes

Answer accepted by question author
  1. Tom Tran (WICLOUD CORPORATION) 3,120 Reputation points Microsoft External Staff Moderator
    2025-11-11T10:42:29.2233333+00:00

    Hi @Shyam Butani ,

    Thank you for the details!

    I looked through your flow and here’s what I think why the connection closes with 1229:

    • After sending headers with HttpSendHttpResponse, you call it again for each SSE event. Once headers are sent, HTTP.sys expects body-only writes; re-sending headers causes ERROR_HTTP_INVALID_HEADER / ERROR_CONNECTION_INVALID. The correct flow is: headers once, then stream body via HttpSendResponseEntityBody.
    • Also, your first send uses flags = 0 , it should include HTTP_SEND_RESPONSE_FLAG_MORE_DATA to keep the response open. Finally, drop Connection: keep-alive (invalid on HTTP/2, here's why) and use text/event-stream; charset=utf-8 per SSE spec.

    Here is what I would try:


    1. Send the headers only once, and mark the response as streaming.

    First call:

    ULONG flags = HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
    HTTP_RESPONSE resp{};
    resp.StatusCode = 200;
    resp.pReason = "OK";
    resp.ReasonLength = (USHORT)strlen("OK");
    resp.Headers.KnownHeaders[HttpHeaderContentType].pRawValue = "text/event-stream; charset=utf-8";
    resp.Headers.KnownHeaders[HttpHeaderContentType].RawValueLength = (USHORT)strlen("text/event-stream; charset=utf-8");
    resp.Headers.KnownHeaders[HttpHeaderCacheControl].pRawValue = "no-cache";
    resp.Headers.KnownHeaders[HttpHeaderCacheControl].RawValueLength = (USHORT)strlen("no-cache");
    // DO NOT set Connection
    
    ULONG result = HttpSendHttpResponse(g_reqQueue, request->RequestId, flags,
                                        &resp, NULL, NULL, NULL, 0, NULL, NULL);
    

    This tells HTTP.sys: more entity-body data will follow later.


    2. Stream each SSE event with HttpSendResponseEntityBody (not HttpSendHttpResponse)

    Inside your loop:

    std::string evt = "data: {\"message\":\"Hello from SSE, count: " + std::to_string(i) + "\"}\n\n";
    HTTP_DATA_CHUNK chunk{};
    chunk.DataChunkType = HttpDataChunkFromMemory;
    chunk.FromMemory.pBuffer = (PVOID)evt.data();
    chunk.FromMemory.BufferLength = (ULONG)evt.size();
    
    ULONG flags = HTTP_SEND_RESPONSE_FLAG_MORE_DATA; // keep streaming
    result = HttpSendResponseEntityBody(g_reqQueue, request->RequestId, flags,
                                        1, &chunk, NULL, NULL, 0, NULL, NULL);
    

    Use MORE_DATA for every non-final chunk; on the last chunk, omit the flag so HTTP.sys finalizes the response.


    3. Remove Connection: keep-alive from your headers.

    It’s unnecessary for HTTP/1.1 and invalid for HTTP/2; letting HTTP.sys manage the connection avoids protocol errors.


    4. Don’t send Content-Length.

    When you stream with MORE_DATA, HTTP.sys handles the semantics; you don’t need (and shouldn’t set) Content-Length. The official overview shows the intended sequence: headers via HttpSendHttpResponse, then body via HttpSendResponseEntityBody.


    Inside your event loop you call:

    result = HttpSendHttpResponse(g_reqQueue, request->RequestId, 0, &response, ...);
    

    This might be the core issue, switch this to HttpSendResponseEntityBody and stop re-sending headers. Also, on your very first HttpSendHttpResponse, set HTTP_SEND_RESPONSE_FLAG_MORE_DATA.


    Disclaimer: Some of these links are not from Microsoft official documentation so only use for information. Do not click on or download any strange links that you might encounter.


    I hope my suggestions help! If you have any questions, please comment below. I'll be happy to help!


1 additional answer

Sort by: Most helpful
  1. Q&A Assist
    2025-11-11T04:51:11.3+00:00
    AI generated content. This question contains an answer created with AI from Q&A Assist. Learn more

    To implement Server Sent Events (SSE) correctly using Win32 HTTP APIs, you need to ensure that the connection remains open and that you are sending the correct headers and data format. Here are some key points to consider:

    1. Connection Management: After sending the initial response, you must keep the connection open. This means you should not close the connection after sending the first event. Instead, you should continuously send data at intervals without closing the connection.
    2. Correct Headers: Make sure you are setting the appropriate headers for SSE:
      • Content-Type: text/event-stream
      • Cache-Control: no-cache
      • Connection: keep-alive
    3. Sending Data: When sending data, ensure that each message follows the SSE format, which typically looks like this:
         data: Your data here
         
      
      Note that there is a blank line after each message, which is crucial for the client to recognize the end of the message.
    4. Error Handling: The error code 1229 (ERROR_HTTP_INVALID_HEADER or ERROR_CONNECTION_INVALID) suggests that there might be an issue with how the headers are being sent or that the connection is being closed prematurely. Ensure that you are not modifying the headers after the initial response and that you are handling the connection appropriately.
    5. Asynchronous Handling: If you are using asynchronous calls, ensure that your callbacks are correctly set up and that you are not blocking the main thread, as this could lead to connection issues.
    6. Debugging: Use logging to capture the responses and any errors that occur. This can help you identify where the connection might be failing.

    By following these guidelines, you should be able to implement SSE effectively using Win32 HTTP APIs. If you continue to face issues, consider reviewing your code for any discrepancies in how the headers and data are being managed after the initial response.


Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.