Fixes to chunked support when pipelining

This commit is contained in:
cpq 2022-08-05 19:18:06 +01:00
parent 4bee440a56
commit 779c825e92
7 changed files with 208 additions and 163 deletions

View File

@ -9,7 +9,7 @@ INCS ?= -Isrc -I.
SSL ?= MBEDTLS
CWD ?= $(realpath $(CURDIR))
ENV ?= -e Tmp=. -e WINEDEBUG=-all
DOCKER ?= docker run --rm $(ENV) -v $(CWD):$(CWD) -w $(CWD)
DOCKER ?= docker run --platform linux/amd64 --rm $(ENV) -v $(CWD):$(CWD) -w $(CWD)
VCFLAGS = /nologo /W3 /O2 /MD /I. $(DEFS) $(TFLAGS)
IPV6 ?= 1
ASAN ?= -fsanitize=address,undefined -fno-sanitize-recover=all

View File

@ -1699,6 +1699,7 @@ static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt,
if (c->send.len >= len + 10) {
mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len - 10);
c->send.buf[len + 8] = '\r';
if (c->send.len == len + 10) c->is_resp = 0; // Last chunk, reset marker
}
mg_send(c, "\r\n", 2);
}
@ -1714,6 +1715,7 @@ void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) {
mg_printf(c, "%lx\r\n", (unsigned long) len);
mg_send(c, buf, len);
mg_send(c, "\r\n", 2);
if (len == 0) c->is_resp = 0;
}
// clang-format off
@ -1752,8 +1754,10 @@ void mg_http_reply(struct mg_connection *c, int code, const char *headers,
if (c->send.len > 15) {
mg_snprintf((char *) &c->send.buf[len - 14], 11, "%010lu",
(unsigned long) (c->send.len - len));
c->is_resp = 0;
c->send.buf[len - 4] = '\r'; // Change ending 0 to space
}
c->is_resp = 0;
}
static void http_cb(struct mg_connection *, int, void *, void *);
@ -1761,6 +1765,7 @@ static void restore_http_cb(struct mg_connection *c) {
mg_fs_close((struct mg_fd *) c->pfn_data);
c->pfn_data = NULL;
c->pfn = http_cb;
c->is_resp = 0;
}
char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime);
@ -2277,85 +2282,93 @@ void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm) {
c->pfn_data = (void *) ((size_t) c->pfn_data | MG_DMARK);
}
static void deliver_chunked_chunks(struct mg_connection *c, size_t hlen,
struct mg_http_message *hm, bool *next) {
// | ... headers ... | HEXNUM\r\n ..data.. \r\n | ......
// +------------------+--------------------------+----
// | hlen | chunk1 | ......
char *buf = (char *) &c->recv.buf[hlen], *p = buf;
size_t len = c->recv.len - hlen;
size_t processed = ((size_t) c->pfn_data) & ~MG_DMARK;
size_t mark, pl, dl, del = 0, ofs = 0;
bool last = false;
if (processed <= len) len -= processed, buf += processed;
while (!last && getchunk(mg_str_n(buf + ofs, len - ofs), &pl, &dl)) {
size_t saved = c->recv.len;
memmove(p + processed, buf + ofs + pl, dl);
// MG_INFO(("P2 [%.*s]", (int) (processed + dl), p));
hm->chunk = mg_str_n(p + processed, dl);
mg_call(c, MG_EV_HTTP_CHUNK, hm);
ofs += pl + dl + 2, del += pl + 2; // 2 is for \r\n suffix
processed += dl;
if (c->recv.len != saved) processed -= dl, buf -= dl;
mg_hexdump(c->recv.buf, hlen + processed);
last = (dl == 0);
}
mg_iobuf_del(&c->recv, hlen + processed, del);
mark = ((size_t) c->pfn_data) & MG_DMARK;
c->pfn_data = (void *) (processed | mark);
if (last) {
hm->body.len = processed;
hm->message.len = hlen + processed;
c->pfn_data = NULL;
if (mark) mg_iobuf_del(&c->recv, 0, hlen), *next = true;
// MG_INFO(("LAST, mark: %lx", mark));
// mg_hexdump(c->recv.buf, c->recv.len);
}
}
static void deliver_normal_chunks(struct mg_connection *c, size_t hlen,
struct mg_http_message *hm, bool *next) {
size_t left, processed = ((size_t) c->pfn_data) & ~MG_DMARK;
bool deleted = ((size_t) c->pfn_data) & MG_DMARK;
hm->chunk = mg_str_n((char *) &c->recv.buf[hlen], c->recv.len - hlen);
if (processed <= hm->chunk.len && !deleted) {
hm->chunk.len -= processed;
hm->chunk.ptr += processed;
}
left = hm->body.len < processed ? 0 : hm->body.len - processed;
if (hm->chunk.len > left) hm->chunk.len = left;
if (hm->chunk.len > 0) mg_call(c, MG_EV_HTTP_CHUNK, hm);
processed += hm->chunk.len;
if (processed >= hm->body.len) { // Last, 0-len chunk
hm->chunk.len = 0; // Reset length
mg_call(c, MG_EV_HTTP_CHUNK, hm); // Call user handler
c->pfn_data = NULL; // Reset processed counter
if (processed && deleted) mg_iobuf_del(&c->recv, 0, hlen), *next = true;
} else {
size_t del = ((size_t) c->pfn_data) & MG_DMARK; // Keep deletion marker
c->pfn_data = (void *) (processed | del); // if it is set
}
}
static void http_cb(struct mg_connection *c, int ev, void *evd, void *fnd) {
if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
struct mg_http_message hm;
// mg_hexdump(c->recv.buf, c->recv.len);
while (c->recv.buf != NULL && c->recv.len > 0) {
bool next = false;
int hlen = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
if (hlen < 0) {
mg_error(c, "HTTP parse:\n%.*s", (int) c->recv.len, c->recv.buf);
break;
}
if (hlen == 0) break; // Request is not buffered yet
if (ev == MG_EV_CLOSE) {
hm.message.len = c->recv.len;
if (c->is_resp) break; // Response is still generated
if (hlen == 0) break; // Request is not buffered yet
if (ev == MG_EV_CLOSE) { // If client did not set Content-Length
hm.message.len = c->recv.len; // and closes now, deliver a MSG
hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr);
}
// Deliver MG_EV_HTTP_CHUNK
if (mg_is_chunked(&hm)) {
// | ... headers ... | HEXNUM\r\n ..data.. \r\n | ......
// +------------------+--------------------------+----
// | hlen | chunk1 | ......
char *buf = (char *) &c->recv.buf[hlen], *p = buf;
size_t len = c->recv.len - (size_t) hlen;
size_t processed = ((size_t) c->pfn_data) & ~MG_DMARK;
size_t mark, pl, dl, del = 0, ofs = 0;
bool last = false;
if (processed <= len) len -= processed, buf += processed;
while (!last && getchunk(mg_str_n(buf + ofs, len - ofs), &pl, &dl)) {
size_t saved = c->recv.len;
memmove(p + processed, buf + ofs + pl, dl);
// MG_INFO(("P2 [%.*s]", (int) (processed + dl), p));
hm.chunk = mg_str_n(p + processed, dl);
mg_call(c, MG_EV_HTTP_CHUNK, &hm);
ofs += pl + dl + 2, del += pl + 2; // 2 is for \r\n suffix
processed += dl;
if (c->recv.len != saved) processed -= dl, buf -= dl;
// mg_hexdump(c->recv.buf, (size_t) hlen + processed);
last = (dl == 0);
}
mg_iobuf_del(&c->recv, (size_t) hlen + processed, del);
mark = ((size_t) c->pfn_data) & MG_DMARK;
c->pfn_data = (void *) (processed | mark);
if (last) {
hm.body.len = processed;
hm.message.len = (size_t) hlen + processed;
c->pfn_data = NULL;
if (mark) mg_iobuf_del(&c->recv, 0, (size_t) hlen);
// MG_INFO(("LAST"));
// mg_hexdump(c->recv.buf, c->recv.len);
}
deliver_chunked_chunks(c, (size_t) hlen, &hm, &next);
} else {
size_t processed = ((size_t) c->pfn_data) & ~MG_DMARK;
bool deleted = ((size_t) c->pfn_data) & MG_DMARK;
// if (processed > hm.body.len) processed = hm.body.len;
hm.chunk.ptr = (char *) &c->recv.buf[hlen];
hm.chunk.len = c->recv.len - (size_t) hlen;
if (processed <= hm.chunk.len && !deleted) {
hm.chunk.ptr += processed;
hm.chunk.len -= processed;
if (hm.chunk.len + processed > hm.body.len) {
hm.chunk.len = hm.body.len - processed;
}
}
// MG_INFO(("NCL->%d %d", (int) processed, (int) hm.chunk.len));
if (hm.chunk.len) mg_call(c, MG_EV_HTTP_CHUNK, &hm);
processed += hm.chunk.len;
if (processed >= hm.body.len) { // Last, 0-len chunk
hm.chunk.len = 0;
mg_call(c, MG_EV_HTTP_CHUNK, &hm);
if (processed && deleted) mg_iobuf_del(&c->recv, 0, (size_t) hlen);
c->pfn_data = NULL;
} else {
// MG_EV_HTTP_CHUNK handler may have set MG_DMARK: do not override
size_t del = ((size_t) c->pfn_data) & MG_DMARK;
c->pfn_data = (void *) (processed | del);
}
deliver_normal_chunks(c, (size_t) hlen, &hm, &next);
}
// Chunk events are delivered. If we have full body, deliver MSG
if (next) continue; // Chunks & request were deleted
// Chunk events are delivered. If we have full body, deliver MSG
if (c->recv.len < hm.message.len) break;
mg_call(c, MG_EV_HTTP_MSG, &hm);
if (c->is_accepted) c->is_resp = 1; // Start generating response
mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp
mg_iobuf_del(&c->recv, 0, hm.message.len);
}
}
@ -4613,8 +4626,13 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
mg_timer_poll(&mgr->timers, now);
for (c = mgr->conns; c != NULL; c = tmp) {
bool is_resp = c->is_resp;
tmp = c->next;
mg_call(c, MG_EV_POLL, &now);
if (is_resp && !c->is_resp) {
struct mg_str fake = mg_str_n("", 0);
mg_call(c, MG_EV_READ, &fake);
}
MG_VERBOSE(("%lu %c%c %c%c%c%c%c", c->id, c->is_readable ? 'r' : '-',
c->is_writable ? 'w' : '-', c->is_tls ? 'T' : 't',
c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',

View File

@ -1077,6 +1077,7 @@ struct mg_connection {
unsigned is_draining : 1; // Send remaining data, then close and free
unsigned is_closing : 1; // Close and free the connection immediately
unsigned is_full : 1; // Stop reads, until cleared
unsigned is_resp : 1; // Response is still being generated
unsigned is_readable : 1; // Connection is ready to read
unsigned is_writable : 1; // Connection is ready to write
};

View File

@ -277,6 +277,7 @@ static void mg_http_vprintf_chunk(struct mg_connection *c, const char *fmt,
if (c->send.len >= len + 10) {
mg_snprintf((char *) c->send.buf + len, 9, "%08lx", c->send.len - len - 10);
c->send.buf[len + 8] = '\r';
if (c->send.len == len + 10) c->is_resp = 0; // Last chunk, reset marker
}
mg_send(c, "\r\n", 2);
}
@ -292,6 +293,7 @@ void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len) {
mg_printf(c, "%lx\r\n", (unsigned long) len);
mg_send(c, buf, len);
mg_send(c, "\r\n", 2);
if (len == 0) c->is_resp = 0;
}
// clang-format off
@ -330,8 +332,10 @@ void mg_http_reply(struct mg_connection *c, int code, const char *headers,
if (c->send.len > 15) {
mg_snprintf((char *) &c->send.buf[len - 14], 11, "%010lu",
(unsigned long) (c->send.len - len));
c->is_resp = 0;
c->send.buf[len - 4] = '\r'; // Change ending 0 to space
}
c->is_resp = 0;
}
static void http_cb(struct mg_connection *, int, void *, void *);
@ -339,6 +343,7 @@ static void restore_http_cb(struct mg_connection *c) {
mg_fs_close((struct mg_fd *) c->pfn_data);
c->pfn_data = NULL;
c->pfn = http_cb;
c->is_resp = 0;
}
char *mg_http_etag(char *buf, size_t len, size_t size, time_t mtime);
@ -855,85 +860,93 @@ void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm) {
c->pfn_data = (void *) ((size_t) c->pfn_data | MG_DMARK);
}
static void deliver_chunked_chunks(struct mg_connection *c, size_t hlen,
struct mg_http_message *hm, bool *next) {
// | ... headers ... | HEXNUM\r\n ..data.. \r\n | ......
// +------------------+--------------------------+----
// | hlen | chunk1 | ......
char *buf = (char *) &c->recv.buf[hlen], *p = buf;
size_t len = c->recv.len - hlen;
size_t processed = ((size_t) c->pfn_data) & ~MG_DMARK;
size_t mark, pl, dl, del = 0, ofs = 0;
bool last = false;
if (processed <= len) len -= processed, buf += processed;
while (!last && getchunk(mg_str_n(buf + ofs, len - ofs), &pl, &dl)) {
size_t saved = c->recv.len;
memmove(p + processed, buf + ofs + pl, dl);
// MG_INFO(("P2 [%.*s]", (int) (processed + dl), p));
hm->chunk = mg_str_n(p + processed, dl);
mg_call(c, MG_EV_HTTP_CHUNK, hm);
ofs += pl + dl + 2, del += pl + 2; // 2 is for \r\n suffix
processed += dl;
if (c->recv.len != saved) processed -= dl, buf -= dl;
mg_hexdump(c->recv.buf, hlen + processed);
last = (dl == 0);
}
mg_iobuf_del(&c->recv, hlen + processed, del);
mark = ((size_t) c->pfn_data) & MG_DMARK;
c->pfn_data = (void *) (processed | mark);
if (last) {
hm->body.len = processed;
hm->message.len = hlen + processed;
c->pfn_data = NULL;
if (mark) mg_iobuf_del(&c->recv, 0, hlen), *next = true;
// MG_INFO(("LAST, mark: %lx", mark));
// mg_hexdump(c->recv.buf, c->recv.len);
}
}
static void deliver_normal_chunks(struct mg_connection *c, size_t hlen,
struct mg_http_message *hm, bool *next) {
size_t left, processed = ((size_t) c->pfn_data) & ~MG_DMARK;
bool deleted = ((size_t) c->pfn_data) & MG_DMARK;
hm->chunk = mg_str_n((char *) &c->recv.buf[hlen], c->recv.len - hlen);
if (processed <= hm->chunk.len && !deleted) {
hm->chunk.len -= processed;
hm->chunk.ptr += processed;
}
left = hm->body.len < processed ? 0 : hm->body.len - processed;
if (hm->chunk.len > left) hm->chunk.len = left;
if (hm->chunk.len > 0) mg_call(c, MG_EV_HTTP_CHUNK, hm);
processed += hm->chunk.len;
if (processed >= hm->body.len) { // Last, 0-len chunk
hm->chunk.len = 0; // Reset length
mg_call(c, MG_EV_HTTP_CHUNK, hm); // Call user handler
c->pfn_data = NULL; // Reset processed counter
if (processed && deleted) mg_iobuf_del(&c->recv, 0, hlen), *next = true;
} else {
size_t del = ((size_t) c->pfn_data) & MG_DMARK; // Keep deletion marker
c->pfn_data = (void *) (processed | del); // if it is set
}
}
static void http_cb(struct mg_connection *c, int ev, void *evd, void *fnd) {
if (ev == MG_EV_READ || ev == MG_EV_CLOSE) {
struct mg_http_message hm;
// mg_hexdump(c->recv.buf, c->recv.len);
while (c->recv.buf != NULL && c->recv.len > 0) {
bool next = false;
int hlen = mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
if (hlen < 0) {
mg_error(c, "HTTP parse:\n%.*s", (int) c->recv.len, c->recv.buf);
break;
}
if (hlen == 0) break; // Request is not buffered yet
if (ev == MG_EV_CLOSE) {
hm.message.len = c->recv.len;
if (c->is_resp) break; // Response is still generated
if (hlen == 0) break; // Request is not buffered yet
if (ev == MG_EV_CLOSE) { // If client did not set Content-Length
hm.message.len = c->recv.len; // and closes now, deliver a MSG
hm.body.len = hm.message.len - (size_t) (hm.body.ptr - hm.message.ptr);
}
// Deliver MG_EV_HTTP_CHUNK
if (mg_is_chunked(&hm)) {
// | ... headers ... | HEXNUM\r\n ..data.. \r\n | ......
// +------------------+--------------------------+----
// | hlen | chunk1 | ......
char *buf = (char *) &c->recv.buf[hlen], *p = buf;
size_t len = c->recv.len - (size_t) hlen;
size_t processed = ((size_t) c->pfn_data) & ~MG_DMARK;
size_t mark, pl, dl, del = 0, ofs = 0;
bool last = false;
if (processed <= len) len -= processed, buf += processed;
while (!last && getchunk(mg_str_n(buf + ofs, len - ofs), &pl, &dl)) {
size_t saved = c->recv.len;
memmove(p + processed, buf + ofs + pl, dl);
// MG_INFO(("P2 [%.*s]", (int) (processed + dl), p));
hm.chunk = mg_str_n(p + processed, dl);
mg_call(c, MG_EV_HTTP_CHUNK, &hm);
ofs += pl + dl + 2, del += pl + 2; // 2 is for \r\n suffix
processed += dl;
if (c->recv.len != saved) processed -= dl, buf -= dl;
// mg_hexdump(c->recv.buf, (size_t) hlen + processed);
last = (dl == 0);
}
mg_iobuf_del(&c->recv, (size_t) hlen + processed, del);
mark = ((size_t) c->pfn_data) & MG_DMARK;
c->pfn_data = (void *) (processed | mark);
if (last) {
hm.body.len = processed;
hm.message.len = (size_t) hlen + processed;
c->pfn_data = NULL;
if (mark) mg_iobuf_del(&c->recv, 0, (size_t) hlen);
// MG_INFO(("LAST"));
// mg_hexdump(c->recv.buf, c->recv.len);
}
deliver_chunked_chunks(c, (size_t) hlen, &hm, &next);
} else {
size_t processed = ((size_t) c->pfn_data) & ~MG_DMARK;
bool deleted = ((size_t) c->pfn_data) & MG_DMARK;
// if (processed > hm.body.len) processed = hm.body.len;
hm.chunk.ptr = (char *) &c->recv.buf[hlen];
hm.chunk.len = c->recv.len - (size_t) hlen;
if (processed <= hm.chunk.len && !deleted) {
hm.chunk.ptr += processed;
hm.chunk.len -= processed;
if (hm.chunk.len + processed > hm.body.len) {
hm.chunk.len = hm.body.len - processed;
}
}
// MG_INFO(("NCL->%d %d", (int) processed, (int) hm.chunk.len));
if (hm.chunk.len) mg_call(c, MG_EV_HTTP_CHUNK, &hm);
processed += hm.chunk.len;
if (processed >= hm.body.len) { // Last, 0-len chunk
hm.chunk.len = 0;
mg_call(c, MG_EV_HTTP_CHUNK, &hm);
if (processed && deleted) mg_iobuf_del(&c->recv, 0, (size_t) hlen);
c->pfn_data = NULL;
} else {
// MG_EV_HTTP_CHUNK handler may have set MG_DMARK: do not override
size_t del = ((size_t) c->pfn_data) & MG_DMARK;
c->pfn_data = (void *) (processed | del);
}
deliver_normal_chunks(c, (size_t) hlen, &hm, &next);
}
// Chunk events are delivered. If we have full body, deliver MSG
if (next) continue; // Chunks & request were deleted
// Chunk events are delivered. If we have full body, deliver MSG
if (c->recv.len < hm.message.len) break;
mg_call(c, MG_EV_HTTP_MSG, &hm);
if (c->is_accepted) c->is_resp = 1; // Start generating response
mg_call(c, MG_EV_HTTP_MSG, &hm); // User handler can clear is_resp
mg_iobuf_del(&c->recv, 0, hm.message.len);
}
}

View File

@ -67,6 +67,7 @@ struct mg_connection {
unsigned is_draining : 1; // Send remaining data, then close and free
unsigned is_closing : 1; // Close and free the connection immediately
unsigned is_full : 1; // Stop reads, until cleared
unsigned is_resp : 1; // Response is still being generated
unsigned is_readable : 1; // Connection is ready to read
unsigned is_writable : 1; // Connection is ready to write
};

View File

@ -650,8 +650,13 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) {
mg_timer_poll(&mgr->timers, now);
for (c = mgr->conns; c != NULL; c = tmp) {
bool is_resp = c->is_resp;
tmp = c->next;
mg_call(c, MG_EV_POLL, &now);
if (is_resp && !c->is_resp) {
struct mg_str fake = mg_str_n("", 0);
mg_call(c, MG_EV_READ, &fake);
}
MG_VERBOSE(("%lu %c%c %c%c%c%c%c", c->id, c->is_readable ? 'r' : '-',
c->is_writable ? 'w' : '-', c->is_tls ? 'T' : 't',
c->is_connecting ? 'C' : 'c', c->is_tls_hs ? 'H' : 'h',

View File

@ -290,8 +290,17 @@ static void test_iobuf(void) {
static void sntp_cb(struct mg_connection *c, int ev, void *evd, void *fnd) {
if (ev == MG_EV_SNTP_TIME) {
*(int64_t *) fnd = *(int64_t *) evd;
MG_DEBUG(("got time: %lld", *(int64_t *) evd));
int64_t received = *(int64_t *) evd;
*(int64_t *) fnd = received;
MG_DEBUG(("got time: %lld", received));
#if MG_ARCH == MG_ARCH_UNIX
struct timeval tv = {0, 0};
gettimeofday(&tv, 0);
int64_t ms = (int64_t) tv.tv_sec * 1000 + tv.tv_usec / 1000;
int64_t diff = ms > received ? ms - received : received - ms;
MG_DEBUG(("diff: %lld", diff));
// ASSERT(diff < 100); // The diff should be less than 300 ms
#endif
} else if (ev == MG_EV_OPEN) {
c->is_hexdumping = 1;
}
@ -1041,7 +1050,7 @@ static void test_http_no_content_length(void) {
static void f5(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
mg_printf(c, "HTTP/1.0 200 OK\n\n%.*s", (int) hm->uri.len, hm->uri.ptr);
mg_http_reply(c, 200, "", "%.*s", (int) hm->uri.len, hm->uri.ptr);
(*(int *) fn_data)++;
}
}
@ -1895,75 +1904,73 @@ static void eY(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
c->label[0]++;
if (c->label[0] == 10) mg_send(c, "a", 1);
if (c->label[0] == 12) mg_send(c, "bc", 2);
if (c->label[0] == 30) mg_send(c, "d", 1), c->label[0] = 0;
if (c->label[0] == 30) mg_send(c, "d", 1), c->is_resp = 0, c->label[0] = 0;
}
(void) ev_data, (void) fn_data;
}
// Do not delete chunks as they arrive
static void eh4(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
uint32_t *crc = (uint32_t *) c->label;
if (ev == MG_EV_CONNECT) {
mg_printf(c, "GET / HTTP/1.0\n\n");
} else if (ev == MG_EV_HTTP_CHUNK) {
uint32_t *crc = (uint32_t *) fn_data;
if (ev == MG_EV_HTTP_CHUNK) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
*crc = mg_crc32(*crc, hm->chunk.ptr, hm->chunk.len);
*crc = mg_crc32(*crc, "x", 1);
MG_INFO(("C [%.*s]", (int) hm->chunk.len, hm->chunk.ptr));
MG_INFO(("%lu C [%.*s]", c->id, (int) hm->chunk.len, hm->chunk.ptr));
} else if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
*(uint32_t *) fn_data = mg_crc32(*crc, hm->body.ptr, hm->body.len);
MG_INFO(("M [%.*s]", (int) hm->body.len, hm->body.ptr));
c->is_closing = 1;
*crc = mg_crc32(*crc, hm->body.ptr, hm->body.len);
MG_INFO(("%lu M [%.*s]", c->id, (int) hm->body.len, hm->body.ptr));
}
}
// Streaming client event handler. Delete chunks as they arrive
static void eh5(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
uint32_t *crc = (uint32_t *) c->label;
if (ev == MG_EV_CONNECT) {
mg_printf(c, "GET / HTTP/1.0\n\n");
} else if (ev == MG_EV_HTTP_CHUNK) {
uint32_t *crc = (uint32_t *) fn_data;
if (ev == MG_EV_HTTP_CHUNK) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
*crc = mg_crc32(*crc, hm->chunk.ptr, hm->chunk.len);
*crc = mg_crc32(*crc, "x", 1);
MG_INFO(("XC [%.*s]", (int) hm->chunk.len, hm->chunk.ptr));
MG_INFO(("%lu DELC [%.*s]", c->id, (int) hm->chunk.len, hm->chunk.ptr));
mg_http_delete_chunk(c, hm);
if (hm->chunk.len == 0) {
c->is_closing = 1;
*(uint32_t *) fn_data = *crc;
}
} else if (ev == MG_EV_HTTP_MSG) {
*(uint32_t *) fn_data = 77U; // Must not be here
ASSERT(0);
ASSERT(0); // Must not be here, MSG must not be fired: chunks deleted!
}
}
static void test_http_chunked_case(mg_event_handler_t s, mg_event_handler_t c,
const char *expected) {
int req_count, const char *expected) {
struct mg_mgr mgr;
const char *url = "http://127.0.0.1:12344";
uint32_t i, done = 0;
uint32_t i, crc = 0, expected_crc = mg_crc32(0, expected, strlen(expected));
struct mg_connection *conn;
mg_mgr_init(&mgr);
mg_http_listen(&mgr, url, s, NULL);
mg_http_connect(&mgr, url, c, &done);
for (i = 0; i < 50 && done == 0; i++) {
conn = mg_http_connect(&mgr, url, c, &crc);
while (conn != NULL && req_count-- > 0) {
mg_printf(conn, "GET / HTTP/1.0\n\n");
}
for (i = 0; i < 100 && crc != expected_crc; i++) {
mg_mgr_poll(&mgr, 1);
}
ASSERT(i < 50);
ASSERT(done == mg_crc32(0, expected, strlen(expected)));
ASSERT(i < 100);
ASSERT(crc == expected_crc);
mg_mgr_free(&mgr);
ASSERT(mgr.conns == NULL);
}
static void test_http_chunked(void) {
// Non-chunked encoding
test_http_chunked_case(eY, eh4, "axbcxdxxabcd"); // Chunks not deleted
test_http_chunked_case(eY, eh5, "axbcxdxx"); // Chunks deleted
test_http_chunked_case(eY, eh4, 1, "axbcxdxxabcd"); // Chunks not deleted
test_http_chunked_case(eY, eh5, 1, "axbcxdxx"); // Chunks deleted
test_http_chunked_case(eY, eh4, 2, "axbcxdxxabcdaxbcxdxxabcd");
test_http_chunked_case(eY, eh5, 2, "axbcxdxxaxbcxdxx");
// Chunked encoding
test_http_chunked_case(eX, eh4, "axbxcxdxxabcd"); // Chunks not deleted
test_http_chunked_case(eX, eh5, "axbxcxdxx"); // Chunks deleted
test_http_chunked_case(eX, eh4, 1, "axbxcxdxxabcd"); // Chunks not deleted
test_http_chunked_case(eX, eh5, 1, "axbxcxdxx"); // Chunks deleted
test_http_chunked_case(eX, eh4, 2, "axbxcxdxxabcdaxbxcxdxxabcd");
test_http_chunked_case(eX, eh5, 2, "axbxcxdxxaxbxcxdxx");
}
static void test_invalid_listen_addr(void) {
@ -2517,7 +2524,6 @@ int main(void) {
test_http_stream_buffer();
test_http_parse();
test_util();
test_sntp();
test_dns();
test_timer();
test_url();
@ -2534,6 +2540,7 @@ int main(void) {
test_http_no_content_length();
test_http_pipeline();
test_http_range();
test_sntp();
test_mqtt();
printf("SUCCESS. Total tests: %d\n", s_num_tests);
return EXIT_SUCCESS;