Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP streaming #8

Open
jvo203 opened this issue Mar 30, 2023 · 6 comments
Open

HTTP streaming #8

jvo203 opened this issue Mar 30, 2023 · 6 comments
Labels
enhancement New feature or request

Comments

@jvo203
Copy link

jvo203 commented Mar 30, 2023

Another essential feature: does zap as it stands today support HTTP streaming of chunked responses, Transfer-Encoding: chunked?

Today I've been kicking the tyres of facil.io in C. It's not bad, the documentation might have been better, but it is not clear at all how to stream HTTP responses in facil (and therefore zap too). Say your code produces a large amount of output data at a "steady" rate that you want to stream via HTTP in response to an HTTP request.

Right now, with libmicrohttpd this is trivial. You open a Unix pipe, pass the write end (the producer) to a separate POSIX thread, and pass the consumer (the read end of a pipe) to an HTTP response handler that accepts a Unix pipe file descriptor (struct MHD_Response * MHD_create_response_from_pipe (int fd)).

i.e.

        // create a response from a pipe by passing the read end of the pipe
        struct MHD_Response *response = MHD_create_response_from_pipe(pipefd[0]);

        // add headers
        MHD_add_response_header(response, "Cache-Control", "no-cache");
        MHD_add_response_header(response, "Cache-Control", "no-store");
        MHD_add_response_header(response, "Pragma", "no-cache");
        MHD_add_response_header(response, "Content-Type", "application/octet-stream");

        // queue the response
        enum MHD_Result ret = MHD_queue_response(connection, MHD_HTTP_OK, response);

        MHD_destroy_response(response);

        // the code below should be run in a separate thread
        // otherwise libmicrohttpd will not have a chance to read from the pipe

        // pass the write end of the pipe to Fortran
        // the binary response data will be generated in Fortran
        printf("[C] calling video_request with the pipe file descriptor %d\n", pipefd[1]);

        struct video_req *req = malloc(sizeof(struct video_req));

        if (req != NULL)
        {
            req->keyframe = keyframe;
            req->frame = frame;
            req->flux = strdup(flux);
            req->len = strlen(req->flux);

            req->dmin = dmin;
            req->dmax = dmax;
            req->dmedian = dmedian;

            req->sensitivity = sensitivity;
            req->slope = slope;
            req->white = white;
            req->black = black;

            req->width = width;
            req->height = height;
            req->downsize = downsize;

            req->fd = pipefd[1];
            req->ptr = item;

            // create and detach the thread
            int stat = pthread_create(&tid, NULL, &video_request, req);

            if (stat == 0)
                pthread_detach(tid);
            else
            {
                close(pipefd[1]);
                free(req);
            }
        }
        else
            close(pipefd[1]);

        return ret;
@jvo203
Copy link
Author

jvo203 commented Mar 31, 2023

It seems the existing stable facil.io-0.7 contains an internal function

http_internal.h:

/** Should send existing headers and data and prepare for streaming */
  int (*const http_stream)(http_s *h, void *data, uintptr_t length);

but it does not seem to be used anywhere throughout the code (tried grep *.h and *.c for http_stream). Certainly it is not exposed to the end-user.

The other function below seems to be used in the code:

 /** Should send existing headers or complete streaming */
  void (*const http_finish)(http_s *h);

Perhaps it is time to check out the facil.io 0.8.X development branch but it does not seem to be actively developed right now.

@renerocksai renerocksai added the enhancement New feature or request label Apr 3, 2023
@renerocksai
Copy link
Member

Sorry for the late reply. Apparently the author of facil.io is working on a new version here which will support streaming w/o blocking a worker thread.

Once this is ready, I consider re-basing zap on top of the new version. This might take a while though, tbh.

@jvo203
Copy link
Author

jvo203 commented Aug 22, 2023

Thanks for the info. I thought facil.io had been pretty much "dead", that the new 0.8.X development was stalled. Apparently it is still alive.

@renerocksai
Copy link
Member

One more thing: You can consider websockets or even server-side messages as an alternative of streaming. Both are supported by facil.io, and at least web-sockets are wrapped by zap. SSM should be easy enough to wrap, too...

Just some thoughts 😄

@jvo203
Copy link
Author

jvo203 commented Dec 19, 2023

Yes it's a good thought. Though the WebSockets are already used for streaming real-time binary messages during a normal operation of the web site. The streaming HTTP is used during the initial phase of the web site loading in order to stream real-time compressed output from the gzip (stream compressed chemical molecules whilst the data is being retrieved from the server database), all done prior to opening a WebSocket connection.

There is quite a lot going on "in the background" upon opening a web site. Streaming left and right, establishing the main WebSocket connection - reserved for non-HTTP streaming - etc.

In addition, and this is the main reason for not using WebSockets in this case: the Server-Side Events or WebSocket responses cannot be cached. HTTP streams are treated like normal HTTP responses by caching proxies and web browsers. Therefore I take full advantage of HTTP response caching to avoid HTTP streaming of responses that have already been delivered to the browser before.

@jvo203
Copy link
Author

jvo203 commented Dec 19, 2023

Thanks but, Server-Side Events (SSE) and WebSocket messages do not support caching by browsers and proxies. HTTP responses are cached, and my application relies on caching. HTTP streams (chunked transfer encoding) are treated like normal HTTP responses and are properly cached.

SSE and WebSockets are not cached (nor should they be). Therefore there is a clear requirement for both HTTP streaming as well as WebSockets. A division of labour, so to speak.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants