Skip to content

Add websocket support#236

Closed
gre-42 wants to merge 1 commit into
etr:masterfrom
gre-42:master
Closed

Add websocket support#236
gre-42 wants to merge 1 commit into
etr:masterfrom
gre-42:master

Conversation

@gre-42

@gre-42 gre-42 commented Jun 21, 2021

Copy link
Copy Markdown

Issue or RFC Endorsed by Maintainers

Enable WebSockets #20.

Description of the Change

Added websocket support.

Alternate Designs

Extending the existing http_resource class was considered, but rejected in favor of a new class and multiple inheritance.

Possible Drawbacks

libmicrohttpserver does not yet compile the websocket-support by default. It must be done by building libmicrohttpd/src/microhttpd_ws.

Verification Process

Created and tested an example application hello_world_websocket.cpp under Ubuntu 20.04.

Release Notes

Added support for websockets.

@nawaz1991

Copy link
Copy Markdown

@etr , @gre-42 ,
Any ETA of merging this PR.
PS: I am using libhttpserver in my project for a REST server and planning to consume websocket in as well in next phase.

@etr

etr commented Sep 10, 2021

Copy link
Copy Markdown
Owner

Sorry for the lack on feedback and thanks for the support and for the code contribution.

I think the main reason I cannot go with this change as is currently wired is that it uses a custom version/configuration of libmicrohttpd without making it optional. I think that needs to be solved before we can merge this change - many consumers of the library use libmicrohttpd as installed by their OSes and this would practically break their builds entirely.

@nawaz1991

Copy link
Copy Markdown

Sorry for the lack on feedback and thanks for the support and for the code contribution.

I think the main reason I cannot go with this change as is currently wired is that it uses a custom version/configuration of libmicrohttpd without making it optional. I think that needs to be solved before we can merge this change - many consumers of the library use libmicrohttpd as installed by their OSes and this would practically break their builds entirely.

Thanks for the quick reply.

@jvo203

jvo203 commented Jan 22, 2024

Copy link
Copy Markdown

Whilst I appreciate concerns for the existing user base, change is inevitable. It is called progress. Existing projects relying on libmicrohttpd would need to update / re-compile their code. Just like all the other open-source projects do when there are API breaks in their codebases and / or dependencies.

I've been a faithful libmicrohttpd user for nearly 10 years. It is just great, the API is ergonomic, the MHD_create_response_from_pipe() function is much appreciated too. But the lack of WebSocket support has always forced me into "clunky" hybrid solutions: for example right now using libmicrohttpd on port 8080 to handle HTTP and a separate thread running the C mongoose library on port 8081 to handle WebSockets. Dealing with two different C APIs as well as frequent changes in the C mongoose API (change is inevitable but hopefully not too frequently!). Not ideal at all.

@jvo203

jvo203 commented Jan 22, 2024

Copy link
Copy Markdown

Verification Process

Created and tested an example application hello_world_websocket.cpp under Ubuntu 20.04.

Hi are there any plans to provide a C-only WebSocket example? My project uses FORTRAN and pure C with no C++ whatsoever.

@LeSpocky

LeSpocky commented Oct 17, 2025

Copy link
Copy Markdown
Contributor

@jvo203

jvo203 commented Oct 17, 2025

Copy link
Copy Markdown

@LeSpocky that's really a pity.

@LeSpocky

Copy link
Copy Markdown
Contributor

The upgrade handling is still in place. I guess you would just need to handle the socket you get with some other code. Will look into that later.

@jvo203

jvo203 commented Oct 17, 2025

Copy link
Copy Markdown

That's great to hear. This is what I am already doing, handling the WebSocket messages (a TCP connection) in a custom C code after the initial upgrade.

@jvo203

jvo203 commented Oct 17, 2025

Copy link
Copy Markdown

Here is some sample code:

static size_t ws_receive_frame(unsigned char *frame, size_t *length, int *type)
{
    unsigned char masks[4];
    unsigned char mask;
    unsigned char flength;
    unsigned char idx_first_mask;
    unsigned char idx_first_data;
    size_t data_length, consumed;
    int i;
    int j;

    *type = frame[0] & 0x0F;
#ifdef DEBUG
    printf("[C] ws_receive_frame type %d, processing %zu bytes.\n", *type, *length);
#endif

    if (frame[0] == (WS_FIN | WS_OPCODE_CON_CLOSE_FRAME))
        return 0;

    consumed = 0; // assume no data is consumed
    if (frame[0] == (WS_FIN | WS_OPCODE_TEXT_FRAME) || frame[0] == (WS_FIN | WS_OPCODE_BINARY_FRAME) || frame[0] == (WS_FIN | WS_OPCODE_PING_FRAME) || frame[0] == (WS_FIN | WS_OPCODE_PONG_FRAME))
    {
        idx_first_mask = 2;
        mask = frame[1];
        flength = mask & 0x7F;

        if (flength == 126)
        {
            idx_first_mask = 4;

            // the following 2 bytes interpreted as a 16-bit unsigned integer are the data length
            data_length = ((size_t)frame[2] << 8) | (size_t)frame[3];
        }
        else if (flength == 127)
        {
            idx_first_mask = 10;

            // the following 8 bytes interpreted as a 64-bit unsigned integer
            // (the most significant bit MUST be 0) are the data length
            data_length = ((size_t)frame[2] << 56) | ((size_t)frame[3] << 48) | ((size_t)frame[4] << 40) | ((size_t)frame[5] << 32) | ((size_t)frame[6] << 24) | ((size_t)frame[7] << 16) | ((size_t)frame[8] << 8) | (size_t)frame[9];
        }
        else
        {
            data_length = (size_t)flength;
        }

#ifdef DEBUG
        printf("[C] ws_receive_frame: flength: %d, data_length: %zu\n", flength, data_length);
#endif

        idx_first_data = (unsigned char)(idx_first_mask + 4);

        masks[0] = frame[idx_first_mask + 0];
        masks[1] = frame[idx_first_mask + 1];
        masks[2] = frame[idx_first_mask + 2];
        masks[3] = frame[idx_first_mask + 3];

        // return if there is insufficient data to complete the frame
        if (idx_first_data + data_length > *length)
        {
            // blank out the type
            *type = 0;
            return 0;
        }

        // decode the message
        for (i = idx_first_data, j = 0; j < (int)data_length; i++, j++)
            frame[j] = frame[i] ^ masks[j % 4]; // neat, overwrite the incoming frame buffer

        // the entire WebSocket frame has been processed
        consumed = i; // the number of bytes consumed (equal to idx_first_data + data_length)

        *length = data_length;
        if (*type == WS_OPCODE_TEXT_FRAME)
            frame[j] = '\0';
    }
    else
        printf("[C] ws_receive_frame: received an unknown frame %02X (%d).\n", frame[0], *type);

    return consumed;
}

size_t preamble_ws_frame(char **frame_data, size_t length, unsigned char type)
{
    unsigned char *frame;
    unsigned char idx_first_data;

    if (length <= 125)
    {
        idx_first_data = 2;
    }
    else if (0xFFFF < length)
    {
        idx_first_data = 10;
    }
    else
    {
        idx_first_data = 4;
    }

    frame = malloc(idx_first_data + length);

    if (frame == NULL)
        return 0;

    frame[0] = type;

    if (length <= 125)
    {
        frame[1] = length & 0x7F;
        idx_first_data = 2;
    }
    else if (0xFFFF < length)
    {
        frame[1] = 127;
        frame[2] = (unsigned char)((length >> 56) & 0xFF);
        frame[3] = (unsigned char)((length >> 48) & 0xFF);
        frame[4] = (unsigned char)((length >> 40) & 0xFF);
        frame[5] = (unsigned char)((length >> 32) & 0xFF);
        frame[6] = (unsigned char)((length >> 24) & 0xFF);
        frame[7] = (unsigned char)((length >> 16) & 0xFF);
        frame[8] = (unsigned char)((length >> 8) & 0xFF);
        frame[9] = (unsigned char)(length & 0xFF);
        idx_first_data = 10;
    }
    else
    {
        frame[1] = 126;
        frame[2] = (length >> 8) & 0xFF;
        frame[3] = length & 0xFF;
        idx_first_data = 4;
    }

    *frame_data = (char *)frame;
    return (size_t)idx_first_data;
}

@etr

etr commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Thanks for this, @gre-42 — the outcome this PR was after (WebSocket support, per #20) shipped in libhttpserver 2.0.0. It's a from-scratch v2 implementation rather than this branch, so I'm noting it here in case it's useful to you.

The public surface:

  • httpserver::websocket_handler — subclass it and override the lifecycle callbacks: on_open, on_message (pure virtual), on_binary, on_ping, on_close.
  • httpserver::websocket_session — the per-connection handle passed to those callbacks, with send_text / send_binary / send_ping / send_pong.
  • webserver::register_ws_resource(path, handler) — register a handler at a URL (unique_ptr and shared_ptr overloads).

A ready-to-run echo example lives at examples/websocket_echo.cpp:

class echo_handler : public httpserver::websocket_handler {
    void on_open(httpserver::websocket_session& session) override {
        session.send_text("Welcome to the echo server!");
    }
    void on_message(httpserver::websocket_session& session, std::string_view msg) override {
        session.send_text(/* echo */ std::string(msg));
    }
    void on_close(httpserver::websocket_session& session, uint16_t code, const std::string& reason) override { /* ... */ }
};
// ws.register_ws_resource("/echo", std::make_unique<echo_handler>());

Full docs are in the README's WebSocket support section.

A couple of notes that connect back to your PR:

  • The design intentionally avoids extending http_resource via multiple inheritance (the alternative you weighed) — WebSocket handling is a separate websocket_handler type with its own session object.
  • Your noted drawback still holds: it's gated on HAVE_WEBSOCKET, which requires libmicrohttpd built with microhttpd_ws (the example links -lmicrohttpd_ws). On a build without it, the send-side methods throw feature_unavailable.

So the goal here is achieved in 2.0 — appreciate you pushing this forward back in 2021.

@etr

etr commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Closing as superseded — WebSocket support shipped in 2.0.0 (see the previous comment for the API and example). Thanks again, @gre-42, for driving this.

@etr etr closed this Jul 23, 2026
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.

5 participants