Copyright © https://mongoose-os.com

Mongoose OS Forum

frame

Mongoose Webserver Event Handling

I am running into an issue that I am having where the mongoose web server is delivering events to unexpected callback functions.

I have a endpoint registered with mg_register_http_endpoint that is in charge of sending random data to the client as a file. On MG_EV_HTTP_REQUEST (endpoint handler is invoked as expected), I generate a HTTP header and send to the client with a file size to be downloaded. The idea is that data is then sent to the client in 512 byte chunks until the desired file size is reached.

I was then planning on using the MG_EV_SEND event to know when the previous data had been sent so I can generate the next random 512 byte chunk to send to the client. However, the MG_EV_SEND event is delivered to the event handler that I provided in the mg_bind API call, and not the one provided in mg_http_register_endpoint(). Is this the correct behavior? If so, is there any disadvantage to overwriting the nc->handler function pointer to my endpoint function inside of the MG_EV_HTTP_REQUEST? That way all events on the connection will be delivered to my endpoint handler, rather than the one in mg_bind. From looking at the code, it seems like this is done for websockets.

Thanks,
Randy

Comments

  • SergeySergey Dublin, Ireland

    could you export a gist with the minimal example code please?

  • I modified the big_upload.c example with a minimal test case that demonstrates the issue that I am seeing. Please see the code in the code block below. When I run the example code, I navigate to http://127.0.0.1:8000/download and I download the random file (I just send an uninitialized buffer for simplicity).

    Based on the docs, what I would expect is that my download endpoint handler to be called when the request comes in (which it does). I respond to the request with a http header and send the header returning from the endpoint function (handle_download). Now, I would expect to get a MG_EV_SEND event when the header is sent, which does occur, but the MG_EV_SEND event is delivered to ev_handler and not handle_download. This makes this use case difficult, because with many endpoint handlers, how would one know in ev_handler what to cast the nc->user_data to if endpoints allocate different structures in the initial request? I was expecting all events associated with the /download connection to be sent to the handle_download function.

    If I enable the #if'ed out section in the example below (override nc->handler to handle_download), the example works in a way such that I would expect (the MG_EV_SEND then is delivered to handle_download). Is this solution acceptable?

    Am I misunderstanding the docs? If so, is there a better way to accomplish what I am trying to do?

    Thanks,
    Randy

    // Copyright (c) 2015 Cesanta Software Limited
    // All rights reserved
    //
    // This example demonstrates how to handle very large requests without keeping
    // them in memory.
    
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include "mongoose.h"
    
    static const char *s_http_port = "8000";
    static struct mg_serve_http_opts s_http_server_opts;
    
    struct file_writer_data {
        size_t bytes_written;
        size_t total_bytes_to_send;
    };
    
    static char send_buf[256];
    
    static void handle_download(struct mg_connection *nc, int ev, void *p) {
        struct file_writer_data *data = (struct file_writer_data *) nc->user_data;
        (void)p;
    
        switch (ev) {
        case MG_EV_HTTP_REQUEST: {
            data = malloc(sizeof(struct file_writer_data));
            data->bytes_written = 0;
            data->total_bytes_to_send = 8192;
    
            printf("%s: sending http header\n", __func__);
            mg_printf(nc,
                      "HTTP/1.1 200 OK\r\n"
                      "Content-Length: %ld\r\n"
                      "Content-Disposition: attachment; filename=\"random.bin\"\r\n\r\n",
                      data->total_bytes_to_send);
    
            nc->user_data = data;
    #if 0
            nc->handler = handle_download;
    #endif
    
            break;
        }
        case MG_EV_SEND: {
            printf("%s: MG_EV_SEND\n", __func__);
            mg_send(nc, send_buf, sizeof(send_buf));
            data->bytes_written += sizeof(send_buf);
            if (data->bytes_written == data->total_bytes_to_send)
            {
                nc->flags |= MG_F_SEND_AND_CLOSE;
            }
            break;
        }
    
        case MG_EV_CLOSE: {
            free(data);
            nc->user_data = NULL;
            break;
        }
        }
    
    }
    
    static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
        if (ev == MG_EV_HTTP_REQUEST) {
            mg_serve_http(nc, ev_data, s_http_server_opts);
        }
        else if (ev == MG_EV_SEND)
        {
            struct file_writer_data *data = (struct file_writer_data *) nc->user_data;
            printf("%s: MG_EV_SEND\n", __func__);
            mg_send(nc, send_buf, sizeof(send_buf));
            data->bytes_written += sizeof(send_buf);
            if (data->bytes_written == data->total_bytes_to_send)
            {
                nc->flags |= MG_F_SEND_AND_CLOSE;
            }
        }
    }
    
    int main(void) {
        struct mg_mgr mgr;
        struct mg_connection *c;
    
        mg_mgr_init(&mgr, NULL);
        c = mg_bind(&mgr, s_http_port, ev_handler);
        if (c == NULL) {
            fprintf(stderr, "Cannot start server on port %s\n", s_http_port);
            exit(EXIT_FAILURE);
        }
    
        s_http_server_opts.document_root = "."; // Serve current directory
        mg_register_http_endpoint(c, "/download", handle_download MG_UD_ARG(NULL));
    
        // Set up HTTP server parameters
        mg_set_protocol_http_websocket(c);
    
        printf("Starting web server on port %s\n", s_http_port);
        for (;;) {
            mg_mgr_poll(&mgr, 1000);
        }
        mg_mgr_free(&mgr);
    
        return 0;
    }
    
    
  • SergeySergey Dublin, Ireland

    don't register endpoints and thus avoid the confusion.
    Do serving with a single event handler.

  • I fail to see how placing everything in a single event handler solves my issue. If I have, say 3 events that on EV_HTTP_REQUEST all allocate a different structure and place in nc->user_data, how would I know on MG_EV_SEND which structure I have? Unless, of course, I set some flag/variable inside the user_data that I allocate.

    I attempted to override the nc->handler in my application and everything seems to work as I would expect. Can you comment on any issues with this approach?

  • SergeySergey Dublin, Ireland
    edited December 4

    It is quite hard to follow on what you're saying.
    So, if whatever approach works for you, that's fine, go with it. I am not going to convince to follow my suggestion.

Sign In or Register to comment.