| 248 return 0; // end |
249 return 0; // end |
| 249 } |
250 } |
| 250 event->fn = client_io; |
251 event->fn = client_io; |
| 251 |
252 |
| 252 return client_io(ev, event); |
253 return client_io(ev, event); |
| 253 } |
|
| 254 |
|
| 255 static int client_send_request_body(HttpClient *client) { |
|
| 256 size_t rbody_readsize = client->req_buffer_alloc; |
|
| 257 size_t rbody_buf_offset = 0; |
|
| 258 if(client->req_content_length == -1) { |
|
| 259 // chunked transfer encoding: |
|
| 260 // don't fill req_buffer completely, reserve some space for |
|
| 261 // a chunk header, that will be inserted at the beginning |
|
| 262 rbody_readsize -= 16; |
|
| 263 rbody_buf_offset = 16; |
|
| 264 } |
|
| 265 while(!client->request_body_complete) { |
|
| 266 ssize_t r = client->request_body_read(client, client->req_buffer + rbody_buf_offset, rbody_readsize, client->request_body_read_userdata); |
|
| 267 if(r <= 0) { |
|
| 268 if(r == HTTP_CLIENT_CALLBACK_WOULD_BLOCK) { |
|
| 269 return 1; |
|
| 270 } else if(r == 0) { |
|
| 271 // EOF |
|
| 272 client->request_body_complete = 1; |
|
| 273 break; |
|
| 274 } else { |
|
| 275 // error |
|
| 276 client->error = 1; |
|
| 277 return 1; |
|
| 278 } |
|
| 279 } |
|
| 280 |
|
| 281 size_t startpos = 0; |
|
| 282 if(client->req_content_length == -1) { |
|
| 283 char chunkheader[16]; |
|
| 284 int chunkheaderlen = snprintf(chunkheader, 16, "%zx\r\n", (size_t)r); |
|
| 285 startpos = 16 - chunkheaderlen; |
|
| 286 memcpy(client->req_buffer + startpos, chunkheader, chunkheaderlen); |
|
| 287 } |
|
| 288 |
|
| 289 client->req_contentlength_pos += r; |
|
| 290 client->req_buffer_pos = startpos; |
|
| 291 client->req_buffer_len = r; |
|
| 292 if(client_send_request(client)) { |
|
| 293 return 1; |
|
| 294 } |
|
| 295 } |
|
| 296 |
|
| 297 if(client->req_content_length > 0 && client->req_content_length != client->req_contentlength_pos) { |
|
| 298 // incomplete request body |
|
| 299 client->error = 1; |
|
| 300 return 1; |
|
| 301 } |
|
| 302 |
|
| 303 return 0; |
|
| 304 } |
254 } |
| 305 |
255 |
| 306 static int client_io(EventHandler *ev, Event *event) { |
256 static int client_io(EventHandler *ev, Event *event) { |
| 307 HttpClient *client = event->cookie; |
257 HttpClient *client = event->cookie; |
| 308 if(client->req_buffer_pos < client->req_buffer_len) { |
258 if(client->req_buffer_pos < client->req_buffer_len) { |
| 432 } |
382 } |
| 433 |
383 |
| 434 return client->req_buffer_pos < client->req_buffer_len; |
384 return client->req_buffer_pos < client->req_buffer_len; |
| 435 } |
385 } |
| 436 |
386 |
| 437 |
387 static int client_send_request_body(HttpClient *client) { |
| |
388 size_t rbody_readsize = client->req_buffer_alloc; |
| |
389 size_t rbody_buf_offset = 0; |
| |
390 if(client->req_content_length == -1) { |
| |
391 // chunked transfer encoding: |
| |
392 // don't fill req_buffer completely, reserve some space for |
| |
393 // a chunk header, that will be inserted at the beginning |
| |
394 rbody_readsize -= 16; |
| |
395 rbody_buf_offset = 16; |
| |
396 } |
| |
397 while(!client->request_body_complete) { |
| |
398 ssize_t r = client->request_body_read(client, client->req_buffer + rbody_buf_offset, rbody_readsize, client->request_body_read_userdata); |
| |
399 if(r <= 0) { |
| |
400 if(r == HTTP_CLIENT_CALLBACK_WOULD_BLOCK) { |
| |
401 return 1; |
| |
402 } else if(r == 0) { |
| |
403 // EOF |
| |
404 client->request_body_complete = 1; |
| |
405 break; |
| |
406 } else { |
| |
407 // error |
| |
408 client->error = 1; |
| |
409 return 1; |
| |
410 } |
| |
411 } |
| |
412 |
| |
413 size_t startpos = 0; |
| |
414 if(client->req_content_length == -1) { |
| |
415 char chunkheader[16]; |
| |
416 int chunkheaderlen = snprintf(chunkheader, 16, "%zx\r\n", (size_t)r); |
| |
417 startpos = 16 - chunkheaderlen; |
| |
418 memcpy(client->req_buffer + startpos, chunkheader, chunkheaderlen); |
| |
419 } |
| |
420 |
| |
421 client->req_contentlength_pos += r; |
| |
422 client->req_buffer_pos = startpos; |
| |
423 client->req_buffer_len = rbody_buf_offset + r; |
| |
424 if(client_send_request(client)) { |
| |
425 return 1; |
| |
426 } |
| |
427 } |
| |
428 |
| |
429 // chunked transfer encoding: terminate |
| |
430 if(client->req_content_length == -1 && client->request_body_complete != 2) { |
| |
431 memcpy(client->req_buffer, "0\r\n", 3); |
| |
432 client->req_buffer_pos = 0; |
| |
433 client->req_buffer_len = 3; |
| |
434 if(client_send_request(client)) { |
| |
435 return 1; |
| |
436 } |
| |
437 |
| |
438 } else if(client->req_content_length != client->req_contentlength_pos) { |
| |
439 // incomplete request body |
| |
440 client->error = 1; |
| |
441 return 1; |
| |
442 } |
| |
443 |
| |
444 return 0; |
| |
445 } |
| 438 |
446 |
| 439 /* --------------------------------- Tests --------------------------------- */ |
447 /* --------------------------------- Tests --------------------------------- */ |
| 440 |
448 |
| 441 static CX_TEST(test_http_client_send_request) { |
449 static CX_TEST(test_http_client_send_request) { |
| 442 CX_TEST_DO { |
450 CX_TEST_DO { |
| 623 http_client_free(client); |
631 http_client_free(client); |
| 624 cxBufferDestroy(&buf); |
632 cxBufferDestroy(&buf); |
| 625 } |
633 } |
| 626 } |
634 } |
| 627 |
635 |
| |
636 |
| |
637 typedef struct TestRequestBody { |
| |
638 char *content; |
| |
639 size_t length; |
| |
640 size_t pos; |
| |
641 int chunksize; |
| |
642 int max_reads; // max number of reads until test_request_body_read returns 0 |
| |
643 int cur_reads; // current number of read-attempts |
| |
644 } TestRequestBody; |
| |
645 |
| |
646 static ssize_t test_request_body_read(HttpClient *client, void *buf, size_t size, void *userdata) { |
| |
647 TestRequestBody *req = userdata; |
| |
648 req->cur_reads++; |
| |
649 if(req->chunksize == 0 || req->cur_reads > req->max_reads) { |
| |
650 return -1; |
| |
651 } |
| |
652 size_t max = req->length - req->pos; |
| |
653 if(max == 0) { |
| |
654 return 0; |
| |
655 } |
| |
656 |
| |
657 size_t sz = req->chunksize > size ? size : req->chunksize; |
| |
658 if(sz > max) { |
| |
659 sz = max; |
| |
660 } |
| |
661 memcpy(buf, req->content + req->pos, sz); |
| |
662 req->pos += sz; |
| |
663 return sz; |
| |
664 } |
| |
665 |
| |
666 static CX_TEST(test_http_client_send_request_body_chunked) { |
| |
667 CX_TEST_DO { |
| |
668 EventHandler dummy; |
| |
669 HttpClient *client = http_client_new(&dummy); |
| |
670 create_req_buffer(client); |
| |
671 client->req_content_length = -1; |
| |
672 |
| |
673 int fds[2]; |
| |
674 util_socketpair(fds); |
| |
675 util_socket_setnonblock(fds[0], 1); |
| |
676 util_socket_setnonblock(fds[1], 1); |
| |
677 client->socketfd = fds[0]; |
| |
678 int sock = fds[1]; |
| |
679 |
| |
680 // response buffer |
| |
681 CxBuffer buf; |
| |
682 cxBufferInit(&buf, cxDefaultAllocator, NULL, 1024, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); |
| |
683 |
| |
684 // test |
| |
685 char request_body[1024]; |
| |
686 memset(request_body, 'x', 1024); |
| |
687 memset(request_body+128, 'y', 128); |
| |
688 memset(request_body+384, 'z', 128); |
| |
689 memset(request_body+640, ':', 128); |
| |
690 memset(request_body+896, '!', 128); |
| |
691 |
| |
692 TestRequestBody req; |
| |
693 req.content = request_body; |
| |
694 req.length = 1024; |
| |
695 req.pos = 0; |
| |
696 req.chunksize = 16; |
| |
697 req.max_reads = 8; |
| |
698 req.cur_reads = 0; |
| |
699 client->request_body_read = test_request_body_read; |
| |
700 client->request_body_read_userdata = &req; |
| |
701 |
| |
702 memset(client->req_buffer, '_', client->req_buffer_alloc); |
| |
703 client->req_buffer_pos = 0; |
| |
704 client->req_buffer_len = 0; |
| |
705 |
| |
706 // send the first 128 bytes |
| |
707 while(req.cur_reads <= req.max_reads) { |
| |
708 int ret = client_send_request_body(client); |
| |
709 CX_TEST_ASSERT(ret == 1); |
| |
710 CX_TEST_ASSERT(!client->error); |
| |
711 char buf2[1024]; |
| |
712 ssize_t r = read(sock, buf2, 1024); |
| |
713 if(r > 0) { |
| |
714 cxBufferWrite(buf2, 1, r, &buf); |
| |
715 } |
| |
716 } |
| |
717 |
| |
718 // because we are using chunked transfer encoding, the result buffer |
| |
719 // (buf) should contain more than 128 bytes (additional chunk headers) |
| |
720 CX_TEST_ASSERT(buf.pos == 160); |
| |
721 // check for chunk headers |
| |
722 CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf.space, 4), "10\r\n")); |
| |
723 CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf.space+20, 4), "10\r\n")); |
| |
724 CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf.space+40, 4), "10\r\n")); |
| |
725 CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf.space+60, 4), "10\r\n")); |
| |
726 CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf.space+80, 4), "10\r\n")); |
| |
727 CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf.space+100, 4), "10\r\n")); |
| |
728 CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf.space+120, 4), "10\r\n")); |
| |
729 CX_TEST_ASSERT(!cx_strcmp(cx_strn(buf.space+140, 4), "10\r\n")); |
| |
730 |
| |
731 // change chunk size to 128 |
| |
732 req.max_reads = 9999; |
| |
733 req.chunksize = 128; |
| |
734 while(req.cur_reads <= req.max_reads) { |
| |
735 int ret = client_send_request_body(client); |
| |
736 CX_TEST_ASSERT(!client->error); |
| |
737 char buf2[2048]; |
| |
738 ssize_t r; |
| |
739 while((r = read(sock, buf2, 2048)) > 0) { |
| |
740 cxBufferWrite(buf2, 1, r, &buf); |
| |
741 } |
| |
742 if(ret == 0) { |
| |
743 break; |
| |
744 } |
| |
745 } |
| |
746 CX_TEST_ASSERT(req.cur_reads < req.max_reads); |
| |
747 CX_TEST_ASSERT(buf.size == 1084 + 3); |
| |
748 |
| |
749 // check chunks |
| |
750 char testbuf[128]; |
| |
751 memset(testbuf, 'x', 128); |
| |
752 cxstring x1 = cx_strn(buf.space + 296, 128); |
| |
753 CX_TEST_ASSERT(!cx_strcmp(x1, cx_strn(testbuf, 128))); |
| |
754 cxstring x2 = cx_strn(buf.space + 560, 128); |
| |
755 CX_TEST_ASSERT(!cx_strcmp(x2, cx_strn(testbuf, 128))); |
| |
756 cxstring x3 = cx_strn(buf.space + 824, 128); |
| |
757 CX_TEST_ASSERT(!cx_strcmp(x3, cx_strn(testbuf, 128))); |
| |
758 memset(testbuf, 'y', 128); |
| |
759 cxstring y1 = cx_strn(buf.space + 164, 128); |
| |
760 CX_TEST_ASSERT(!cx_strcmp(y1, cx_strn(testbuf, 128))); |
| |
761 cxstring z1 = cx_strn(buf.space + 428, 128); |
| |
762 memset(testbuf, 'z', 128); |
| |
763 CX_TEST_ASSERT(!cx_strcmp(z1, cx_strn(testbuf, 128))); |
| |
764 |
| |
765 // cleanup |
| |
766 close(fds[0]); |
| |
767 close(fds[1]); |
| |
768 http_client_free(client); |
| |
769 cxBufferDestroy(&buf); |
| |
770 } |
| |
771 } |
| |
772 |
| 628 void http_client_add_tests(CxTestSuite *suite) { |
773 void http_client_add_tests(CxTestSuite *suite) { |
| 629 cx_test_register(suite, test_http_client_send_request); |
774 cx_test_register(suite, test_http_client_send_request); |
| |
775 cx_test_register(suite, test_http_client_send_request_body_chunked); |
| 630 cx_test_register(suite, test_http_client_io_simple); |
776 cx_test_register(suite, test_http_client_io_simple); |
| 631 } |
777 } |