public
Last active

HTTP chunked transfer in a libevent server

  • Download Gist
chunk.c
C
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 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
/*
This shows how to use HTTP chunked transfer in libevent. It starts an HTTP server on port 8080; when the client connects, it outputs an message every second. After all messages are outputted, the server closes the HTTP connection.
 
It has an unsolved crashing problem though... to see it crash, run the next commands in a shell.
 
See my ML post "after the http client abruptly closes socket libevent (2.0.3-alpha) http server crashes" at http://archives.seul.org/libevent/users/Jan-2010/msg00054.html
 
Compile with:
 
gcc -Wall -g -std=c99 -D_GNU_SOURCE -Iinclude -Llib -levent -o chunk chunk.c
 
NB: This assumes you have installed libevent 2.0.3-alpha into the current working directory.
 
 
Run it through valgrind (you can just use gdb or run it directly):
 
LD_LIBRARY_PATH=lib valgrind -v --leak-check=full ./chunk
 
 
In a second shell, access it:
 
printf 'GET / HTTP/1.0\n\n' | nc localhost 8080
 
Press Ctrl+C after a couple of seconds.
 
The HTTP server will crash and burn inside the http_chunked_trickle_cb function:
 
 
Program received signal SIGSEGV, Segmentation fault.
0x005fa0e3 in evhttp_send_reply_chunk (req=0x804b930, databuf=0x804bab0) at http.c:2082
2082 struct evbuffer *output = bufferevent_get_output(req->evcon->bufev);
(gdb) bt
#0 0x005fa0e3 in evhttp_send_reply_chunk (req=0x804b930, databuf=0x804bab0) at http.c:2082
#1 0x08048b18 in http_chunked_trickle_cb (fd=-1, events=1, arg=0x804bb68) at chunk.c:47
#2 0x005e8f58 in event_process_active_single_queue (base=0x804b020, flags=<value optimized out>) at event.c:925
#3 event_process_active (base=0x804b020, flags=<value optimized out>) at event.c:978
#4 event_base_loop (base=0x804b020, flags=<value optimized out>) at event.c:1156
#5 0x005e9b95 in event_base_dispatch (event_base=0x804b020) at event.c:1005
#6 0x08048dd6 in main () at chunk.c:132
(gdb)
 
I'm guessing that the evhttp_request req object is freed before http_chunked_trickle_cb is called. At first I though on using evhttp_request_own(req) would do the trick, but it didn't... So, what you think is going on and how to fix it?
 
Oh, this code still needs pervasive error checking...
*/
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/http.h>
 
static char const* const CHUNKS[] = {
"Preparing...\n",
"T-10\n",
"T-9\n",
"T-8\n",
"T-7\n",
"T-6\n",
"T-5\n",
"T-4\n",
"T-3\n",
"T-2\n",
"T-1\n",
"Liftoff!\n"
};
 
struct chunk_req_state {
struct event_base *base;
struct evhttp_request *req;
struct event *timer;
int i;
};
 
static void
schedule_trickle(struct chunk_req_state *state, int ms)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = ms * 1000;
// XXX TODO why no base argument in evtimer_add? well, because it was already given in evtimer_new!
evtimer_add(state->timer, &tv);
}
 
static void
http_chunked_trickle_cb(evutil_socket_t fd, short events, void *arg)
{
struct chunk_req_state *state = arg;
struct evbuffer *evb = evbuffer_new();
 
evbuffer_add_printf(evb, "%s", CHUNKS[state->i]);
evhttp_send_reply_chunk(state->req, evb);
evbuffer_free(evb);
 
if (++state->i < sizeof(CHUNKS)/sizeof(CHUNKS[0])) {
schedule_trickle(state, 1000);
} else {
evhttp_send_reply_end(state->req);
// XXX TODO why no evtimer_free?
event_free(state->timer);
free(state);
}
}
 
static void
http_chunked_cb(struct evhttp_request *req, void *arg)
{
struct chunk_req_state *state = malloc(sizeof(struct chunk_req_state));
 
memset(state, 0, sizeof(struct chunk_req_state));
state->req = req;
state->base = arg;
state->timer = evtimer_new(state->base, http_chunked_trickle_cb, state);
 
evhttp_send_reply_start(req, HTTP_OK, "OK");
 
schedule_trickle(state, 0);
}
 
static void
http_quit_cb(struct evhttp_request *req, void *arg)
{
struct event_base *base = arg;
 
struct evbuffer *body = evbuffer_new();
evbuffer_add_printf(body, "Bye bye\n");
evhttp_send_reply(req, HTTP_OK, "OK", body);
evbuffer_free(body);
 
struct timeval delay = { 2, 0 };
//event_base_loopexit(req->evcon->base, &delay);
event_base_loopexit(base, &delay);
}
 
static struct evhttp *
http_setup(const char *address, short port, struct event_base *base)
{
struct evhttp *http = evhttp_new(base);
 
evhttp_bind_socket(http, address, port);
 
evhttp_set_cb(http, "/", http_chunked_cb, base);
evhttp_set_cb(http, "/quit", http_quit_cb, base);
 
return http;
}
 
// TODO XXX if you press Ctrl+C multiple times, this is called, and at the end,
// valgrind will repost leaks...
static void
sigint_cb(evutil_socket_t sig, short events, void *ptr)
{
struct event_base *base = ptr;
struct timeval delay = { 2, 0 };
 
printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");
 
event_base_loopexit(base, &delay);
}
 
static void
ignore_sigpipe(void)
{
// ignore SIGPIPE (or else it will bring our program down if the client
// closes its socket).
// NB: if running under gdb, you might need to issue this gdb command:
// handle SIGPIPE nostop noprint pass
// because, by default, gdb will stop our program execution (which we
// might not want).
struct sigaction sa;
 
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_IGN;
 
if (sigemptyset(&sa.sa_mask) < 0 || sigaction(SIGPIPE, &sa, 0) < 0) {
perror("Could not ignore the SIGPIPE signal");
exit(EXIT_FAILURE);
}
}
 
int
main(void)
{
ignore_sigpipe();
 
struct event_base *base = event_base_new();
 
struct event *sigint_event = evsignal_new(base, SIGINT, sigint_cb, base);
if (!sigint_event || event_add(sigint_event, NULL) < 0) {
fprintf(stderr, "Could not create or add the SIGINT signal event.\n");
return -1;
}
 
struct evhttp *http = http_setup("127.0.0.1", 8080, base);
 
event_base_dispatch(base);
 
event_free(sigint_event);
evhttp_free(http);
event_base_free(base);
 
return 0;
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.