1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.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);
evhtp_safe_free(reply->buffer, evbuffer_free);
evhtp_safe_free(reply, free);
}
}
return EVHTP_RES_OK;
}
static evhtp_res
http__conn_fini_(struct evhtp_connection * c, void * arg)
{
log_info("hi");
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(filename, "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);
/* set a hook to be called when the client disconnects */
evhtp_connection_set_hook(req->conn,
evhtp_hook_on_connection_fini,
http__conn_fini_, NULL);
/* 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]));
log_info("curl http://127.0.0.1:%d/", bind__sock_port0_(htp));
event_base_loop(evbase, 0);
return 0;
}
|