fhessel / esp32_https_server

Alternative ESP32 Webserver implementation for the ESP32 Arduino Core, supporting HTTPS and HTTP.
MIT License
342 stars 125 forks source link

Web server requests timeout while streaming MJPEG via a resource handler #139

Open marciot opened 3 years ago

marciot commented 3 years ago

Describe the bug

Maybe not a bug, more of a limitation of the current implementation and a feature request for a way around this problem.

I have created a handler for streaming MJPEG video via a multipart form. This handler serves data continuously until the client closes the connection. I've noticed that while a stream is active, all other connections to the web server timeout.

At first, I was confused by this, because ESP32 is supposed to allow multiple connections, but it appears as if this means is that while it can allow multiple sequential requests over an open connection, only one request handler can run at a time.

My question is whether it would be possible to have a special handler for HTTP streams. Since esp32_https_server allows websockets, which also run continuously, I assume it is theoretically possible, but my guess is that it would need to be a special type of connection object for HTTP streams, just like WebsocketHandler. The difference would be where instead of having a single handler function that is called once, there are multiple functions, one for setting up the connection, one for emitting each "frame" in the stream, and a last for cleaning up.

How To Reproduce

Create a handler that streams video as a multipart form:

void handleCameraStream(HTTPRequest * req, HTTPResponse * res) {
        #define PART_BOUNDARY "123456789000000000000987654321"
        constexpr char* STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
        constexpr char* STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
        constexpr char* STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";

        camera_fb_t * fb = NULL;
        char part_buf[64];

        res->setHeader("Content-Type", STREAM_CONTENT_TYPE);
        res->setHeader("Access-Control-Allow-Origin","*");

        while(1){
            fb = esp_camera_fb_get();
            if (!fb) {
                res->setStatusText("Camera capture failed");
                res->setStatusCode(400);
                return;
            }
            size_t hlen = snprintf(part_buf, 64, STREAM_PART, fb->len);
            if(res->write((unsigned char*)part_buf, hlen) != hlen) break;
            if(res->write((unsigned char*)fb->buf, fb->len) != fb->len) break;
            if(res->write((unsigned char*)STREAM_BOUNDARY, strlen(STREAM_BOUNDARY)) != strlen(STREAM_BOUNDARY)) break;
            esp_camera_fb_return(fb);
        }
        esp_camera_fb_return(fb);
}

...

webServer.registerNode(  new ResourceNode("/capture.mjpg", "GET", &handleCameraStream));

Expected Behavior

The user should be able to connect to "http//esp32_server/capture.mjpg", leave that window open, and still be able to access other resources on the web server.

Actual Behavior

Once the user makes a connection to "http//esp32_server/capture.mjpg", all accesses to the server time out until that window is closed.

ESP32 Module Please provide specifications of your module

Software (please complete the following information if applicable)

Additional context Add any other context about the problem here.

squonk11 commented 3 years ago

Hello, I think this is an inherent "problem" of this webserver implementation - it runs all requests in one loop. If one of the requests does not end, all other requests are blocked and time out. Although this implementation is a very nice and fast webserver it does not really work "asynchronous". But for Arduino on ESP32 I currently do not know a better one. If you can/want to use ESP-IDF directly you could check if this is something which would fit your needs. It works totally asynchronous, is (high-level) cpp, non-Arduino and quite stable. Unfortunately for some reason this solution works a little bit slow...