This repository has been archived by the owner on Jun 30, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 164
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extensive example of streaming data without hogging memory
This is an example as a reply to #47 examples/example_chunked.c will open up a file (of any size), and stream data back to the client in 128 byte chunked segments. This is the proper way of sending large amounts of data back to a client without consuming much memory.
- Loading branch information
1 parent
f7b6ca7
commit 8f50b83
Showing
4 changed files
with
184 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#pragma once | ||
|
||
static void * mm__dup_(const void * src, size_t size) { | ||
void * mem = malloc(size); | ||
|
||
return mem ? memcpy(mem, src, size) : NULL; | ||
} | ||
|
||
#define mm__alloc_(type, ...) \ | ||
(type *)mm__dup_((type[]) {__VA_ARGS__ }, sizeof(type)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <stdint.h> | ||
#include <errno.h> | ||
|
||
#include "../log.h" | ||
#include "./eutils.h" | ||
#include "internal.h" | ||
#include "evhtp/evhtp.h" | ||
|
||
struct reply_ { | ||
evhtp_request_t * request; | ||
FILE * file_desc; | ||
struct evbuffer * buffer; | ||
}; | ||
|
||
|
||
/* This function is called each time the client has been sent | ||
* all outstanding data. We use this to send the next part of | ||
* the file in a chunk at 128 byte increments. | ||
* | ||
* When there is no more data to be read from the file, this | ||
* will send the final chunked reply and free our struct reply_. | ||
*/ | ||
static evhtp_res | ||
http__send_chunk_(evhtp_connection_t * conn, void * arg) { | ||
struct reply_ * reply = (struct reply_ *)arg; | ||
char buf[128]; | ||
size_t bytes_read; | ||
|
||
/* try to read 128 bytes from the file pointer */ | ||
bytes_read = fread(buf, 1, sizeof(buf), reply->file_desc); | ||
|
||
log_info("Sending %zu bytes", bytes_read); | ||
|
||
if (bytes_read > 0) { | ||
/* add our data we read from the file into our reply buffer */ | ||
evbuffer_add(reply->buffer, buf, bytes_read); | ||
|
||
/* send the reply buffer as a http chunked message */ | ||
evhtp_send_reply_chunk(reply->request, reply->buffer); | ||
|
||
/* we can now drain our reply buffer as to not be a resource | ||
* hog. | ||
*/ | ||
evbuffer_drain(reply->buffer, bytes_read); | ||
} | ||
|
||
/* check if we have read everything from the file */ | ||
if (feof(reply->file_desc)) { | ||
log_info("Sending last chunk"); | ||
|
||
/* now that we have read everything from the file, we must | ||
* first unset our on_write hook, then inform evhtp to send | ||
* this message as the final chunk. | ||
*/ | ||
evhtp_connection_unset_hook(conn, evhtp_hook_on_write); | ||
evhtp_send_reply_chunk_end(reply->request); | ||
|
||
/* we can now free up our little reply_ structure */ | ||
{ | ||
fclose(reply->file_desc); | ||
evbuffer_free(reply->buffer); | ||
free(reply); | ||
} | ||
} | ||
|
||
return EVHTP_RES_OK; | ||
} | ||
|
||
/* This function is called when a request has been fully received. | ||
* | ||
* This function assumes the `arg` value is the filename that was | ||
* passed via `evhtp_set_gencb` in `main`. | ||
* | ||
* 1. open the file | ||
* 2. create a `struct reply_` | ||
* 3. create an evbuffer that we will write into. | ||
* 4. set a hook to call the function `http__send_chunk_` each | ||
* time all data has been sent from the previous write call. | ||
* 5. start the chunked stream via `evhtp_send_reply_chunk_start` | ||
*/ | ||
static void | ||
http__callback_(evhtp_request_t * req, void * arg) { | ||
const char * filename = arg; | ||
FILE * file_desc; | ||
struct reply_ * reply; | ||
|
||
evhtp_assert(arg != NULL); | ||
|
||
/* open up the file as passed to us via evhtp_set_gencb */ | ||
file_desc = fopen((const char *)arg, "r"); | ||
evhtp_assert(file_desc != NULL); | ||
|
||
/* create our little internal reply structure which will | ||
* be used by `http__send_chunk_` | ||
*/ | ||
reply = mm__alloc_(struct reply_, { | ||
req, | ||
file_desc, | ||
evbuffer_new() | ||
}); | ||
|
||
/* here we set a connection hook of the type `evhtp_hook_on_write` | ||
* | ||
* this will execute the function `http__send_chunk_` each time | ||
* all data has been written to the client. | ||
*/ | ||
evhtp_connection_set_hook(req->conn, | ||
evhtp_hook_on_write, | ||
http__send_chunk_, reply); | ||
|
||
/* we do not have to start sending data from the file from here - | ||
* this function will write data to the client, thus when finished, | ||
* will call our `http__send_chunk_` callback. | ||
*/ | ||
evhtp_send_reply_chunk_start(req, EVHTP_RES_OK); | ||
} | ||
|
||
int | ||
main(int argc, char ** argv) { | ||
evhtp_t * htp; | ||
struct event_base * evbase; | ||
|
||
if (argc < 2) { | ||
printf("Usage: %s <file>\n", argv[0]); | ||
exit(EXIT_FAILURE); | ||
} | ||
|
||
evbase = event_base_new(); | ||
evhtp_alloc_assert(evbase); | ||
|
||
htp = evhtp_new(evbase, NULL); | ||
evhtp_alloc_assert(htp); | ||
|
||
/* here we set our default request response callback, the argument | ||
* that is passed will be the filename we want to stream to the | ||
* client in chunked form. | ||
*/ | ||
evhtp_set_gencb(htp, http__callback_, strdup(argv[1])); | ||
|
||
evhtp_bind_socket(htp, "127.0.0.1", 0, 128); | ||
{ | ||
struct sockaddr_in sin; | ||
socklen_t len = sizeof(struct sockaddr); | ||
uint16_t port; | ||
|
||
getsockname( | ||
evconnlistener_get_fd(htp->server), | ||
(struct sockaddr *)&sin, &len); | ||
|
||
port = ntohs(sin.sin_port); | ||
|
||
log_info("curl http://127.0.0.1:%d/", port); | ||
} | ||
|
||
event_base_loop(evbase, 0); | ||
|
||
|
||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters